From 80ce5960c479fe35ab72c822e3b67799d7e1491e Mon Sep 17 00:00:00 2001 From: Adam Malczewski Date: Sat, 23 May 2026 04:18:54 +0900 Subject: feat: web_search + youtube_transcribe tools, shell interrupt backgrounding, fixes - Add web_search tool (Firecrawl POST to /v1/search with query, limit, lang, country, scrapeOptions) - Add youtube_transcribe tool (GET to transcriber service, handles completed/queued/failed statuses) - Both tools registered for parent agents (always) and child agents (permission-gated) - Added to summon enum, TOOL_DESCRIPTIONS, and core exports - Shell interrupt: run_shell now races against user queue interrupt - When interrupted, command continues in background with run_shell_ job ID - BackgroundShellStore holds running processes, auto-cleans 10min after completion - retrieve tool extended to handle both agent IDs and shell job IDs - Tool error detection: results starting with 'Error:' now marked isError in UI - Fix TS error: cast unavailMatch[1] regex capture group to string - Docker: network_mode host for Tailscale/LAN access to external services - Bun.serve idleTimeout set to 60s (was default 10s) - KeyUsage: clearer message when OpenCode usage data unavailable - Firecrawl: only send scrapeOptions when scrape=true (avoid 400 on instances without scrape support) --- packages/api/src/agent-manager.ts | 34 ++++++++++++++++++++++++++++++---- packages/api/src/index.ts | 1 + 2 files changed, 31 insertions(+), 4 deletions(-) (limited to 'packages/api/src') diff --git a/packages/api/src/agent-manager.ts b/packages/api/src/agent-manager.ts index 70cada4..b0a2f56 100644 --- a/packages/api/src/agent-manager.ts +++ b/packages/api/src/agent-manager.ts @@ -11,10 +11,13 @@ import { createReadFileTool, createRetrieveTool, createRunShellTool, + BackgroundShellStore, createSkillsWatcher, createSummonTool, createTaskListTool, + createWebSearchTool, createWriteFileTool, + createYoutubeTranscribeTool, type DispatchConfig, getClaudeAccountsFromDB, getSetting, @@ -46,6 +49,8 @@ const TOOL_DESCRIPTIONS: Record = { "Spawn a child agent to work on a task independently. Returns an agent_id immediately (non-blocking). Use retrieve to collect the result later.", retrieve: "Wait for a child agent to finish and get its result (blocking). Pass the agent_id from summon.", + web_search: "Search the web and optionally scrape full page content from results.", + youtube_transcribe: "Fetch the transcript/subtitles for a YouTube video.", }; const DEFAULT_SYSTEM_PROMPT = @@ -134,6 +139,8 @@ interface TabAgent { messageQueue: QueuedMessage[]; /** Callbacks to wake up blocking tools waiting for queued messages. */ queueListeners: Array<() => void>; + /** Store for shell commands backgrounded due to user interrupt. */ + shellStore: BackgroundShellStore; } export class AgentManager { @@ -269,6 +276,7 @@ export class AgentManager { taskList, messageQueue: [], queueListeners: [], + shellStore: new BackgroundShellStore(), }; this.tabAgents.set(tabId, tabAgent); } @@ -346,7 +354,13 @@ export class AgentManager { toolEntries.push({ name: "write_file", tool: createWriteFileTool(workingDirectory) }); } if (allowed.has("run_shell")) { - toolEntries.push({ name: "run_shell", tool: createRunShellTool(workingDirectory) }); + toolEntries.push({ name: "run_shell", tool: createRunShellTool(workingDirectory, tabAgent.shellStore) }); + } + if (allowed.has("web_search")) { + toolEntries.push({ name: "web_search", tool: createWebSearchTool() }); + } + if (allowed.has("youtube_transcribe")) { + toolEntries.push({ name: "youtube_transcribe", tool: createYoutubeTranscribeTool() }); } if (allowed.has("todo")) { toolEntries.push({ name: "todo", tool: createTaskListTool(tabAgent.taskList) }); @@ -370,7 +384,12 @@ export class AgentManager { if (allowed.has("retrieve")) { toolEntries.push({ name: "retrieve", - tool: createRetrieveTool({ getResult: (id) => this.getChildResult(id) }), + tool: createRetrieveTool({ + getResult: (id) => + tabAgent.shellStore.has(id) + ? tabAgent.shellStore.getResult(id) + : this.getChildResult(id), + }), }); } } else { @@ -383,8 +402,10 @@ export class AgentManager { toolEntries.push({ name: "write_file", tool: createWriteFileTool(workingDirectory) }); } if (permBash) { - toolEntries.push({ name: "run_shell", tool: createRunShellTool(workingDirectory) }); + toolEntries.push({ name: "run_shell", tool: createRunShellTool(workingDirectory, tabAgent.shellStore) }); } + toolEntries.push({ name: "web_search", tool: createWebSearchTool() }); + toolEntries.push({ name: "youtube_transcribe", tool: createYoutubeTranscribeTool() }); toolEntries.push({ name: "todo", tool: createTaskListTool(tabAgent.taskList) }); if (permSummon) { // Capture parent's allowed tool names for child permission enforcement @@ -404,7 +425,12 @@ export class AgentManager { }); toolEntries.push({ name: "retrieve", - tool: createRetrieveTool({ getResult: (id) => this.getChildResult(id) }), + tool: createRetrieveTool({ + getResult: (id) => + tabAgent.shellStore.has(id) + ? tabAgent.shellStore.getResult(id) + : this.getChildResult(id), + }), }); } } diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts index b188e39..a0ad025 100644 --- a/packages/api/src/index.ts +++ b/packages/api/src/index.ts @@ -72,6 +72,7 @@ export { app }; export default { port: Number(process.env.PORT) || 3000, + idleTimeout: 60, fetch: app.fetch, websocket, }; -- cgit v1.2.3