wave-agent-sdk 1.0.5 → 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 (193) 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 +13 -6
  7. package/dist/managers/slashCommandManager.js +28 -0
  8. package/dist/managers/subagentManager.d.ts +0 -2
  9. package/dist/telemetry/instrumentation.d.ts +1 -1
  10. package/dist/telemetry/instrumentation.js +57 -28
  11. package/dist/tools/bashTool.js +16 -6
  12. package/dist/tools/grepTool.js +11 -3
  13. package/dist/types/index.d.ts +3 -0
  14. package/dist/types/messaging.d.ts +2 -0
  15. package/dist/utils/containerSetup.js +0 -3
  16. package/dist/utils/messageOperations.d.ts +2 -1
  17. package/dist/utils/messageOperations.js +2 -1
  18. package/dist/utils/notificationXml.js +5 -0
  19. package/dist/utils/ripgrep.d.ts +4 -4
  20. package/dist/utils/ripgrep.js +4 -27
  21. package/package.json +20 -8
  22. package/bin/rg +0 -79
  23. package/dist/managers/forkedAgentManager.d.ts +0 -50
  24. package/dist/managers/forkedAgentManager.js +0 -130
  25. package/scripts/install_ripgrep.js +0 -111
  26. package/scripts/postinstall.js +0 -38
  27. package/src/agent.ts +0 -1271
  28. package/src/constants/subagents.ts +0 -4
  29. package/src/constants/toolLimits.ts +0 -15
  30. package/src/constants/tools.ts +0 -24
  31. package/src/core/plugin.ts +0 -237
  32. package/src/core/session.ts +0 -9
  33. package/src/index.ts +0 -38
  34. package/src/managers/MemoryRuleManager.ts +0 -198
  35. package/src/managers/aiManager.ts +0 -2429
  36. package/src/managers/backgroundTaskManager.ts +0 -480
  37. package/src/managers/bangManager.ts +0 -119
  38. package/src/managers/cronManager.ts +0 -401
  39. package/src/managers/foregroundTaskManager.ts +0 -30
  40. package/src/managers/forkedAgentManager.ts +0 -216
  41. package/src/managers/hookManager.ts +0 -1089
  42. package/src/managers/liveConfigManager.ts +0 -445
  43. package/src/managers/lspManager.ts +0 -470
  44. package/src/managers/mcpManager.ts +0 -1010
  45. package/src/managers/messageManager.ts +0 -1184
  46. package/src/managers/messageQueue.ts +0 -157
  47. package/src/managers/permissionManager.ts +0 -1140
  48. package/src/managers/planManager.ts +0 -112
  49. package/src/managers/pluginManager.ts +0 -330
  50. package/src/managers/pluginScopeManager.ts +0 -124
  51. package/src/managers/reversionManager.ts +0 -178
  52. package/src/managers/skillManager.ts +0 -578
  53. package/src/managers/slashCommandManager.ts +0 -564
  54. package/src/managers/subagentManager.ts +0 -915
  55. package/src/managers/toolManager.ts +0 -489
  56. package/src/managers/workflowManager.ts +0 -493
  57. package/src/prompts/autoMemory.ts +0 -33
  58. package/src/prompts/autoMemoryExtraction.ts +0 -146
  59. package/src/prompts/index.ts +0 -506
  60. package/src/prompts/planModeReminders.ts +0 -126
  61. package/src/services/GitService.ts +0 -131
  62. package/src/services/MarketplaceService.ts +0 -1052
  63. package/src/services/MemoryRuleService.ts +0 -71
  64. package/src/services/aiService.ts +0 -919
  65. package/src/services/authService.ts +0 -565
  66. package/src/services/autoMemoryService.ts +0 -280
  67. package/src/services/configurationService.ts +0 -1520
  68. package/src/services/fileWatcher.ts +0 -319
  69. package/src/services/hook.ts +0 -348
  70. package/src/services/initializationService.ts +0 -357
  71. package/src/services/interactionService.ts +0 -229
  72. package/src/services/jsonlHandler.ts +0 -295
  73. package/src/services/memory.ts +0 -234
  74. package/src/services/pluginLoader.ts +0 -252
  75. package/src/services/remoteSettingsService.ts +0 -366
  76. package/src/services/reversionService.ts +0 -122
  77. package/src/services/session.ts +0 -917
  78. package/src/services/taskManager.ts +0 -321
  79. package/src/telemetry/events.ts +0 -65
  80. package/src/telemetry/instrumentation.ts +0 -499
  81. package/src/telemetry/sessionTracing.ts +0 -348
  82. package/src/tools/agentTool.ts +0 -322
  83. package/src/tools/askUserQuestion.ts +0 -160
  84. package/src/tools/bashTool.ts +0 -621
  85. package/src/tools/buildTool.ts +0 -61
  86. package/src/tools/cronCreateTool.ts +0 -161
  87. package/src/tools/cronDeleteTool.ts +0 -51
  88. package/src/tools/cronListTool.ts +0 -47
  89. package/src/tools/editTool.ts +0 -313
  90. package/src/tools/enterPlanMode.ts +0 -124
  91. package/src/tools/enterWorktreeTool.ts +0 -190
  92. package/src/tools/exitPlanMode.ts +0 -139
  93. package/src/tools/exitWorktreeTool.ts +0 -236
  94. package/src/tools/globTool.ts +0 -167
  95. package/src/tools/grepTool.ts +0 -399
  96. package/src/tools/lspTool.ts +0 -883
  97. package/src/tools/readTool.ts +0 -426
  98. package/src/tools/skillTool.ts +0 -248
  99. package/src/tools/taskManagementTools.ts +0 -574
  100. package/src/tools/taskStopTool.ts +0 -78
  101. package/src/tools/types.ts +0 -137
  102. package/src/tools/webFetchTool.ts +0 -373
  103. package/src/tools/workflowTool.ts +0 -205
  104. package/src/tools/writeTool.ts +0 -267
  105. package/src/types/agent.ts +0 -128
  106. package/src/types/auth.ts +0 -19
  107. package/src/types/commands.ts +0 -30
  108. package/src/types/config.ts +0 -43
  109. package/src/types/configuration.ts +0 -168
  110. package/src/types/core.ts +0 -105
  111. package/src/types/cron.ts +0 -13
  112. package/src/types/environment.ts +0 -106
  113. package/src/types/fileSearch.ts +0 -4
  114. package/src/types/history.ts +0 -7
  115. package/src/types/hooks.ts +0 -256
  116. package/src/types/index.ts +0 -41
  117. package/src/types/lsp.ts +0 -98
  118. package/src/types/marketplace.ts +0 -71
  119. package/src/types/mcp.ts +0 -43
  120. package/src/types/memoryRule.ts +0 -31
  121. package/src/types/messaging.ts +0 -114
  122. package/src/types/permissions.ts +0 -80
  123. package/src/types/plugins.ts +0 -39
  124. package/src/types/processes.ts +0 -99
  125. package/src/types/reversion.ts +0 -29
  126. package/src/types/session.ts +0 -12
  127. package/src/types/skills.ts +0 -108
  128. package/src/types/tasks.ts +0 -13
  129. package/src/types/telemetry.ts +0 -98
  130. package/src/types/tools.ts +0 -44
  131. package/src/types/workflow.ts +0 -6
  132. package/src/utils/abortUtils.ts +0 -118
  133. package/src/utils/atomicWrite.ts +0 -61
  134. package/src/utils/bashParser.ts +0 -899
  135. package/src/utils/cacheControlUtils.ts +0 -426
  136. package/src/utils/commandArgumentParser.ts +0 -161
  137. package/src/utils/commandPathResolver.ts +0 -88
  138. package/src/utils/configPaths.ts +0 -220
  139. package/src/utils/configValidator.ts +0 -100
  140. package/src/utils/constants.ts +0 -39
  141. package/src/utils/container.ts +0 -92
  142. package/src/utils/containerSetup.ts +0 -371
  143. package/src/utils/convertMessagesForAPI.ts +0 -326
  144. package/src/utils/cronTasks.ts +0 -128
  145. package/src/utils/cronTasksLock.ts +0 -182
  146. package/src/utils/cronToHuman.ts +0 -99
  147. package/src/utils/customCommands.ts +0 -95
  148. package/src/utils/editUtils.ts +0 -20
  149. package/src/utils/fileFormat.ts +0 -40
  150. package/src/utils/fileSearch.ts +0 -151
  151. package/src/utils/fileUtils.ts +0 -223
  152. package/src/utils/gitUtils.ts +0 -315
  153. package/src/utils/globalLogger.ts +0 -128
  154. package/src/utils/groupMessagesByApiRound.ts +0 -121
  155. package/src/utils/hookMatcher.ts +0 -176
  156. package/src/utils/markdownParser.ts +0 -317
  157. package/src/utils/mcpUtils.ts +0 -138
  158. package/src/utils/messageOperations.ts +0 -659
  159. package/src/utils/modelCapabilities.ts +0 -30
  160. package/src/utils/nameGenerator.ts +0 -99
  161. package/src/utils/notificationXml.ts +0 -57
  162. package/src/utils/openaiClient.ts +0 -251
  163. package/src/utils/parseCronExpression.ts +0 -78
  164. package/src/utils/path.ts +0 -72
  165. package/src/utils/pathEncoder.ts +0 -400
  166. package/src/utils/pathSafety.ts +0 -40
  167. package/src/utils/promptHistory.ts +0 -168
  168. package/src/utils/ripgrep.ts +0 -35
  169. package/src/utils/shellResolver.ts +0 -190
  170. package/src/utils/skillParser.ts +0 -246
  171. package/src/utils/stringUtils.ts +0 -156
  172. package/src/utils/subagentParser.ts +0 -279
  173. package/src/utils/taskReminder.ts +0 -96
  174. package/src/utils/tokenCalculation.ts +0 -43
  175. package/src/utils/tokenEstimate.ts +0 -34
  176. package/src/utils/toolResultStorage.ts +0 -117
  177. package/src/utils/worktreeSession.ts +0 -26
  178. package/src/utils/worktreeUtils.ts +0 -786
  179. package/src/workflow/budgetTracker.ts +0 -34
  180. package/src/workflow/concurrencyLimiter.ts +0 -47
  181. package/src/workflow/journal.ts +0 -95
  182. package/src/workflow/progressReporter.ts +0 -141
  183. package/src/workflow/runState.ts +0 -65
  184. package/src/workflow/scriptRuntime.ts +0 -274
  185. package/src/workflow/structuredOutput.ts +0 -123
  186. package/src/workflow/types.ts +0 -101
  187. package/src/workflow/workflowApis.ts +0 -410
  188. package/vendor/ripgrep/linux-aarch64/rg +0 -0
  189. package/vendor/ripgrep/linux-x86_64/rg +0 -0
  190. package/vendor/ripgrep/macos-aarch64/rg +0 -0
  191. package/vendor/ripgrep/macos-x86_64/rg +0 -0
  192. package/vendor/ripgrep/windows-aarch64/rg.exe +0 -0
  193. 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
- };