wave-agent-sdk 1.0.4 → 1.0.6

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.
Files changed (194) hide show
  1. package/dist/agent.d.ts +23 -1
  2. package/dist/agent.js +34 -4
  3. package/dist/managers/aiManager.d.ts +36 -0
  4. package/dist/managers/aiManager.js +240 -8
  5. package/dist/managers/messageManager.d.ts +1 -3
  6. package/dist/managers/messageManager.js +19 -9
  7. package/dist/managers/slashCommandManager.js +28 -0
  8. package/dist/managers/subagentManager.d.ts +0 -2
  9. package/dist/services/session.js +2 -9
  10. package/dist/telemetry/instrumentation.d.ts +1 -1
  11. package/dist/telemetry/instrumentation.js +57 -28
  12. package/dist/tools/bashTool.js +16 -6
  13. package/dist/tools/grepTool.js +11 -3
  14. package/dist/types/index.d.ts +3 -0
  15. package/dist/types/messaging.d.ts +2 -0
  16. package/dist/utils/containerSetup.js +0 -3
  17. package/dist/utils/messageOperations.d.ts +10 -1
  18. package/dist/utils/messageOperations.js +19 -1
  19. package/dist/utils/notificationXml.js +5 -0
  20. package/dist/utils/ripgrep.d.ts +4 -4
  21. package/dist/utils/ripgrep.js +4 -27
  22. package/package.json +20 -8
  23. package/bin/rg +0 -79
  24. package/dist/managers/forkedAgentManager.d.ts +0 -50
  25. package/dist/managers/forkedAgentManager.js +0 -130
  26. package/scripts/install_ripgrep.js +0 -111
  27. package/scripts/postinstall.js +0 -38
  28. package/src/agent.ts +0 -1271
  29. package/src/constants/subagents.ts +0 -4
  30. package/src/constants/toolLimits.ts +0 -15
  31. package/src/constants/tools.ts +0 -24
  32. package/src/core/plugin.ts +0 -237
  33. package/src/core/session.ts +0 -9
  34. package/src/index.ts +0 -38
  35. package/src/managers/MemoryRuleManager.ts +0 -198
  36. package/src/managers/aiManager.ts +0 -2429
  37. package/src/managers/backgroundTaskManager.ts +0 -480
  38. package/src/managers/bangManager.ts +0 -119
  39. package/src/managers/cronManager.ts +0 -401
  40. package/src/managers/foregroundTaskManager.ts +0 -30
  41. package/src/managers/forkedAgentManager.ts +0 -216
  42. package/src/managers/hookManager.ts +0 -1089
  43. package/src/managers/liveConfigManager.ts +0 -445
  44. package/src/managers/lspManager.ts +0 -470
  45. package/src/managers/mcpManager.ts +0 -1010
  46. package/src/managers/messageManager.ts +0 -1180
  47. package/src/managers/messageQueue.ts +0 -157
  48. package/src/managers/permissionManager.ts +0 -1140
  49. package/src/managers/planManager.ts +0 -112
  50. package/src/managers/pluginManager.ts +0 -330
  51. package/src/managers/pluginScopeManager.ts +0 -124
  52. package/src/managers/reversionManager.ts +0 -178
  53. package/src/managers/skillManager.ts +0 -578
  54. package/src/managers/slashCommandManager.ts +0 -564
  55. package/src/managers/subagentManager.ts +0 -915
  56. package/src/managers/toolManager.ts +0 -489
  57. package/src/managers/workflowManager.ts +0 -493
  58. package/src/prompts/autoMemory.ts +0 -33
  59. package/src/prompts/autoMemoryExtraction.ts +0 -146
  60. package/src/prompts/index.ts +0 -506
  61. package/src/prompts/planModeReminders.ts +0 -126
  62. package/src/services/GitService.ts +0 -131
  63. package/src/services/MarketplaceService.ts +0 -1052
  64. package/src/services/MemoryRuleService.ts +0 -71
  65. package/src/services/aiService.ts +0 -919
  66. package/src/services/authService.ts +0 -565
  67. package/src/services/autoMemoryService.ts +0 -280
  68. package/src/services/configurationService.ts +0 -1520
  69. package/src/services/fileWatcher.ts +0 -319
  70. package/src/services/hook.ts +0 -348
  71. package/src/services/initializationService.ts +0 -357
  72. package/src/services/interactionService.ts +0 -229
  73. package/src/services/jsonlHandler.ts +0 -295
  74. package/src/services/memory.ts +0 -234
  75. package/src/services/pluginLoader.ts +0 -252
  76. package/src/services/remoteSettingsService.ts +0 -366
  77. package/src/services/reversionService.ts +0 -122
  78. package/src/services/session.ts +0 -922
  79. package/src/services/taskManager.ts +0 -321
  80. package/src/telemetry/events.ts +0 -65
  81. package/src/telemetry/instrumentation.ts +0 -499
  82. package/src/telemetry/sessionTracing.ts +0 -348
  83. package/src/tools/agentTool.ts +0 -322
  84. package/src/tools/askUserQuestion.ts +0 -160
  85. package/src/tools/bashTool.ts +0 -621
  86. package/src/tools/buildTool.ts +0 -61
  87. package/src/tools/cronCreateTool.ts +0 -161
  88. package/src/tools/cronDeleteTool.ts +0 -51
  89. package/src/tools/cronListTool.ts +0 -47
  90. package/src/tools/editTool.ts +0 -313
  91. package/src/tools/enterPlanMode.ts +0 -124
  92. package/src/tools/enterWorktreeTool.ts +0 -190
  93. package/src/tools/exitPlanMode.ts +0 -139
  94. package/src/tools/exitWorktreeTool.ts +0 -236
  95. package/src/tools/globTool.ts +0 -167
  96. package/src/tools/grepTool.ts +0 -399
  97. package/src/tools/lspTool.ts +0 -883
  98. package/src/tools/readTool.ts +0 -426
  99. package/src/tools/skillTool.ts +0 -248
  100. package/src/tools/taskManagementTools.ts +0 -574
  101. package/src/tools/taskStopTool.ts +0 -78
  102. package/src/tools/types.ts +0 -137
  103. package/src/tools/webFetchTool.ts +0 -373
  104. package/src/tools/workflowTool.ts +0 -205
  105. package/src/tools/writeTool.ts +0 -267
  106. package/src/types/agent.ts +0 -128
  107. package/src/types/auth.ts +0 -19
  108. package/src/types/commands.ts +0 -30
  109. package/src/types/config.ts +0 -43
  110. package/src/types/configuration.ts +0 -168
  111. package/src/types/core.ts +0 -105
  112. package/src/types/cron.ts +0 -13
  113. package/src/types/environment.ts +0 -106
  114. package/src/types/fileSearch.ts +0 -4
  115. package/src/types/history.ts +0 -7
  116. package/src/types/hooks.ts +0 -256
  117. package/src/types/index.ts +0 -41
  118. package/src/types/lsp.ts +0 -98
  119. package/src/types/marketplace.ts +0 -71
  120. package/src/types/mcp.ts +0 -43
  121. package/src/types/memoryRule.ts +0 -31
  122. package/src/types/messaging.ts +0 -114
  123. package/src/types/permissions.ts +0 -80
  124. package/src/types/plugins.ts +0 -39
  125. package/src/types/processes.ts +0 -99
  126. package/src/types/reversion.ts +0 -29
  127. package/src/types/session.ts +0 -12
  128. package/src/types/skills.ts +0 -108
  129. package/src/types/tasks.ts +0 -13
  130. package/src/types/telemetry.ts +0 -98
  131. package/src/types/tools.ts +0 -44
  132. package/src/types/workflow.ts +0 -6
  133. package/src/utils/abortUtils.ts +0 -118
  134. package/src/utils/atomicWrite.ts +0 -61
  135. package/src/utils/bashParser.ts +0 -899
  136. package/src/utils/cacheControlUtils.ts +0 -426
  137. package/src/utils/commandArgumentParser.ts +0 -161
  138. package/src/utils/commandPathResolver.ts +0 -88
  139. package/src/utils/configPaths.ts +0 -220
  140. package/src/utils/configValidator.ts +0 -100
  141. package/src/utils/constants.ts +0 -39
  142. package/src/utils/container.ts +0 -92
  143. package/src/utils/containerSetup.ts +0 -371
  144. package/src/utils/convertMessagesForAPI.ts +0 -326
  145. package/src/utils/cronTasks.ts +0 -128
  146. package/src/utils/cronTasksLock.ts +0 -182
  147. package/src/utils/cronToHuman.ts +0 -99
  148. package/src/utils/customCommands.ts +0 -95
  149. package/src/utils/editUtils.ts +0 -20
  150. package/src/utils/fileFormat.ts +0 -40
  151. package/src/utils/fileSearch.ts +0 -151
  152. package/src/utils/fileUtils.ts +0 -223
  153. package/src/utils/gitUtils.ts +0 -315
  154. package/src/utils/globalLogger.ts +0 -128
  155. package/src/utils/groupMessagesByApiRound.ts +0 -121
  156. package/src/utils/hookMatcher.ts +0 -176
  157. package/src/utils/markdownParser.ts +0 -317
  158. package/src/utils/mcpUtils.ts +0 -138
  159. package/src/utils/messageOperations.ts +0 -641
  160. package/src/utils/modelCapabilities.ts +0 -30
  161. package/src/utils/nameGenerator.ts +0 -99
  162. package/src/utils/notificationXml.ts +0 -57
  163. package/src/utils/openaiClient.ts +0 -251
  164. package/src/utils/parseCronExpression.ts +0 -78
  165. package/src/utils/path.ts +0 -72
  166. package/src/utils/pathEncoder.ts +0 -400
  167. package/src/utils/pathSafety.ts +0 -40
  168. package/src/utils/promptHistory.ts +0 -168
  169. package/src/utils/ripgrep.ts +0 -35
  170. package/src/utils/shellResolver.ts +0 -190
  171. package/src/utils/skillParser.ts +0 -246
  172. package/src/utils/stringUtils.ts +0 -156
  173. package/src/utils/subagentParser.ts +0 -279
  174. package/src/utils/taskReminder.ts +0 -96
  175. package/src/utils/tokenCalculation.ts +0 -43
  176. package/src/utils/tokenEstimate.ts +0 -34
  177. package/src/utils/toolResultStorage.ts +0 -117
  178. package/src/utils/worktreeSession.ts +0 -26
  179. package/src/utils/worktreeUtils.ts +0 -786
  180. package/src/workflow/budgetTracker.ts +0 -34
  181. package/src/workflow/concurrencyLimiter.ts +0 -47
  182. package/src/workflow/journal.ts +0 -95
  183. package/src/workflow/progressReporter.ts +0 -141
  184. package/src/workflow/runState.ts +0 -65
  185. package/src/workflow/scriptRuntime.ts +0 -274
  186. package/src/workflow/structuredOutput.ts +0 -123
  187. package/src/workflow/types.ts +0 -101
  188. package/src/workflow/workflowApis.ts +0 -410
  189. package/vendor/ripgrep/linux-aarch64/rg +0 -0
  190. package/vendor/ripgrep/linux-x86_64/rg +0 -0
  191. package/vendor/ripgrep/macos-aarch64/rg +0 -0
  192. package/vendor/ripgrep/macos-x86_64/rg +0 -0
  193. package/vendor/ripgrep/windows-aarch64/rg.exe +0 -0
  194. package/vendor/ripgrep/windows-x86_64/rg.exe +0 -0
