verybot 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of verybot might be problematic. Click here for more details.

Files changed (244) hide show
  1. package/dist/brain/agent-registry.d.ts +75 -0
  2. package/dist/brain/agent-registry.js +124 -0
  3. package/dist/brain/agent.d.ts +146 -0
  4. package/dist/brain/agent.js +680 -0
  5. package/dist/brain/channel-store.d.ts +27 -0
  6. package/dist/brain/channel-store.js +78 -0
  7. package/dist/brain/compaction.d.ts +37 -0
  8. package/dist/brain/compaction.js +214 -0
  9. package/dist/brain/context.d.ts +33 -0
  10. package/dist/brain/context.js +77 -0
  11. package/dist/brain/delegation-store.d.ts +33 -0
  12. package/dist/brain/delegation-store.js +106 -0
  13. package/dist/brain/loop.d.ts +21 -0
  14. package/dist/brain/loop.js +161 -0
  15. package/dist/brain/mcp-adapter.d.ts +39 -0
  16. package/dist/brain/mcp-adapter.js +227 -0
  17. package/dist/brain/memory-extractor.d.ts +26 -0
  18. package/dist/brain/memory-extractor.js +82 -0
  19. package/dist/brain/providers.d.ts +10 -0
  20. package/dist/brain/providers.js +69 -0
  21. package/dist/brain/queue.d.ts +18 -0
  22. package/dist/brain/queue.js +84 -0
  23. package/dist/brain/run-tools.d.ts +47 -0
  24. package/dist/brain/run-tools.js +84 -0
  25. package/dist/brain/session-key.d.ts +23 -0
  26. package/dist/brain/session-key.js +41 -0
  27. package/dist/brain/session-state.d.ts +36 -0
  28. package/dist/brain/session-state.js +51 -0
  29. package/dist/brain/session-store.d.ts +50 -0
  30. package/dist/brain/session-store.js +207 -0
  31. package/dist/brain/session.d.ts +32 -0
  32. package/dist/brain/session.js +75 -0
  33. package/dist/brain/utils.d.ts +4 -0
  34. package/dist/brain/utils.js +26 -0
  35. package/dist/brain/worker-coordinator.d.ts +25 -0
  36. package/dist/brain/worker-coordinator.js +83 -0
  37. package/dist/channels/commands.d.ts +35 -0
  38. package/dist/channels/commands.js +65 -0
  39. package/dist/channels/discord/channel.d.ts +18 -0
  40. package/dist/channels/discord/channel.js +154 -0
  41. package/dist/channels/discord/markdown.d.ts +19 -0
  42. package/dist/channels/discord/markdown.js +62 -0
  43. package/dist/channels/manager.d.ts +29 -0
  44. package/dist/channels/manager.js +100 -0
  45. package/dist/channels/slack/channel.d.ts +26 -0
  46. package/dist/channels/slack/channel.js +207 -0
  47. package/dist/channels/slack/markdown.d.ts +19 -0
  48. package/dist/channels/slack/markdown.js +62 -0
  49. package/dist/channels/specs.d.ts +21 -0
  50. package/dist/channels/specs.js +96 -0
  51. package/dist/channels/telegram/channel.d.ts +18 -0
  52. package/dist/channels/telegram/channel.js +156 -0
  53. package/dist/channels/telegram/markdown.d.ts +17 -0
  54. package/dist/channels/telegram/markdown.js +66 -0
  55. package/dist/channels/types.d.ts +26 -0
  56. package/dist/channels/types.js +1 -0
  57. package/dist/channels/whatsapp/channel.d.ts +23 -0
  58. package/dist/channels/whatsapp/channel.js +242 -0
  59. package/dist/channels/whatsapp/markdown.d.ts +20 -0
  60. package/dist/channels/whatsapp/markdown.js +51 -0
  61. package/dist/cli/config.d.ts +5 -0
  62. package/dist/cli/config.js +78 -0
  63. package/dist/cli/index.d.ts +5 -0
  64. package/dist/cli/index.js +13 -0
  65. package/dist/computer/browser/actions.d.ts +31 -0
  66. package/dist/computer/browser/actions.js +148 -0
  67. package/dist/computer/browser/manager.d.ts +55 -0
  68. package/dist/computer/browser/manager.js +496 -0
  69. package/dist/computer/browser/profile-badge.d.ts +13 -0
  70. package/dist/computer/browser/profile-badge.js +67 -0
  71. package/dist/computer/browser/screenshot.d.ts +5 -0
  72. package/dist/computer/browser/screenshot.js +21 -0
  73. package/dist/computer/browser/snapshot.d.ts +30 -0
  74. package/dist/computer/browser/snapshot.js +242 -0
  75. package/dist/computer/browser/tools.d.ts +5 -0
  76. package/dist/computer/browser/tools.js +167 -0
  77. package/dist/computer/desktop/adapter.d.ts +25 -0
  78. package/dist/computer/desktop/adapter.js +11 -0
  79. package/dist/computer/desktop/macos.d.ts +24 -0
  80. package/dist/computer/desktop/macos.js +223 -0
  81. package/dist/computer/desktop/tools.d.ts +25 -0
  82. package/dist/computer/desktop/tools.js +114 -0
  83. package/dist/config/agent-config.d.ts +41 -0
  84. package/dist/config/agent-config.js +14 -0
  85. package/dist/config/model-catalog.d.ts +22 -0
  86. package/dist/config/model-catalog.js +99 -0
  87. package/dist/config/store.d.ts +25 -0
  88. package/dist/config/store.js +143 -0
  89. package/dist/config.d.ts +103 -0
  90. package/dist/config.js +224 -0
  91. package/dist/control-ui/assets/index-BANXNUyt.js +143 -0
  92. package/dist/control-ui/assets/index-BSUFrP9R.css +1 -0
  93. package/dist/control-ui/assets/noto-sans-cyrillic-ext-wght-normal-DSNfmdVt.woff2 +0 -0
  94. package/dist/control-ui/assets/noto-sans-cyrillic-wght-normal-B2hlT84T.woff2 +0 -0
  95. package/dist/control-ui/assets/noto-sans-devanagari-wght-normal-Cv-Vwajv.woff2 +0 -0
  96. package/dist/control-ui/assets/noto-sans-greek-ext-wght-normal-12T8GTDR.woff2 +0 -0
  97. package/dist/control-ui/assets/noto-sans-greek-wght-normal-Ymb6dZNd.woff2 +0 -0
  98. package/dist/control-ui/assets/noto-sans-latin-ext-wght-normal-W1qJv59z.woff2 +0 -0
  99. package/dist/control-ui/assets/noto-sans-latin-wght-normal-BYSzYMf3.woff2 +0 -0
  100. package/dist/control-ui/assets/noto-sans-vietnamese-wght-normal-DLTJy58D.woff2 +0 -0
  101. package/dist/control-ui/index.html +14 -0
  102. package/dist/control-ui/vite.svg +1 -0
  103. package/dist/events.d.ts +2 -0
  104. package/dist/events.js +11 -0
  105. package/dist/gateway/broadcast.d.ts +5 -0
  106. package/dist/gateway/broadcast.js +33 -0
  107. package/dist/gateway/methods/chat.d.ts +24 -0
  108. package/dist/gateway/methods/chat.js +19 -0
  109. package/dist/gateway/methods/config.d.ts +13 -0
  110. package/dist/gateway/methods/config.js +14 -0
  111. package/dist/gateway/methods/models.d.ts +10 -0
  112. package/dist/gateway/methods/models.js +14 -0
  113. package/dist/gateway/methods/prompt-templates.d.ts +23 -0
  114. package/dist/gateway/methods/prompt-templates.js +82 -0
  115. package/dist/gateway/methods/scheduler.d.ts +62 -0
  116. package/dist/gateway/methods/scheduler.js +129 -0
  117. package/dist/gateway/methods/sessions.d.ts +26 -0
  118. package/dist/gateway/methods/sessions.js +54 -0
  119. package/dist/gateway/methods/skills.d.ts +35 -0
  120. package/dist/gateway/methods/skills.js +202 -0
  121. package/dist/gateway/methods/system.d.ts +12 -0
  122. package/dist/gateway/methods/system.js +39 -0
  123. package/dist/gateway/methods/tasks.d.ts +21 -0
  124. package/dist/gateway/methods/tasks.js +46 -0
  125. package/dist/gateway/methods/teams.d.ts +70 -0
  126. package/dist/gateway/methods/teams.js +374 -0
  127. package/dist/gateway/methods/tools.d.ts +6 -0
  128. package/dist/gateway/methods/tools.js +7 -0
  129. package/dist/gateway/methods/whatsapp.d.ts +19 -0
  130. package/dist/gateway/methods/whatsapp.js +35 -0
  131. package/dist/gateway/rpc.d.ts +38 -0
  132. package/dist/gateway/rpc.js +75 -0
  133. package/dist/gateway/server.d.ts +4 -0
  134. package/dist/gateway/server.js +133 -0
  135. package/dist/index.d.ts +1 -0
  136. package/dist/index.js +212 -0
  137. package/dist/integrations/github.d.ts +7 -0
  138. package/dist/integrations/github.js +133 -0
  139. package/dist/integrations/mcp.d.ts +7 -0
  140. package/dist/integrations/mcp.js +106 -0
  141. package/dist/integrations/registry.d.ts +43 -0
  142. package/dist/integrations/registry.js +258 -0
  143. package/dist/integrations/scanner.d.ts +10 -0
  144. package/dist/integrations/scanner.js +122 -0
  145. package/dist/integrations/twitter.d.ts +10 -0
  146. package/dist/integrations/twitter.js +120 -0
  147. package/dist/integrations/types.d.ts +72 -0
  148. package/dist/integrations/types.js +1 -0
  149. package/dist/logger.d.ts +16 -0
  150. package/dist/logger.js +104 -0
  151. package/dist/markdown/chunk.d.ts +9 -0
  152. package/dist/markdown/chunk.js +52 -0
  153. package/dist/markdown/ir.d.ts +37 -0
  154. package/dist/markdown/ir.js +529 -0
  155. package/dist/markdown/render.d.ts +22 -0
  156. package/dist/markdown/render.js +148 -0
  157. package/dist/markdown/table-render.d.ts +43 -0
  158. package/dist/markdown/table-render.js +219 -0
  159. package/dist/markdown/tables.d.ts +17 -0
  160. package/dist/markdown/tables.js +27 -0
  161. package/dist/memory/embedding.d.ts +16 -0
  162. package/dist/memory/embedding.js +66 -0
  163. package/dist/memory/extractor.d.ts +6 -0
  164. package/dist/memory/extractor.js +72 -0
  165. package/dist/memory/search.d.ts +15 -0
  166. package/dist/memory/search.js +57 -0
  167. package/dist/memory/store.d.ts +34 -0
  168. package/dist/memory/store.js +328 -0
  169. package/dist/memory/types.d.ts +9 -0
  170. package/dist/memory/types.js +2 -0
  171. package/dist/paths.d.ts +20 -0
  172. package/dist/paths.js +29 -0
  173. package/dist/prompt-templates/builtins.d.ts +2 -0
  174. package/dist/prompt-templates/builtins.js +72 -0
  175. package/dist/prompt-templates/store.d.ts +39 -0
  176. package/dist/prompt-templates/store.js +174 -0
  177. package/dist/prompt-templates/types.d.ts +10 -0
  178. package/dist/prompt-templates/types.js +1 -0
  179. package/dist/scheduler/connected-channels.d.ts +24 -0
  180. package/dist/scheduler/connected-channels.js +57 -0
  181. package/dist/scheduler/scheduler.d.ts +22 -0
  182. package/dist/scheduler/scheduler.js +132 -0
  183. package/dist/scheduler/store.d.ts +27 -0
  184. package/dist/scheduler/store.js +205 -0
  185. package/dist/scheduler/types.d.ts +29 -0
  186. package/dist/scheduler/types.js +1 -0
  187. package/dist/security/command-validator.d.ts +22 -0
  188. package/dist/security/command-validator.js +160 -0
  189. package/dist/security/docker-sandbox.d.ts +48 -0
  190. package/dist/security/docker-sandbox.js +218 -0
  191. package/dist/security/env-filter.d.ts +8 -0
  192. package/dist/security/env-filter.js +41 -0
  193. package/dist/skills/loader.d.ts +33 -0
  194. package/dist/skills/loader.js +132 -0
  195. package/dist/skills/prompt.d.ts +6 -0
  196. package/dist/skills/prompt.js +17 -0
  197. package/dist/skills/read-tool.d.ts +7 -0
  198. package/dist/skills/read-tool.js +24 -0
  199. package/dist/skills/scanner.d.ts +6 -0
  200. package/dist/skills/scanner.js +73 -0
  201. package/dist/skills/types.d.ts +15 -0
  202. package/dist/skills/types.js +1 -0
  203. package/dist/tasks/store.d.ts +47 -0
  204. package/dist/tasks/store.js +193 -0
  205. package/dist/tasks/types.d.ts +75 -0
  206. package/dist/tasks/types.js +32 -0
  207. package/dist/teams/store.d.ts +78 -0
  208. package/dist/teams/store.js +420 -0
  209. package/dist/teams/types.d.ts +23 -0
  210. package/dist/teams/types.js +1 -0
  211. package/dist/tools/bash.d.ts +16 -0
  212. package/dist/tools/bash.js +62 -0
  213. package/dist/tools/channel-history.d.ts +10 -0
  214. package/dist/tools/channel-history.js +43 -0
  215. package/dist/tools/delegate.d.ts +16 -0
  216. package/dist/tools/delegate.js +216 -0
  217. package/dist/tools/fs.d.ts +4 -0
  218. package/dist/tools/fs.js +335 -0
  219. package/dist/tools/integration-toggle.d.ts +14 -0
  220. package/dist/tools/integration-toggle.js +47 -0
  221. package/dist/tools/memory.d.ts +13 -0
  222. package/dist/tools/memory.js +65 -0
  223. package/dist/tools/registry.d.ts +6 -0
  224. package/dist/tools/registry.js +9 -0
  225. package/dist/tools/schedule.d.ts +8 -0
  226. package/dist/tools/schedule.js +219 -0
  227. package/dist/tools/speak.d.ts +10 -0
  228. package/dist/tools/speak.js +56 -0
  229. package/dist/tools/tasks.d.ts +29 -0
  230. package/dist/tools/tasks.js +92 -0
  231. package/dist/tools/teams.d.ts +7 -0
  232. package/dist/tools/teams.js +180 -0
  233. package/dist/tools/web-fetch.d.ts +3 -0
  234. package/dist/tools/web-fetch.js +22 -0
  235. package/dist/tts/edge.d.ts +10 -0
  236. package/dist/tts/edge.js +60 -0
  237. package/dist/tts/speak.d.ts +12 -0
  238. package/dist/tts/speak.js +81 -0
  239. package/dist/tts/transcribe.d.ts +5 -0
  240. package/dist/tts/transcribe.js +40 -0
  241. package/dist/utils.d.ts +5 -0
  242. package/dist/utils.js +22 -0
  243. package/package.json +90 -0
  244. package/verybot.js +2 -0
