diff options
| author | Adam Malczewski <[email protected]> | 2026-05-23 04:18:54 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-05-23 04:18:54 +0900 |
| commit | 80ce5960c479fe35ab72c822e3b67799d7e1491e (patch) | |
| tree | 2b58bb1cfbc458a70ad021adc1ce0ae6ed810d84 /packages/api/src | |
| parent | c47346cc6237044ecb60ff22c4011d89744af581 (diff) | |
| download | dispatch-80ce5960c479fe35ab72c822e3b67799d7e1491e.tar.gz dispatch-80ce5960c479fe35ab72c822e3b67799d7e1491e.zip | |
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_<uuid> 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)
Diffstat (limited to 'packages/api/src')
| -rw-r--r-- | packages/api/src/agent-manager.ts | 34 | ||||
| -rw-r--r-- | packages/api/src/index.ts | 1 |
2 files changed, 31 insertions, 4 deletions
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<string, string> = { "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, }; |