@@ -1,348 +0,0 @@
1
- /**
2
- * Session Tracing -- OpenTelemetry Span Management
3
- *
4
- * Provides span creation/ending APIs for interactions, LLM requests, and tool
5
- * executions. Uses two independent AsyncLocalStorage contexts:
6
- * - interactionContext: holds the interaction span for the entire turn
7
- * - toolContext: holds the current tool span (cleared on end)
8
- *
9
- * LLM request spans do not enter any ALS; they are passed explicitly to
10
- * endLLMRequestSpan, aligning with Claude Code's approach.
11
- */
12
-
13
- import { AsyncLocalStorage } from "node:async_hooks";
14
- import { createHash } from "node:crypto";
15
- import type { Span } from "@opentelemetry/api";
16
- import {
17
- getOTELApi,
18
- isInitialized,
19
- getCurrentConfig,
20
- } from "./instrumentation.js";
21
- import { logOTelEvent } from "./events.js";
22
- import type {
23
- LLMRequestMetadata,
24
- LLMRequestInput,
25
- ToolMetadata,
26
- } from "../types/telemetry.js";
27
-
28
- // -- Constants --
29
-
30
- /** Max content length for span attributes (60KB, aligning with Claude Code) */
31
- const MAX_CONTENT_LENGTH = 60000;
32
-
33
- // -- AsyncLocalStorage for context propagation --
34
-
35
- const interactionContext = new AsyncLocalStorage<Span | undefined>();
36
- const toolContext = new AsyncLocalStorage<Span | undefined>();
37
-
38
- // -- Incremental tracking state (module-level, per session) --
39
-
40
- /** Number of messages already reported in new_context; only deltas are sent */
41
- let lastReportedMessageCount = 0;
42
- /** Set of system prompt hashes already emitted via logOTelEvent */
43
- const seenSystemPromptHashes = new Set<string>();
44
- /** Set of tool schema hashes already emitted via logOTelEvent */
45
- const seenToolSchemaHashes = new Set<string>();
46
-
47
- // -- Helpers --
48
-
49
- /** Computes SHA-256 hash of content, returns first 12 hex characters */
50
- function hashContent(content: string): string {
51
- return createHash("sha256").update(content).digest("hex").substring(0, 12);
52
- }
53
-
54
- /** Truncates content to MAX_CONTENT_LENGTH, returns [truncated, originalLength] */
55
- function truncateContent(content: string): [string, number] {
56
- const originalLength = content.length;
57
- if (originalLength > MAX_CONTENT_LENGTH) {
58
- return [content.substring(0, MAX_CONTENT_LENGTH), originalLength];
59
- }
60
- return [content, originalLength];
61
- }
62
-
63
- // -- Tracer accessor --
64
-
65
- function getTracer() {
66
- if (!isInitialized()) return undefined;
67
- const otelApi = getOTELApi();
68
- if (!otelApi) return undefined;
69
- return otelApi.trace.getTracer("wave");
70
- }
71
-
72
- // -- Public API --
73
-
74
- /**
75
- * Creates an interaction span for a user turn.
76
- * The span is stored in interactionContext for the duration of the turn.
77
- */
78
- export function startInteractionSpan(
79
- userPrompt: string,
80
- sequence: number,
81
- ): Span | undefined {
82
- const tracer = getTracer();
83
- if (!tracer) return undefined;
84
-
85
- const config = getCurrentConfig();
86
- const attributes: Record<string, string | number> = {
87
- "span.type": "interaction",
88
- user_prompt_length: userPrompt.length,
89
- "interaction.sequence": sequence,
90
- };
91
- if (config?.logUserPrompts) {
92
- attributes.user_prompt = userPrompt;
93
- }
94
-
95
- const span = tracer.startSpan("interaction", { attributes });
96
- interactionContext.enterWith(span);
97
- return span;
98
- }
99
-
100
- /**
101
- * Ends the current interaction span and clears the context.
102
- */
103
- export function endInteractionSpan(): void {
104
- const span = interactionContext.getStore();
105
- if (!span) return;
106
- span.end();
107
- interactionContext.enterWith(undefined);
108
- }
109
-
110
- /**
111
- * Resets incremental tracing state. Should be called after compaction to
112
- * prevent incorrect delta calculations.
113
- */
114
- export function resetTracingState(): void {
115
- lastReportedMessageCount = 0;
116
- seenSystemPromptHashes.clear();
117
- seenToolSchemaHashes.clear();
118
- }
119
-
120
- /**
121
- * Creates an LLM request span as a child of the interaction span.
122
- * Does NOT enter any ALS — the span must be passed explicitly to endLLMRequestSpan.
123
- *
124
- * When logToolContent is enabled, captures:
125
- * - system_prompt_hash / system_prompt_preview / system_prompt_length
126
- * - new_context (incremental messages delta)
127
- * - tools (JSON array of {name, hash}) / tools_count
128
- */
129
- export function startLLMRequestSpan(
130
- model: string,
131
- options?: LLMRequestInput,
132
- ): Span | undefined {
133
- const tracer = getTracer();
134
- if (!tracer) return undefined;
135
-
136
- const attributes: Record<string, string | number | boolean> = {
137
- "span.type": "llm_request",
138
- model,
139
- };
140
- if (options?.context) {
141
- attributes["llm_request.context"] = options.context;
142
- }
143
-
144
- const config = getCurrentConfig();
145
-
146
- // Content capture gated by logToolContent
147
- if (config?.logToolContent) {
148
- // System prompt: hash + preview + length
149
- if (options?.systemPrompt) {
150
- const hash = hashContent(options.systemPrompt);
151
- attributes.system_prompt_hash = hash;
152
- attributes.system_prompt_length = options.systemPrompt.length;
153
- const preview =
154
- options.systemPrompt.length > 500
155
- ? options.systemPrompt.substring(0, 500)
156
- : options.systemPrompt;
157
- attributes.system_prompt_preview = preview;
158
-
159
- // Emit full system prompt via event only once per hash
160
- if (!seenSystemPromptHashes.has(hash)) {
161
- seenSystemPromptHashes.add(hash);
162
- logOTelEvent("system_prompt", {
163
- system_prompt_hash: hash,
164
- system_prompt: options.systemPrompt,
165
- }).catch(() => {});
166
- }
167
- }
168
-
169
- // Incremental input messages: only send new messages since last report
170
- if (options?.inputMessages && options.inputMessages.length > 0) {
171
- const totalCount = options.inputMessages.length;
172
- const deltaCount = totalCount - lastReportedMessageCount;
173
- if (deltaCount > 0) {
174
- const newMessages = options.inputMessages.slice(
175
- lastReportedMessageCount,
176
- );
177
- const newContextStr = JSON.stringify(newMessages);
178
- const [truncated, originalLength] = truncateContent(newContextStr);
179
- attributes.new_context = truncated;
180
- attributes.new_context_message_count = deltaCount;
181
- if (originalLength > MAX_CONTENT_LENGTH) {
182
- attributes.new_context_truncated = true;
183
- attributes.new_context_original_length = originalLength;
184
- }
185
- }
186
- lastReportedMessageCount = totalCount;
187
- }
188
-
189
- // Tool schemas: hash per tool + count
190
- if (options?.toolsSchema) {
191
- try {
192
- const tools = JSON.parse(options.toolsSchema);
193
- if (Array.isArray(tools)) {
194
- const toolHashes = tools.map(
195
- (tool: { function?: { name?: string } }) => {
196
- const name = tool.function?.name || "unknown";
197
- const toolStr = JSON.stringify(tool);
198
- const hash = hashContent(toolStr);
199
- return { name, hash };
200
- },
201
- );
202
- attributes.tools = JSON.stringify(toolHashes);
203
- attributes.tools_count = toolHashes.length;
204
-
205
- // Emit full tool schema via event only once per hash
206
- for (const { name, hash } of toolHashes) {
207
- if (!seenToolSchemaHashes.has(hash)) {
208
- seenToolSchemaHashes.add(hash);
209
- const fullTool = tools.find(
210
- (t: { function?: { name?: string } }) =>
211
- t.function?.name === name,
212
- );
213
- if (fullTool) {
214
- logOTelEvent("tool_schema", {
215
- tool_name: name,
216
- tool_hash: hash,
217
- tool_schema: JSON.stringify(fullTool),
218
- }).catch(() => {});
219
- }
220
- }
221
- }
222
- }
223
- } catch {
224
- // toolsSchema is not valid JSON — skip
225
- }
226
- }
227
- }
228
-
229
- const parent = interactionContext.getStore();
230
- let span: Span;
231
- if (parent) {
232
- const otelApi = getOTELApi()!;
233
- const ctx = otelApi.trace.setSpan(otelApi.context.active(), parent);
234
- span = tracer.startSpan("llm.request", { attributes }, ctx);
235
- } else {
236
- span = tracer.startSpan("llm.request", { attributes });
237
- }
238
- return span;
239
- }
240
-
241
- /**
242
- * Ends an LLM request span with response metadata.
243
- * The span is passed explicitly — no ALS is read or modified.
244
- *
245
- * When logToolContent is enabled, captures response.model_output.
246
- */
247
- export function endLLMRequestSpan(
248
- span: Span | undefined,
249
- metadata: LLMRequestMetadata,
250
- ): void {
251
- if (!span) return;
252
-
253
- if (metadata.inputTokens != null)
254
- span.setAttribute("input_tokens", metadata.inputTokens);
255
- if (metadata.outputTokens != null)
256
- span.setAttribute("output_tokens", metadata.outputTokens);
257
- if (metadata.cacheReadTokens != null)
258
- span.setAttribute("cache_read_tokens", metadata.cacheReadTokens);
259
- if (metadata.cacheCreationTokens != null)
260
- span.setAttribute("cache_creation_tokens", metadata.cacheCreationTokens);
261
- if (metadata.ttftMs != null) span.setAttribute("ttft_ms", metadata.ttftMs);
262
- if (metadata.ttltMs != null) span.setAttribute("ttlt_ms", metadata.ttltMs);
263
- span.setAttribute("success", metadata.success);
264
- if (metadata.error) span.setAttribute("error", metadata.error);
265
- if (metadata.hasToolCall != null)
266
- span.setAttribute("has_tool_call", metadata.hasToolCall);
267
-
268
- // Content capture gated by logToolContent
269
- const config = getCurrentConfig();
270
- if (config?.logToolContent && metadata.modelOutput) {
271
- const [truncated, originalLength] = truncateContent(metadata.modelOutput);
272
- span.setAttribute("response.model_output", truncated);
273
- if (originalLength > MAX_CONTENT_LENGTH) {
274
- span.setAttribute("response.model_output_truncated", true);
275
- span.setAttribute(
276
- "response.model_output_original_length",
277
- originalLength,
278
- );
279
- }
280
- }
281
-
282
- span.end();
283
- }
284
-
285
- /**
286
- * Creates a tool execution span as a child of the interaction span.
287
- * Enters toolContext with the new span.
288
- */
289
- export function startToolSpan(
290
- toolName: string,
291
- input?: unknown,
292
- ): Span | undefined {
293
- const tracer = getTracer();
294
- if (!tracer) return undefined;
295
-
296
- const config = getCurrentConfig();
297
- const attributes: Record<string, string | number | boolean> = {
298
- "span.type": "tool",
299
- tool_name: toolName,
300
- };
301
- if (config?.logToolContent && input !== undefined) {
302
- const inputStr = typeof input === "string" ? input : JSON.stringify(input);
303
- const [truncated, originalLength] = truncateContent(inputStr);
304
- attributes.tool_input = truncated;
305
- if (originalLength > MAX_CONTENT_LENGTH) {
306
- attributes.tool_input_truncated = true;
307
- attributes.tool_input_original_length = originalLength;
308
- }
309
- }
310
-
311
- const parent = interactionContext.getStore();
312
- let span: Span;
313
- if (parent) {
314
- const otelApi = getOTELApi()!;
315
- const ctx = otelApi.trace.setSpan(otelApi.context.active(), parent);
316
- span = tracer.startSpan(`tool.${toolName}`, { attributes }, ctx);
317
- } else {
318
- span = tracer.startSpan(`tool.${toolName}`, { attributes });
319
- }
320
- toolContext.enterWith(span);
321
- return span;
322
- }
323
-
324
- /**
325
- * Ends the current tool span with execution metadata.
326
- * Reads the span from toolContext, then clears it.
327
- */
328
- export function endToolSpan(metadata: ToolMetadata): void {
329
- const span = toolContext.getStore();
330
- if (!span) return;
331
-
332
- span.setAttribute("success", metadata.success);
333
- if (metadata.error) span.setAttribute("error", metadata.error);
334
- span.setAttribute("duration_ms", metadata.durationMs);
335
-
336
- const config = getCurrentConfig();
337
- if (config?.logToolContent && metadata.output) {
338
- const [truncated, originalLength] = truncateContent(metadata.output);
339
- span.setAttribute("tool_output", truncated);
340
- if (originalLength > MAX_CONTENT_LENGTH) {
341
- span.setAttribute("tool_output_truncated", true);
342
- span.setAttribute("tool_output_original_length", originalLength);
343
- }
344
- }
345
-
346
- span.end();
347
- toolContext.enterWith(undefined);
348
- }
@@ -1,322 +0,0 @@
1
- import type { ToolPlugin, ToolResult, ToolContext } from "./types.js";
2
- import { EXPLORE_SUBAGENT_TYPE } from "../constants/subagents.js";
3
- import { AGENT_TOOL_NAME } from "../constants/tools.js";
4
- import type { SubagentConfiguration } from "../utils/subagentParser.js";
5
- import {
6
- countToolBlocks,
7
- formatToolTokenSummary,
8
- } from "../utils/messageOperations.js";
9
-
10
- /**
11
- * Agent tool plugin for launching specialized agents to handle complex tasks
12
- */
13
- export const agentTool: ToolPlugin = {
14
- name: AGENT_TOOL_NAME,
15
- config: {
16
- type: "function" as const,
17
- function: {
18
- name: AGENT_TOOL_NAME,
19
- description:
20
- "Launch a new agent to handle complex, multi-step tasks autonomously.",
21
- parameters: {
22
- type: "object",
23
- properties: {
24
- description: {
25
- type: "string",
26
- description: "A short (3-5 word) description of the task",
27
- },
28
- prompt: {
29
- type: "string",
30
- description: "The task for the agent to perform",
31
- },
32
- subagent_type: {
33
- type: "string",
34
- description: "The type of specialized agent to use for this task",
35
- },
36
- run_in_background: {
37
- type: "boolean",
38
- description:
39
- "Set to true to run this command in the background. Use Read to read the output later.",
40
- },
41
- },
42
- required: ["description", "prompt", "subagent_type"],
43
- },
44
- },
45
- },
46
-
47
- prompt: (args?: { availableSubagents?: SubagentConfiguration[] }) => {
48
- const subagentList = args?.availableSubagents
49
- ? args.availableSubagents
50
- .map((config) => {
51
- let toolsStr = "";
52
- if (config.tools && config.tools.length > 0) {
53
- toolsStr = ` (Tools: ${config.tools.join(", ")})`;
54
- } else {
55
- toolsStr = " (Tools: *)";
56
- }
57
-
58
- return `- ${config.name}: ${config.description}${toolsStr}`;
59
- })
60
- .join("\n")
61
- : "";
62
-
63
- return `Launch a new agent to handle complex, multi-step tasks autonomously.
64
-
65
- The Agent tool launches specialized agents (subprocesses) that autonomously handle complex tasks. Each agent type has specific capabilities and tools available to it.
66
-
67
- Available agent types and the tools they have access to:
68
- ${subagentList || "No agents configured"}
69
-
70
- When using the Agent tool, you must specify a subagent_type parameter to select which agent type to use from the list above. Choose the agent whose description best matches the task you want to delegate.
71
-
72
- - When doing file search, prefer to use the ${AGENT_TOOL_NAME} tool in order to reduce context usage.
73
- - You should proactively use the ${AGENT_TOOL_NAME} tool with specialized agents when the task at hand matches the agent's description.
74
- - VERY IMPORTANT: When exploring the codebase to gather context or to answer a question that is not a needle query for a specific file/class/function, it is CRITICAL that you use the ${AGENT_TOOL_NAME} tool with subagent_type=${EXPLORE_SUBAGENT_TYPE} instead of running search commands directly.
75
- - You can optionally run agents in the background using the run_in_background parameter. When an agent runs in the background, you will be automatically notified when it completes — do NOT sleep, poll, or proactively check on its progress. Continue with other work or respond to the user instead.
76
- - **Foreground vs background**: Use foreground (default) when you need the agent's results before you can proceed — e.g., research agents whose findings inform your next steps. Use background when you have genuinely independent work to do in parallel.
77
- - **Don't peek.** The tool result includes an output file path — do not Read or tail it unless the user explicitly asks for a progress check. You get a completion notification; trust it. Reading the transcript mid-flight pulls the agent's tool noise into your context, which defeats the point of backgrounding.
78
- - **Don't race.** After launching, you know nothing about what the agent found. Never fabricate or predict agent results in any format — not as prose, summary, or structured output. The notification arrives as a user-role message in a later turn; it is never something you write yourself. If the user asks a follow-up before the notification lands, tell them the agent is still running — give status, not a guess.`;
79
- },
80
-
81
- execute: async (
82
- args: Record<string, unknown>,
83
- context: ToolContext,
84
- ): Promise<ToolResult> => {
85
- const subagentManager = context.subagentManager;
86
- if (!subagentManager) {
87
- return {
88
- success: false,
89
- content: "",
90
- error: "Subagent manager not available in tool context",
91
- shortResult: "Agent delegation failed",
92
- };
93
- }
94
-
95
- // Input validation
96
- const description = args.description as string;
97
- const prompt = args.prompt as string;
98
- const subagent_type = args.subagent_type as string;
99
- const run_in_background = args.run_in_background as boolean;
100
-
101
- if (!description || typeof description !== "string") {
102
- return {
103
- success: false,
104
- content: "",
105
- error: "description parameter is required and must be a string",
106
- shortResult: "Agent delegation failed",
107
- };
108
- }
109
-
110
- if (!prompt || typeof prompt !== "string") {
111
- return {
112
- success: false,
113
- content: "",
114
- error: "prompt parameter is required and must be a string",
115
- shortResult: "Agent delegation failed",
116
- };
117
- }
118
-
119
- if (!subagent_type || typeof subagent_type !== "string") {
120
- return {
121
- success: false,
122
- content: "",
123
- error: "subagent_type parameter is required and must be a string",
124
- shortResult: "Agent delegation failed",
125
- };
126
- }
127
-
128
- try {
129
- // Subagent selection logic with explicit name matching only
130
- const configuration = await subagentManager.findSubagent(subagent_type);
131
-
132
- if (!configuration) {
133
- // Error handling for nonexistent subagents with available subagents listing
134
- const allConfigs = subagentManager.getConfigurations();
135
- const availableNames = allConfigs.map((c) => c.name).join(", ");
136
-
137
- return {
138
- success: false,
139
- content: "",
140
- error: `No agent found matching "${subagent_type}". Available agents: ${availableNames || "none"}`,
141
- shortResult: "Agent not found",
142
- };
143
- }
144
-
145
- let isBackgrounded = false;
146
-
147
- // Create subagent instance and execute agent
148
- const instance = await subagentManager.createInstance(
149
- configuration,
150
- {
151
- description,
152
- prompt,
153
- subagent_type,
154
- },
155
- run_in_background,
156
- () => {
157
- // Do not update shortResult if it is running in background
158
- if (run_in_background || isBackgrounded) return;
159
-
160
- const messages = instance.messageManager.getMessages();
161
- const tokens = instance.messageManager.getLatestTotalTokens();
162
- const usedTools = instance.usedTools;
163
-
164
- const toolCount = countToolBlocks(messages);
165
- const summary = formatToolTokenSummary(toolCount, tokens);
166
-
167
- const getDisplayParam = (t: {
168
- name: string;
169
- parameters: string;
170
- compactParams?: string;
171
- stage?: string;
172
- }) => {
173
- if (
174
- (t.stage === "end" || t.stage === "running") &&
175
- t.compactParams
176
- ) {
177
- return t.compactParams;
178
- }
179
- const flat = t.parameters.replace(/\n/g, "\\n");
180
- return flat.length > 30 ? `…${flat.slice(-30)}` : flat;
181
- };
182
-
183
- let shortResult = "";
184
- if (toolCount > 2) {
185
- shortResult += "... ";
186
- }
187
- shortResult += summary;
188
- if (usedTools.length > 0) {
189
- shortResult +=
190
- "\n" +
191
- usedTools
192
- .map((t) => `${t.name} ${getDisplayParam(t)}`)
193
- .join("\n");
194
- }
195
-
196
- context.onShortResultUpdate?.(shortResult);
197
- },
198
- );
199
-
200
- return new Promise<ToolResult>((resolve) => {
201
- (async () => {
202
- // Register for backgrounding if not already in background
203
- if (!run_in_background && context.foregroundTaskManager) {
204
- context.foregroundTaskManager.registerForegroundTask({
205
- id: instance.subagentId,
206
- backgroundHandler: async () => {
207
- isBackgrounded = true;
208
- const taskId = await subagentManager.backgroundInstance(
209
- instance.subagentId,
210
- );
211
- const task = context.backgroundTaskManager?.getTask(taskId);
212
- const outputPath = task?.outputPath;
213
- resolve({
214
- success: true,
215
- content: `Agent backgrounded with ID: ${taskId}.${outputPath ? ` Real-time output: ${outputPath}` : ""}`,
216
- shortResult: "Agent backgrounded",
217
- isManuallyBackgrounded: true,
218
- });
219
- },
220
- });
221
- }
222
-
223
- try {
224
- const result = await subagentManager.executeAgent(
225
- instance,
226
- prompt,
227
- context.abortSignal,
228
- run_in_background,
229
- );
230
-
231
- if (isBackgrounded) {
232
- return;
233
- }
234
-
235
- if (run_in_background) {
236
- const task = context.backgroundTaskManager?.getTask(result);
237
- const outputPath = task?.outputPath;
238
- const backgroundMsg = [
239
- `Agent started in background with ID: ${result}.`,
240
- `The agent is working in the background. You will be notified automatically when it completes.`,
241
- `Do not duplicate this agent's work — avoid working with the same files or topics it is using.`,
242
- outputPath
243
- ? `output_file: ${outputPath}`
244
- : `Briefly tell the user what you launched and end your response.`,
245
- ].join("\n");
246
- resolve({
247
- success: true,
248
- content: backgroundMsg,
249
- shortResult: `Agent started in background: ${result}${outputPath ? ` → ${outputPath}` : ""}`,
250
- });
251
- return;
252
- }
253
-
254
- // Cleanup subagent instance after agent completion
255
- subagentManager.cleanupInstance(instance.subagentId);
256
-
257
- const messages = instance.messageManager.getMessages();
258
- const tokens = instance.messageManager.getLatestTotalTokens();
259
- const toolCount = countToolBlocks(messages);
260
- const summary = formatToolTokenSummary(toolCount, tokens);
261
-
262
- resolve({
263
- success: true,
264
- content: result,
265
- shortResult: `Agent completed${summary ? ` ${summary}` : ""}`,
266
- });
267
- } catch (error) {
268
- if (!isBackgrounded) {
269
- // Extract content from the subagent's last assistant message.
270
- // aiManager.sendAIMessage() catch block already added an error
271
- // block via addErrorBlock(), so the error info is in the message
272
- // history — just return it directly.
273
- let content = "";
274
- try {
275
- const messages = instance.messageManager.getMessages();
276
- const lastAssistant = messages
277
- .filter((m) => m.role === "assistant")
278
- .pop();
279
- if (lastAssistant) {
280
- content = lastAssistant.blocks
281
- .filter((b) => b.type === "text" || b.type === "error")
282
- .map((b) => (b as { content: string }).content)
283
- .join("\n")
284
- .trim();
285
- }
286
- } catch {
287
- // Ignore errors when extracting messages
288
- }
289
-
290
- const errorMsg = `Agent delegation failed: ${error instanceof Error ? error.message : String(error)}`;
291
- resolve({
292
- success: false,
293
- content: content || errorMsg,
294
- error: errorMsg,
295
- shortResult: "Delegation error",
296
- });
297
- }
298
- } finally {
299
- if (!run_in_background && context.foregroundTaskManager) {
300
- context.foregroundTaskManager.unregisterForegroundTask(
301
- instance.subagentId,
302
- );
303
- }
304
- }
305
- })();
306
- });
307
- } catch (error) {
308
- return {
309
- success: false,
310
- content: "",
311
- error: `Agent delegation failed: ${error instanceof Error ? error.message : String(error)}`,
312
- shortResult: "Delegation error",
313
- };
314
- }
315
- },
316
-
317
- formatCompactParams: (params: Record<string, unknown>) => {
318
- const subagent_type = params.subagent_type as string;
319
- const description = params.description as string;
320
- return `${subagent_type || "unknown"}: ${description || "no description"}`;
321
- },
322
- };