@@ -0,0 +1,216 @@
1
+ import { randomUUID } from "crypto";
2
+ import { tool } from "ai";
3
+ import { z } from "zod";
4
+ import { Session } from "../brain/session.js";
5
+ import { buildSystemPrompt } from "../brain/context.js";
6
+ import { runLoop } from "../brain/loop.js";
7
+ import { createMemorySearchTool } from "./memory.js";
8
+ import { emit } from "../events.js";
9
+ import { logger } from "../logger.js";
10
+ import { BrowserManager } from "../computer/browser/manager.js";
11
+ import { createBrowserTools, BROWSER_TOOL_NAMES } from "../computer/browser/tools.js";
12
+ import { DEFAULT_WORKER_TIMEOUT_S } from "../config/agent-config.js";
13
+ /**
14
+ * Creates delegation tools:
15
+ * - `delegate` — fires a worker in background, returns channel ID immediately
16
+ * - `read_channel` — reads all messages from a channel (available to orchestrator AND workers)
17
+ */
18
+ export function createDelegationTools(registry, channelStore, delegationStore, sessionStore, orchestratorId, sessionKey, memoryStore, embeddingProvider, memoryMaxResults, onWorkerComplete, browserConfig,
19
+ /** Human-readable session label for logs (team name instead of UUID). */
20
+ sessionLabel) {
21
+ const delegatable = registry.delegatableWorkers();
22
+ const orchestratorName = registry.getOrchestrator().name;
23
+ const idToName = registry.buildIdToNameMap();
24
+ const delegate = tool({
25
+ description: `Delegate a task to a specialized worker agent. Returns immediately with a channel ID — the worker runs in the background. ` +
26
+ `You will be automatically notified when workers finish. Use read_channel to see results. ` +
27
+ `Available workers: ${delegatable.join(", ")}. ` +
28
+ `You can fire multiple delegations in the same step — they all run concurrently. ` +
29
+ `Pass contextChannels to give the worker read access to other workers' channels.`,
30
+ inputSchema: z.object({
31
+ worker: z
32
+ .string()
33
+ .describe(`Which worker to delegate to. One of: ${delegatable.join(", ")}`),
34
+ task: z
35
+ .string()
36
+ .describe("Self-contained task description for the worker"),
37
+ contextChannels: z
38
+ .array(z.string())
39
+ .optional()
40
+ .describe("Channel IDs the worker can read for context from other workers"),
41
+ }),
42
+ execute: async ({ worker, task, contextChannels }) => {
43
+ const resolved = registry.resolveWorker(worker);
44
+ if (!resolved) {
45
+ return `Unknown worker: ${worker}. Available: ${delegatable.join(", ")}`;
46
+ }
47
+ const { agentConfig, model, tools: workerBaseTools } = resolved;
48
+ const agentId = agentConfig.id;
49
+ const agentName = agentConfig.name;
50
+ // Create channel for this delegation
51
+ const channelId = channelStore.createChannel();
52
+ channelStore.post(channelId, orchestratorId, "task", task);
53
+ // Record in delegation store (uses DB id for storage)
54
+ const delegationId = randomUUID().slice(0, 8);
55
+ delegationStore.insert({
56
+ id: delegationId,
57
+ agentId,
58
+ sessionKey,
59
+ task,
60
+ channelId,
61
+ status: "running",
62
+ createdAt: Date.now(),
63
+ });
64
+ // Worker tools: base (filtered) + memory_search (read-only) + read_channel
65
+ // Give worker its own browser instance (temp profile) so it doesn't conflict with others
66
+ const hasBrowserTools = BROWSER_TOOL_NAMES.some((n) => n in workerBaseTools);
67
+ let workerBrowser = null;
68
+ const workerTools = { ...workerBaseTools };
69
+ if (hasBrowserTools && browserConfig) {
70
+ // Strip shared browser tools, replace with per-worker instance
71
+ for (const n of BROWSER_TOOL_NAMES)
72
+ delete workerTools[n];
73
+ workerBrowser = new BrowserManager({ ...browserConfig, profileDir: "temp" });
74
+ Object.assign(workerTools, createBrowserTools(workerBrowser));
75
+ }
76
+ if (memoryStore) {
77
+ workerTools.memory_search = createMemorySearchTool(memoryStore, embeddingProvider, memoryMaxResults);
78
+ }
79
+ // Workers can read channels (their own + any context channels)
80
+ workerTools.read_channel = createReadChannelTool(channelStore, idToName);
81
+ // Build context hint about available channels
82
+ let channelHint = "";
83
+ if (contextChannels?.length) {
84
+ channelHint = `\n\nYou have access to read_channel. Context channels from other workers: ${contextChannels.join(", ")}. Call read_channel to see their results before starting your work.`;
85
+ }
86
+ const system = buildSystemPrompt({
87
+ identity: agentConfig.identity,
88
+ hasMemory: memoryStore !== null,
89
+ });
90
+ // Worker key extends parent key: {parentSessionKey}:{workerName}:{timestamp}
91
+ const workerSessionKey = `${sessionKey}:${agentName}:${Date.now()}`;
92
+ const session = new Session(workerSessionKey);
93
+ session.append({ role: "user", content: task + channelHint });
94
+ logger.info(`[delegate] ${orchestratorId} -> ${agentName}: ${task.slice(0, 200)} (channel: ${channelId})`);
95
+ emit("agent", {
96
+ sessionKey,
97
+ delegation: { agentId, agentName, channelId, status: "started" },
98
+ });
99
+ // Fire worker in background with timeout — does NOT await
100
+ const workerMaxSteps = agentConfig.maxSteps > 0 ? agentConfig.maxSteps : undefined;
101
+ const workerLabel = sessionLabel ? `worker:${sessionLabel}:${agentName}` : undefined;
102
+ // Extract team name from sessionLabel (format: "TeamName:channel:id...") for worker metadata
103
+ const teamName = sessionLabel?.split(":")[0];
104
+ const timeoutMs = (agentConfig.timeout ?? DEFAULT_WORKER_TIMEOUT_S) * 1_000;
105
+ withTimeout(runWorker(agentId, agentName, model, system, session, workerTools, workerSessionKey, workerMaxSteps, workerLabel), timeoutMs, `Worker ${agentName} timed out after ${timeoutMs / 1_000}s`).then(({ text: result, responseMessages }) => {
106
+ // Persist worker session with full tool call detail
107
+ for (const msg of responseMessages)
108
+ session.append(msg);
109
+ sessionStore.save(session).then(() => {
110
+ // Enrich worker session metadata for UI display
111
+ sessionStore.updateMetadata(workerSessionKey, {
112
+ channelType: "worker",
113
+ agentId,
114
+ agentName,
115
+ ...(teamName ? { teamName } : {}),
116
+ });
117
+ }).catch((err) => {
118
+ logger.warn(`[delegate] Failed to save worker session ${workerSessionKey}: ${err instanceof Error ? err.message : err}`);
119
+ });
120
+ // Close the worker's own browser instance
121
+ cleanupWorkerBrowser(workerBrowser, agentName);
122
+ channelStore.post(channelId, agentId, "result", result);
123
+ delegationStore.markCompleted(delegationId, result);
124
+ logger.info(`[delegate] ${agentName} completed (${result.length} chars, channel: ${channelId})`);
125
+ emit("agent", {
126
+ sessionKey,
127
+ delegation: { agentId, agentName, channelId, status: "completed", resultLength: result.length },
128
+ });
129
+ onWorkerComplete(sessionKey, channelId);
130
+ }, (err) => {
131
+ const error = err instanceof Error ? err.message : String(err);
132
+ // Persist worker session even on failure for debugging
133
+ sessionStore.save(session).then(() => {
134
+ sessionStore.updateMetadata(workerSessionKey, {
135
+ channelType: "worker",
136
+ agentId,
137
+ agentName,
138
+ ...(teamName ? { teamName } : {}),
139
+ });
140
+ }).catch((saveErr) => {
141
+ logger.warn(`[delegate] Failed to save worker session ${workerSessionKey}: ${saveErr instanceof Error ? saveErr.message : saveErr}`);
142
+ });
143
+ // Close the worker's own browser instance even on failure
144
+ cleanupWorkerBrowser(workerBrowser, agentName);
145
+ channelStore.post(channelId, agentId, "error", error);
146
+ delegationStore.markFailed(delegationId, error);
147
+ logger.error(`[delegate] ${agentName} failed: ${error} (channel: ${channelId})`);
148
+ emit("agent", {
149
+ sessionKey,
150
+ delegation: { agentId, agentName, channelId, status: "failed", error },
151
+ });
152
+ onWorkerComplete(sessionKey, channelId);
153
+ });
154
+ return `Delegated to ${agentName}. Channel: ${channelId}`;
155
+ },
156
+ });
157
+ const readChannel = createReadChannelTool(channelStore, idToName);
158
+ return { delegate, read_channel: readChannel };
159
+ }
160
+ /** Standalone read_channel tool — reused by both orchestrator and workers. */
161
+ function createReadChannelTool(channelStore, nameMap) {
162
+ return tool({
163
+ description: "Read all messages from a delegation channel. Returns the full conversation log " +
164
+ "including tasks, results, and errors posted by any agent.",
165
+ inputSchema: z.object({
166
+ channelId: z
167
+ .string()
168
+ .describe("The channel ID to read"),
169
+ }),
170
+ execute: async ({ channelId }) => {
171
+ const messages = channelStore.read(channelId);
172
+ if (messages.length === 0)
173
+ return `Channel ${channelId}: no messages yet.`;
174
+ return messages
175
+ .map((m) => `[${nameMap?.get(m.sender) ?? m.sender}] (${m.role}): ${m.content}`)
176
+ .join("\n\n");
177
+ },
178
+ });
179
+ }
180
+ const DEFAULT_WORKER_MAX_STEPS = 20;
181
+ /** Run a worker's loop — returned promise settles when done. */
182
+ async function runWorker(agentId, agentName, model, system, session, tools, workerSessionKey, maxSteps, sessionLabel) {
183
+ try {
184
+ return await runLoop({
185
+ model,
186
+ system,
187
+ messages: session.getMessages(),
188
+ tools,
189
+ sessionKey: workerSessionKey,
190
+ sessionLabel,
191
+ agentId,
192
+ maxSteps: maxSteps ?? DEFAULT_WORKER_MAX_STEPS,
193
+ silent: true,
194
+ });
195
+ }
196
+ catch (err) {
197
+ const error = err instanceof Error ? err.message : String(err);
198
+ logger.error(`[delegate] ${agentName} worker failed: ${error}`);
199
+ throw err;
200
+ }
201
+ }
202
+ /** Close the browser if the worker had it open. No-op if no browser or already closed. */
203
+ function cleanupWorkerBrowser(browserManager, agentName) {
204
+ if (!browserManager?.isLaunched())
205
+ return;
206
+ browserManager.close().catch((err) => {
207
+ logger.warn(`[delegate] ${agentName} browser cleanup failed: ${err instanceof Error ? err.message : err}`);
208
+ });
209
+ }
210
+ /** Race a promise against a timeout. */
211
+ function withTimeout(promise, ms, message) {
212
+ return new Promise((resolve, reject) => {
213
+ const timer = setTimeout(() => reject(new Error(message)), ms);
214
+ promise.then((val) => { clearTimeout(timer); resolve(val); }, (err) => { clearTimeout(timer); reject(err); });
215
+ });
216
+ }
@@ -0,0 +1,4 @@
1
+ import { type Tool } from "ai";
2
+ export declare function createFsTools(opts?: {
3
+ sandboxed?: boolean;
4
+ }): Record<string, Tool>;
@@ -0,0 +1,335 @@
1
+ import { tool } from "ai";
2
+ import { z } from "zod";
3
+ import { readFile, writeFile, mkdir, readdir } from "fs/promises";
4
+ import { execSync } from "child_process";
5
+ import { dirname, resolve } from "path";
6
+ import { sanitizeEnv } from "../security/env-filter.js";
7
+ import { logger } from "../logger.js";
8
+ /* ------------------------------------------------------------------ */
9
+ /* Constants */
10
+ /* ------------------------------------------------------------------ */
11
+ const MAX_OUTPUT = 10_000;
12
+ const DEFAULT_LINE_LIMIT = 2_000;
13
+ const DEFAULT_SEARCH_LIMIT = 200;
14
+ const EXEC_TIMEOUT = 15_000;
15
+ /* ------------------------------------------------------------------ */
16
+ /* Helpers */
17
+ /* ------------------------------------------------------------------ */
18
+ function sliceLines(text, offset, limit) {
19
+ const lines = text.split("\n");
20
+ const start = Math.max(0, offset);
21
+ const end = Math.min(lines.length, start + limit);
22
+ return lines
23
+ .slice(start, end)
24
+ .map((line, i) => `${start + i + 1}\t${line}`)
25
+ .join("\n");
26
+ }
27
+ function truncate(text, max = MAX_OUTPUT) {
28
+ if (text.length <= max)
29
+ return text;
30
+ return text.slice(0, max) + "\n…[truncated]";
31
+ }
32
+ /** Truncate output to N lines (for limiting search results in JS). */
33
+ function truncateLines(text, maxLines) {
34
+ const lines = text.split("\n");
35
+ if (lines.length <= maxLines)
36
+ return text;
37
+ return lines.slice(0, maxLines).join("\n") + "\n…[truncated]";
38
+ }
39
+ let rgAvailable = null;
40
+ function isRipgrepAvailable() {
41
+ if (rgAvailable === null) {
42
+ try {
43
+ execSync("rg --version", { stdio: "ignore", timeout: 3_000 });
44
+ rgAvailable = true;
45
+ }
46
+ catch {
47
+ rgAvailable = false;
48
+ }
49
+ }
50
+ return rgAvailable;
51
+ }
52
+ function shellEscape(arg) {
53
+ if (arg.includes("\0")) {
54
+ throw new Error("Null bytes not allowed in shell arguments");
55
+ }
56
+ return `'${arg.replace(/'/g, "'\\''")}'`;
57
+ }
58
+ /* ------------------------------------------------------------------ */
59
+ /* Tools */
60
+ /* ------------------------------------------------------------------ */
61
+ const readTool = tool({
62
+ description: "Read a file from the filesystem. Returns numbered lines. " +
63
+ "Use offset/limit for large files.",
64
+ inputSchema: z.object({
65
+ path: z.string().describe("Absolute or relative file path"),
66
+ offset: z
67
+ .number()
68
+ .int()
69
+ .min(0)
70
+ .optional()
71
+ .describe("0-based line offset to start reading from"),
72
+ limit: z
73
+ .number()
74
+ .int()
75
+ .min(1)
76
+ .optional()
77
+ .describe("Max number of lines to return"),
78
+ }),
79
+ execute: async ({ path, offset, limit }) => {
80
+ try {
81
+ const content = await readFile(resolve(path), "utf-8");
82
+ const lineOffset = offset ?? 0;
83
+ const lineLimit = limit ?? DEFAULT_LINE_LIMIT;
84
+ return truncate(sliceLines(content, lineOffset, lineLimit));
85
+ }
86
+ catch (err) {
87
+ const msg = err instanceof Error ? err.message : String(err);
88
+ logger.warn(`read failed: ${msg}`);
89
+ return `Error: ${msg}`;
90
+ }
91
+ },
92
+ });
93
+ const writeTool = tool({
94
+ description: "Write content to a file. Creates parent directories if needed. " +
95
+ "Overwrites existing files.",
96
+ inputSchema: z.object({
97
+ path: z.string().describe("Absolute or relative file path"),
98
+ content: z.string().describe("File content to write"),
99
+ }),
100
+ execute: async ({ path, content }) => {
101
+ try {
102
+ const resolved = resolve(path);
103
+ await mkdir(dirname(resolved), { recursive: true });
104
+ await writeFile(resolved, content, "utf-8");
105
+ return `Wrote ${content.split("\n").length} lines to ${resolved}`;
106
+ }
107
+ catch (err) {
108
+ const msg = err instanceof Error ? err.message : String(err);
109
+ logger.warn(`write failed: ${msg}`);
110
+ return `Error: ${msg}`;
111
+ }
112
+ },
113
+ });
114
+ const editTool = tool({
115
+ description: "Edit a file by replacing an exact text match. " +
116
+ "oldText must appear exactly once in the file.",
117
+ inputSchema: z.object({
118
+ path: z.string().describe("Absolute or relative file path"),
119
+ oldText: z.string().min(1).describe("Exact text to find (must be unique in file)"),
120
+ newText: z.string().describe("Replacement text"),
121
+ }),
122
+ execute: async ({ path, oldText, newText }) => {
123
+ try {
124
+ const resolved = resolve(path);
125
+ const content = await readFile(resolved, "utf-8");
126
+ const occurrences = content.split(oldText).length - 1;
127
+ if (occurrences === 0) {
128
+ return "Error: oldText not found in file";
129
+ }
130
+ if (occurrences > 1) {
131
+ return `Error: oldText found ${occurrences} times — must be unique. Provide more surrounding context.`;
132
+ }
133
+ // Use function replacement to avoid special pattern interpretation ($&, $`, etc.)
134
+ const updated = content.replace(oldText, () => newText);
135
+ await writeFile(resolved, updated, "utf-8");
136
+ return `Edited ${resolved} — replaced 1 occurrence`;
137
+ }
138
+ catch (err) {
139
+ const msg = err instanceof Error ? err.message : String(err);
140
+ logger.warn(`edit failed: ${msg}`);
141
+ return `Error: ${msg}`;
142
+ }
143
+ },
144
+ });
145
+ const grepTool = tool({
146
+ description: "Search file contents using ripgrep (rg) or grep. " +
147
+ "Returns matching lines with file paths and line numbers.",
148
+ inputSchema: z.object({
149
+ pattern: z.string().describe("Search pattern (regex unless literal=true)"),
150
+ path: z
151
+ .string()
152
+ .optional()
153
+ .describe("Directory or file to search (default: cwd)"),
154
+ glob: z
155
+ .string()
156
+ .optional()
157
+ .describe("File glob filter, e.g. '*.ts'"),
158
+ ignoreCase: z
159
+ .boolean()
160
+ .optional()
161
+ .describe("Case-insensitive search"),
162
+ literal: z
163
+ .boolean()
164
+ .optional()
165
+ .describe("Treat pattern as literal string, not regex"),
166
+ context: z
167
+ .number()
168
+ .int()
169
+ .min(0)
170
+ .optional()
171
+ .describe("Lines of context around each match"),
172
+ limit: z
173
+ .number()
174
+ .int()
175
+ .min(1)
176
+ .optional()
177
+ .describe("Max number of result lines to return"),
178
+ }),
179
+ execute: async ({ pattern, path, glob: fileGlob, ignoreCase, literal, context, limit }) => {
180
+ const searchPath = resolve(path ?? ".");
181
+ const maxLines = limit ?? DEFAULT_SEARCH_LIMIT;
182
+ try {
183
+ let cmd;
184
+ if (isRipgrepAvailable()) {
185
+ const args = ["rg", "--no-heading", "-n"];
186
+ if (ignoreCase)
187
+ args.push("-i");
188
+ if (literal)
189
+ args.push("-F");
190
+ if (context)
191
+ args.push("-C", String(context));
192
+ if (fileGlob)
193
+ args.push("--glob", fileGlob);
194
+ args.push("--", pattern, searchPath);
195
+ cmd = args.map(shellEscape).join(" ");
196
+ }
197
+ else {
198
+ const args = ["grep", "-rn"];
199
+ if (ignoreCase)
200
+ args.push("-i");
201
+ if (literal)
202
+ args.push("-F");
203
+ if (context)
204
+ args.push("-C", String(context));
205
+ if (fileGlob)
206
+ args.push("--include", fileGlob);
207
+ args.push("--", pattern, searchPath);
208
+ cmd = args.map(shellEscape).join(" ");
209
+ }
210
+ const output = execSync(cmd, {
211
+ encoding: "utf-8",
212
+ timeout: EXEC_TIMEOUT,
213
+ maxBuffer: 2 * 1024 * 1024,
214
+ env: sanitizeEnv(),
215
+ });
216
+ return truncate(truncateLines(output || "No matches found", maxLines));
217
+ }
218
+ catch (err) {
219
+ // grep/rg exit code 1 = no matches (not an error)
220
+ const code = err.status;
221
+ if (code === 1)
222
+ return "No matches found";
223
+ const stderr = err.stderr ?? "";
224
+ const msg = err instanceof Error ? err.message : String(err);
225
+ logger.warn(`grep failed: ${msg}`);
226
+ return `Error: ${stderr || msg}`.slice(0, MAX_OUTPUT);
227
+ }
228
+ },
229
+ });
230
+ const findTool = tool({
231
+ description: "Find files by name pattern. Uses the system `find` command. " +
232
+ "Returns matching file paths.",
233
+ inputSchema: z.object({
234
+ pattern: z
235
+ .string()
236
+ .describe("Filename glob pattern, e.g. '*.ts' or 'index.*'"),
237
+ path: z
238
+ .string()
239
+ .optional()
240
+ .describe("Directory to search in (default: cwd)"),
241
+ limit: z
242
+ .number()
243
+ .int()
244
+ .min(1)
245
+ .optional()
246
+ .describe("Max number of results"),
247
+ }),
248
+ execute: async ({ pattern, path, limit }) => {
249
+ const searchPath = resolve(path ?? ".");
250
+ const maxResults = limit ?? DEFAULT_SEARCH_LIMIT;
251
+ try {
252
+ const cmd = [
253
+ "find",
254
+ shellEscape(searchPath),
255
+ "-name",
256
+ shellEscape(pattern),
257
+ "-not",
258
+ "-path",
259
+ shellEscape("*/node_modules/*"),
260
+ "-not",
261
+ "-path",
262
+ shellEscape("*/.git/*"),
263
+ ].join(" ");
264
+ const output = execSync(cmd, {
265
+ encoding: "utf-8",
266
+ timeout: EXEC_TIMEOUT,
267
+ maxBuffer: 2 * 1024 * 1024,
268
+ env: sanitizeEnv(),
269
+ });
270
+ return truncate(truncateLines(output || "No files found", maxResults));
271
+ }
272
+ catch (err) {
273
+ const msg = err instanceof Error ? err.message : String(err);
274
+ logger.warn(`find failed: ${msg}`);
275
+ return `Error: ${msg}`.slice(0, MAX_OUTPUT);
276
+ }
277
+ },
278
+ });
279
+ const lsTool = tool({
280
+ description: "List directory contents with type indicators. " +
281
+ "Directories end with /, symlinks with @.",
282
+ inputSchema: z.object({
283
+ path: z
284
+ .string()
285
+ .optional()
286
+ .describe("Directory to list (default: cwd)"),
287
+ limit: z
288
+ .number()
289
+ .int()
290
+ .min(1)
291
+ .optional()
292
+ .describe("Max number of entries to return"),
293
+ }),
294
+ execute: async ({ path, limit }) => {
295
+ const dirPath = resolve(path ?? ".");
296
+ const maxEntries = limit ?? DEFAULT_SEARCH_LIMIT;
297
+ try {
298
+ const entries = await readdir(dirPath, { withFileTypes: true });
299
+ const lines = entries.slice(0, maxEntries).map((entry) => {
300
+ if (entry.isDirectory())
301
+ return `${entry.name}/`;
302
+ if (entry.isSymbolicLink())
303
+ return `${entry.name}@`;
304
+ return entry.name;
305
+ });
306
+ if (entries.length > maxEntries) {
307
+ lines.push(`…and ${entries.length - maxEntries} more`);
308
+ }
309
+ return lines.join("\n") || "(empty directory)";
310
+ }
311
+ catch (err) {
312
+ const msg = err instanceof Error ? err.message : String(err);
313
+ logger.warn(`ls failed: ${msg}`);
314
+ return `Error: ${msg}`;
315
+ }
316
+ },
317
+ });
318
+ /* ------------------------------------------------------------------ */
319
+ /* Export */
320
+ /* ------------------------------------------------------------------ */
321
+ const SANDBOX_ERROR = "Error: This tool is disabled in sandbox mode. Use bash instead (cat, tee, grep, find, ls).";
322
+ function sandboxGuard(t) {
323
+ return { ...t, execute: async () => SANDBOX_ERROR };
324
+ }
325
+ export function createFsTools(opts) {
326
+ const wrap = opts?.sandboxed ? sandboxGuard : (t) => t;
327
+ return {
328
+ read: wrap(readTool),
329
+ write: wrap(writeTool),
330
+ edit: wrap(editTool),
331
+ grep: wrap(grepTool),
332
+ find: wrap(findTool),
333
+ ls: wrap(lsTool),
334
+ };
335
+ }
@@ -0,0 +1,14 @@
1
+ import type { IntegrationRegistry } from "../integrations/registry.js";
2
+ /**
3
+ * Create enable_integration and disable_integration tools.
4
+ * These mutate the session's active integrations set so that
5
+ * subsequent runs include/exclude the integration tools.
6
+ */
7
+ export declare function createIntegrationToggleTools(registry: IntegrationRegistry, activeIntegrations: Set<string>): {
8
+ enable_integration: import("ai").Tool<{
9
+ name: string;
10
+ }, string>;
11
+ disable_integration: import("ai").Tool<{
12
+ name: string;
13
+ }, string>;
14
+ };
@@ -0,0 +1,47 @@
1
+ import { tool } from "ai";
2
+ import { z } from "zod";
3
+ /**
4
+ * Create enable_integration and disable_integration tools.
5
+ * These mutate the session's active integrations set so that
6
+ * subsequent runs include/exclude the integration tools.
7
+ */
8
+ export function createIntegrationToggleTools(registry, activeIntegrations) {
9
+ const available = registry.names;
10
+ const availableList = available.join(", ");
11
+ const enableIntegration = tool({
12
+ description: `Enable an integration for this session. Available: ${availableList}. ` +
13
+ `Once enabled, the integration's tools will be available in subsequent messages.`,
14
+ inputSchema: z.object({
15
+ name: z.string().describe("Integration name to enable"),
16
+ }),
17
+ execute: async ({ name }) => {
18
+ const normalized = name.toLowerCase();
19
+ if (!registry.has(normalized)) {
20
+ return `Unknown integration "${name}". Available: ${availableList}`;
21
+ }
22
+ if (activeIntegrations.has(normalized)) {
23
+ return `Integration "${normalized}" is already enabled.`;
24
+ }
25
+ activeIntegrations.add(normalized);
26
+ const integration = registry.get(normalized);
27
+ const toolNames = Object.keys(integration.tools.tools).join(", ");
28
+ return `Integration "${normalized}" enabled. Tools now available: ${toolNames}`;
29
+ },
30
+ });
31
+ const disableIntegration = tool({
32
+ description: `Disable an integration for this session. ` +
33
+ `Once disabled, the integration's tools will no longer be available.`,
34
+ inputSchema: z.object({
35
+ name: z.string().describe("Integration name to disable"),
36
+ }),
37
+ execute: async ({ name }) => {
38
+ const normalized = name.toLowerCase();
39
+ if (!activeIntegrations.has(normalized)) {
40
+ return `Integration "${normalized}" is not currently enabled.`;
41
+ }
42
+ activeIntegrations.delete(normalized);
43
+ return `Integration "${normalized}" disabled.`;
44
+ },
45
+ });
46
+ return { enable_integration: enableIntegration, disable_integration: disableIntegration };
47
+ }
@@ -0,0 +1,13 @@
1
+ import { type Tool } from "ai";
2
+ import type { MemoryStore } from "../memory/store.js";
3
+ import type { EmbeddingProvider } from "../memory/embedding.js";
4
+ /**
5
+ * Create a session-scoped memory_search tool.
6
+ * Called per-run so results are restricted to the current session's memories.
7
+ */
8
+ export declare function createMemorySearchTool(store: MemoryStore, embeddingProvider: EmbeddingProvider | null, maxResults: number, teamId?: string): Tool;
9
+ /**
10
+ * Create a session-scoped memory_save tool.
11
+ * Allows the agent to explicitly persist facts when the user asks to remember something.
12
+ */
13
+ export declare function createMemorySaveTool(store: MemoryStore, embeddingProvider: EmbeddingProvider | null, sessionKey: string, teamId?: string): Tool;