summaryrefslogtreecommitdiffhomepage
path: root/packages/api/src
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-05-23 04:18:54 +0900
committerAdam Malczewski <[email protected]>2026-05-23 04:18:54 +0900
commit80ce5960c479fe35ab72c822e3b67799d7e1491e (patch)
tree2b58bb1cfbc458a70ad021adc1ce0ae6ed810d84 /packages/api/src
parentc47346cc6237044ecb60ff22c4011d89744af581 (diff)
downloaddispatch-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.ts34
-rw-r--r--packages/api/src/index.ts1
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,
};