diff --git a/CHANGELOG.md b/CHANGELOG.md index 991a73075..7962f8276 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Breaking - Identifiers: rename bundle IDs and internal domains to `com.clawdis.*` (macOS: `com.clawdis.mac`, iOS: `com.clawdis.ios`, Android: `com.clawdis.android`) and update the gateway LaunchAgent label to `com.clawdis.gateway`. +- Agent tools: drop the `clawdis_` prefix (`browser`, `canvas`, `nodes`, `cron`, `gateway`). ### Features - Gateway: support `gateway.port` + `CLAWDIS_GATEWAY_PORT` across CLI, TUI, and macOS app. @@ -230,7 +231,7 @@ - Hook mappings + Gmail Pub/Sub helper (`clawdis hooks gmail setup/run`) with auto-renew + Tailscale Funnel support. - Command queue modes + per-session overrides (`/queue ...`) and new `agent.maxConcurrent` cap for safe parallelism across sessions. - Background bash tasks: `bash` auto-yields after 20s (or on demand) with a `process` tool to list/poll/log/write/kill sessions. -- Gateway in-process restart: `clawdis_gateway` tool action triggers a SIGUSR1 restart without needing a supervisor. +- Gateway in-process restart: `gateway` tool action triggers a SIGUSR1 restart without needing a supervisor. ### Breaking - Config refactor: `inbound.*` removed; use top-level `routing` (allowlists + group rules + transcription), `messages` (prefixes/timestamps), and `session` (scoping/store/mainKey). No legacy keys read. diff --git a/docs/gateway.md b/docs/gateway.md index ca9c2df0e..a00e6d087 100644 --- a/docs/gateway.md +++ b/docs/gateway.md @@ -29,7 +29,7 @@ pnpm gateway:watch - Pass `--verbose` to mirror debug logging (handshakes, req/res, events) from the log file into stdio when troubleshooting. - `--force` uses `lsof` to find listeners on the chosen port, sends SIGTERM, logs what it killed, then starts the gateway (fails fast if `lsof` is missing). - If you run under a supervisor (launchd/systemd/mac app child-process mode), a stop/restart typically sends **SIGTERM**; older builds may surface this as `pnpm` `ELIFECYCLE` exit code **143** (SIGTERM), which is a normal shutdown, not a crash. -- **SIGUSR1** triggers an in-process restart (no external supervisor required). This is what the `clawdis_gateway` agent tool uses. +- **SIGUSR1** triggers an in-process restart (no external supervisor required). This is what the `gateway` agent tool uses. - Optional shared secret: pass `--token ` or set `CLAWDIS_GATEWAY_TOKEN` to require clients to send `connect.params.auth.token`. - Port precedence: `--port` > `CLAWDIS_GATEWAY_PORT` > `gateway.port` > default `18789`. diff --git a/docs/tools.md b/docs/tools.md index 72d2a4c7a..8cb0a41eb 100644 --- a/docs/tools.md +++ b/docs/tools.md @@ -37,7 +37,7 @@ Notes: - `poll` returns new output and exit status when complete. - `log` supports line-based `offset`/`limit` (omit `offset` to grab the last N lines). -### `clawdis_browser` +### `browser` Control the dedicated clawd browser. Core actions: @@ -56,7 +56,7 @@ Notes: - `upload` can optionally pass a `ref` to auto-click after arming. - `upload` also supports `inputRef` (aria ref) or `element` (CSS selector) to set `` directly. -### `clawdis_canvas` +### `canvas` Drive the node Canvas (present, eval, snapshot, A2UI). Core actions: @@ -70,7 +70,7 @@ Notes: - A2UI is v0.8 only (no `createSurface`); the CLI rejects v0.9 JSONL with line errors. - Quick smoke: `clawdis canvas a2ui push --text "Hello from A2UI"`. -### `clawdis_nodes` +### `nodes` Discover and target paired nodes; send notifications; capture camera/screen. Core actions: @@ -84,7 +84,7 @@ Notes: - Images return image blocks + `MEDIA:`. - Videos return `FILE:` (mp4). -### `clawdis_cron` +### `cron` Manage Gateway cron jobs and wakeups. Core actions: @@ -96,7 +96,7 @@ Notes: - `add` expects a full cron job object (same schema as `cron.add` RPC). - `update` uses `{ jobId, patch }`. -### `clawdis_gateway` +### `gateway` Restart the running Gateway process (in-place). Core actions: @@ -147,7 +147,7 @@ Notes: ## Parameters (common) -Gateway-backed tools (`clawdis_canvas`, `clawdis_nodes`, `clawdis_cron`): +Gateway-backed tools (`canvas`, `nodes`, `cron`): - `gatewayUrl` (default `ws://127.0.0.1:18789`) - `gatewayToken` (if auth enabled) - `timeoutMs` @@ -158,18 +158,18 @@ Browser tool: ## Recommended agent flows Browser automation: -1) `clawdis_browser` → `status` / `start` +1) `browser` → `status` / `start` 2) `snapshot` (ai or aria) 3) `act` (click/type/press) 4) `screenshot` if you need visual confirmation Canvas render: -1) `clawdis_canvas` → `present` +1) `canvas` → `present` 2) `a2ui_push` (optional) 3) `snapshot` Node targeting: -1) `clawdis_nodes` → `status` +1) `nodes` → `status` 2) `describe` on the chosen node 3) `notify` / `camera_snap` / `screen_record` diff --git a/src/agents/clawdis-gateway-tool.test.ts b/src/agents/clawdis-gateway-tool.test.ts index b61c0f1f9..9bdf6d321 100644 --- a/src/agents/clawdis-gateway-tool.test.ts +++ b/src/agents/clawdis-gateway-tool.test.ts @@ -2,17 +2,17 @@ import { describe, expect, it, vi } from "vitest"; import { createClawdisTools } from "./clawdis-tools.js"; -describe("clawdis_gateway tool", () => { +describe("gateway tool", () => { it("schedules SIGUSR1 restart", async () => { vi.useFakeTimers(); const kill = vi.spyOn(process, "kill").mockImplementation(() => true); try { const tool = createClawdisTools().find( - (candidate) => candidate.name === "clawdis_gateway", + (candidate) => candidate.name === "gateway", ); expect(tool).toBeDefined(); - if (!tool) throw new Error("missing clawdis_gateway tool"); + if (!tool) throw new Error("missing gateway tool"); const result = await tool.execute("call1", { action: "restart", diff --git a/src/agents/clawdis-tools.camera.test.ts b/src/agents/clawdis-tools.camera.test.ts index a97bfde64..03fa802b8 100644 --- a/src/agents/clawdis-tools.camera.test.ts +++ b/src/agents/clawdis-tools.camera.test.ts @@ -12,7 +12,7 @@ vi.mock("../media/image-ops.js", () => ({ import { createClawdisTools } from "./clawdis-tools.js"; -describe("clawdis_nodes camera_snap", () => { +describe("nodes camera_snap", () => { beforeEach(() => { callGateway.mockReset(); }); @@ -36,9 +36,9 @@ describe("clawdis_nodes camera_snap", () => { }); const tool = createClawdisTools().find( - (candidate) => candidate.name === "clawdis_nodes", + (candidate) => candidate.name === "nodes", ); - if (!tool) throw new Error("missing clawdis_nodes tool"); + if (!tool) throw new Error("missing nodes tool"); const result = await tool.execute("call1", { action: "camera_snap", @@ -76,9 +76,9 @@ describe("clawdis_nodes camera_snap", () => { }); const tool = createClawdisTools().find( - (candidate) => candidate.name === "clawdis_nodes", + (candidate) => candidate.name === "nodes", ); - if (!tool) throw new Error("missing clawdis_nodes tool"); + if (!tool) throw new Error("missing nodes tool"); await tool.execute("call1", { action: "camera_snap", diff --git a/src/agents/clawdis-tools.ts b/src/agents/clawdis-tools.ts index af40a885f..1b19f4f3f 100644 --- a/src/agents/clawdis-tools.ts +++ b/src/agents/clawdis-tools.ts @@ -577,8 +577,8 @@ const BrowserToolSchema = Type.Union([ function createBrowserTool(): AnyAgentTool { return { - label: "Clawdis Browser", - name: "clawdis_browser", + label: "Browser", + name: "browser", description: "Control clawd's dedicated browser (status/start/stop/tabs/open/snapshot/screenshot/actions). Use snapshot+act for UI automation. Avoid act:wait by default; use only in exceptional cases when no reliable UI state exists.", parameters: BrowserToolSchema, @@ -837,8 +837,8 @@ const CanvasToolSchema = Type.Union([ function createCanvasTool(): AnyAgentTool { return { - label: "Clawdis Canvas", - name: "clawdis_canvas", + label: "Canvas", + name: "canvas", description: "Control node canvases (present/hide/navigate/eval/snapshot/A2UI). Use snapshot to capture the rendered UI.", parameters: CanvasToolSchema, @@ -1088,8 +1088,8 @@ const NodesToolSchema = Type.Union([ function createNodesTool(): AnyAgentTool { return { - label: "Clawdis Nodes", - name: "clawdis_nodes", + label: "Nodes", + name: "nodes", description: "Discover and control paired nodes (status/describe/pairing/notify/camera/screen).", parameters: NodesToolSchema, @@ -1454,8 +1454,8 @@ const CronToolSchema = Type.Union([ function createCronTool(): AnyAgentTool { return { - label: "Clawdis Cron", - name: "clawdis_cron", + label: "Cron", + name: "cron", description: "Manage Gateway cron jobs (status/list/add/update/remove/run/runs) and send wake events.", parameters: CronToolSchema, @@ -1736,7 +1736,7 @@ const DiscordToolSchema = Type.Union([ function createDiscordTool(): AnyAgentTool { return { - label: "Clawdis Discord", + label: "Discord", name: "discord", description: "Manage Discord messages, reactions, and moderation.", parameters: DiscordToolSchema, @@ -2264,8 +2264,8 @@ function createDiscordTool(): AnyAgentTool { function createGatewayTool(): AnyAgentTool { return { - label: "Clawdis Gateway", - name: "clawdis_gateway", + label: "Gateway", + name: "gateway", description: "Restart the running gateway process in-place (SIGUSR1) without needing an external supervisor. Use delayMs to avoid interrupting an in-flight reply.", parameters: GatewayToolSchema, diff --git a/src/agents/pi-tools.test.ts b/src/agents/pi-tools.test.ts index 06ef91229..c915cc56f 100644 --- a/src/agents/pi-tools.test.ts +++ b/src/agents/pi-tools.test.ts @@ -9,7 +9,7 @@ import { createClawdisCodingTools } from "./pi-tools.js"; describe("createClawdisCodingTools", () => { it("merges properties for union tool schemas", () => { const tools = createClawdisCodingTools(); - const browser = tools.find((tool) => tool.name === "clawdis_browser"); + const browser = tools.find((tool) => tool.name === "browser"); expect(browser).toBeDefined(); const parameters = browser?.parameters as { anyOf?: unknown[]; @@ -26,9 +26,7 @@ describe("createClawdisCodingTools", () => { it("preserves union action values in merged schema", () => { const tools = createClawdisCodingTools(); - const toolNames = tools - .filter((tool) => tool.name.startsWith("clawdis_")) - .map((tool) => tool.name); + const toolNames = ["browser", "canvas", "nodes", "cron", "gateway"]; for (const name of toolNames) { const tool = tools.find((candidate) => candidate.name === name); diff --git a/src/agents/system-prompt.ts b/src/agents/system-prompt.ts index facddb19f..0b7e00582 100644 --- a/src/agents/system-prompt.ts +++ b/src/agents/system-prompt.ts @@ -62,10 +62,10 @@ export function buildAgentSystemPromptAppend(params: { "- bash: run shell commands (supports background via yieldMs/background)", "- process: manage background bash sessions", "- whatsapp_login: generate a WhatsApp QR code and wait for linking", - "- clawdis_browser: control clawd's dedicated browser", - "- clawdis_canvas: present/eval/snapshot the Canvas", - "- clawdis_nodes: list/describe/notify/camera/screen on paired nodes", - "- clawdis_cron: manage cron jobs and wake events", + "- browser: control clawd's dedicated browser", + "- canvas: present/eval/snapshot the Canvas", + "- nodes: list/describe/notify/camera/screen on paired nodes", + "- cron: manage cron jobs and wake events", "TOOLS.md does not control tool availability; it is user guidance for how to use external tools.", "", "## Workspace", diff --git a/src/auto-reply/tool-meta.test.ts b/src/auto-reply/tool-meta.test.ts index 214738b8a..01aa83ea5 100644 --- a/src/auto-reply/tool-meta.test.ts +++ b/src/auto-reply/tool-meta.test.ts @@ -24,6 +24,7 @@ describe("tool meta formatting", () => { vi.stubEnv("HOME", "/Users/test"); expect(shortenMeta("/Users/test/a.txt")).toBe("~/a.txt"); expect(shortenMeta("/Users/test/a.txt:12")).toBe("~/a.txt:12"); + expect(shortenMeta("cd /Users/test/dir && ls")).toBe("cd ~/dir && ls"); expect(shortenMeta("")).toBe(""); }); @@ -35,7 +36,7 @@ describe("tool meta formatting", () => { "note", "a→b", ]); - expect(out).toMatch(/^🛠️ fs/); + expect(out).toMatch(/^🧩 fs/); expect(out).toContain("~/dir/{a.txt, b.txt}"); expect(out).toContain("note"); expect(out).toContain("a→b"); @@ -43,8 +44,8 @@ describe("tool meta formatting", () => { it("formats prefixes with default labels", () => { vi.stubEnv("HOME", "/Users/test"); - expect(formatToolPrefix(undefined, undefined)).toBe("🛠️ tool"); - expect(formatToolPrefix("x", "/Users/test/a.txt")).toBe("🛠️ x: ~/a.txt"); + expect(formatToolPrefix(undefined, undefined)).toBe("🧩 tool"); + expect(formatToolPrefix("x", "/Users/test/a.txt")).toBe("🧩 x: ~/a.txt"); }); }); diff --git a/src/auto-reply/tool-meta.ts b/src/auto-reply/tool-meta.ts index 532994459..5eb98427f 100644 --- a/src/auto-reply/tool-meta.ts +++ b/src/auto-reply/tool-meta.ts @@ -2,17 +2,17 @@ export const TOOL_RESULT_DEBOUNCE_MS = 500; export const TOOL_RESULT_FLUSH_COUNT = 5; const TOOL_EMOJI_BY_NAME: Record = { - bash: "💻", + bash: "🛠️", process: "🧰", read: "📖", write: "✍️", edit: "📝", attach: "📎", - clawdis_browser: "🌐", - clawdis_canvas: "🖼️", - clawdis_nodes: "📱", - clawdis_cron: "⏰", - clawdis_gateway: "🔌", + browser: "🌐", + canvas: "🖼️", + nodes: "📱", + cron: "⏰", + gateway: "🔌", whatsapp_login: "🟢", discord: "💬", }; @@ -20,7 +20,13 @@ const TOOL_EMOJI_BY_NAME: Record = { function resolveToolEmoji(toolName?: string): string { const key = toolName?.trim().toLowerCase(); if (key && TOOL_EMOJI_BY_NAME[key]) return TOOL_EMOJI_BY_NAME[key]; - return "🛠️"; + return "🧩"; +} + +function shortenHomeInString(input: string): string { + const home = process.env.HOME; + if (!home) return input; + return input.split(home).join("~"); } export function shortenPath(p: string): string { @@ -33,10 +39,10 @@ export function shortenPath(p: string): string { export function shortenMeta(meta: string): string { if (!meta) return meta; const colonIdx = meta.indexOf(":"); - if (colonIdx === -1) return shortenPath(meta); + if (colonIdx === -1) return shortenHomeInString(meta); const base = meta.slice(0, colonIdx); const rest = meta.slice(colonIdx); - return `${shortenPath(base)}${rest}`; + return `${shortenHomeInString(base)}${rest}`; } export function formatToolAggregate( diff --git a/src/web/auto-reply.test.ts b/src/web/auto-reply.test.ts index 2b4053e91..8d1a1d1c0 100644 --- a/src/web/auto-reply.test.ts +++ b/src/web/auto-reply.test.ts @@ -1591,8 +1591,8 @@ describe("web auto-reply", () => { _ctx, opts?: { onToolResult?: (r: { text: string }) => Promise }, ) => { - await opts?.onToolResult?.({ text: "🛠️ tool1" }); - await opts?.onToolResult?.({ text: "🛠️ tool2" }); + await opts?.onToolResult?.({ text: "🧩 tool1" }); + await opts?.onToolResult?.({ text: "🧩 tool2" }); return { text: "final" }; }, ); @@ -1611,7 +1611,7 @@ describe("web auto-reply", () => { }); const replies = reply.mock.calls.map((call) => call[0]); - expect(replies).toEqual(["🦞 🛠️ tool1", "🦞 🛠️ tool2", "🦞 final"]); + expect(replies).toEqual(["🦞 🧩 tool1", "🦞 🧩 tool2", "🦞 final"]); resetLoadConfigMock(); }); });