@vybestack/llxprt-code-agents 0.10.0-nightly.260613.1adad3b34

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 (176) hide show
  1. package/dist/.last_build +0 -0
  2. package/dist/index.d.ts +10 -0
  3. package/dist/index.js +11 -0
  4. package/dist/index.js.map +1 -0
  5. package/dist/src/agents/executor-prompt-builder.d.ts +19 -0
  6. package/dist/src/agents/executor-prompt-builder.js +44 -0
  7. package/dist/src/agents/executor-prompt-builder.js.map +1 -0
  8. package/dist/src/agents/executor-termination.d.ts +22 -0
  9. package/dist/src/agents/executor-termination.js +44 -0
  10. package/dist/src/agents/executor-termination.js.map +1 -0
  11. package/dist/src/agents/executor-validation.d.ts +17 -0
  12. package/dist/src/agents/executor-validation.js +35 -0
  13. package/dist/src/agents/executor-validation.js.map +1 -0
  14. package/dist/src/agents/executor.d.ts +136 -0
  15. package/dist/src/agents/executor.js +874 -0
  16. package/dist/src/agents/executor.js.map +1 -0
  17. package/dist/src/agents/invocation.d.ts +45 -0
  18. package/dist/src/agents/invocation.js +118 -0
  19. package/dist/src/agents/invocation.js.map +1 -0
  20. package/dist/src/agents/recovery.d.ts +75 -0
  21. package/dist/src/agents/recovery.js +107 -0
  22. package/dist/src/agents/recovery.js.map +1 -0
  23. package/dist/src/agents/types.d.ts +149 -0
  24. package/dist/src/agents/types.js +18 -0
  25. package/dist/src/agents/types.js.map +1 -0
  26. package/dist/src/agents/utils.d.ts +15 -0
  27. package/dist/src/agents/utils.js +28 -0
  28. package/dist/src/agents/utils.js.map +1 -0
  29. package/dist/src/compression/CompressionHandler.d.ts +178 -0
  30. package/dist/src/compression/CompressionHandler.js +630 -0
  31. package/dist/src/compression/CompressionHandler.js.map +1 -0
  32. package/dist/src/compression/HighDensityStrategy.d.ts +113 -0
  33. package/dist/src/compression/HighDensityStrategy.js +769 -0
  34. package/dist/src/compression/HighDensityStrategy.js.map +1 -0
  35. package/dist/src/compression/MiddleOutStrategy.d.ts +27 -0
  36. package/dist/src/compression/MiddleOutStrategy.js +347 -0
  37. package/dist/src/compression/MiddleOutStrategy.js.map +1 -0
  38. package/dist/src/compression/OneShotStrategy.d.ts +22 -0
  39. package/dist/src/compression/OneShotStrategy.js +235 -0
  40. package/dist/src/compression/OneShotStrategy.js.map +1 -0
  41. package/dist/src/compression/TopDownTruncationStrategy.d.ts +13 -0
  42. package/dist/src/compression/TopDownTruncationStrategy.js +74 -0
  43. package/dist/src/compression/TopDownTruncationStrategy.js.map +1 -0
  44. package/dist/src/compression/compressionBudgeting.d.ts +34 -0
  45. package/dist/src/compression/compressionBudgeting.js +139 -0
  46. package/dist/src/compression/compressionBudgeting.js.map +1 -0
  47. package/dist/src/compression/compressionStrategyFactory.d.ts +23 -0
  48. package/dist/src/compression/compressionStrategyFactory.js +40 -0
  49. package/dist/src/compression/compressionStrategyFactory.js.map +1 -0
  50. package/dist/src/compression/index.d.ts +16 -0
  51. package/dist/src/compression/index.js +17 -0
  52. package/dist/src/compression/index.js.map +1 -0
  53. package/dist/src/compression/reasoningUtils.d.ts +18 -0
  54. package/dist/src/compression/reasoningUtils.js +22 -0
  55. package/dist/src/compression/reasoningUtils.js.map +1 -0
  56. package/dist/src/compression/utils.d.ts +114 -0
  57. package/dist/src/compression/utils.js +316 -0
  58. package/dist/src/compression/utils.js.map +1 -0
  59. package/dist/src/core/AgentHookManager.d.ts +41 -0
  60. package/dist/src/core/AgentHookManager.js +65 -0
  61. package/dist/src/core/AgentHookManager.js.map +1 -0
  62. package/dist/src/core/ChatSessionFactory.d.ts +50 -0
  63. package/dist/src/core/ChatSessionFactory.js +216 -0
  64. package/dist/src/core/ChatSessionFactory.js.map +1 -0
  65. package/dist/src/core/ConversationManager.d.ts +113 -0
  66. package/dist/src/core/ConversationManager.js +311 -0
  67. package/dist/src/core/ConversationManager.js.map +1 -0
  68. package/dist/src/core/DirectMessageProcessor.d.ts +79 -0
  69. package/dist/src/core/DirectMessageProcessor.js +478 -0
  70. package/dist/src/core/DirectMessageProcessor.js.map +1 -0
  71. package/dist/src/core/IdeContextTracker.d.ts +56 -0
  72. package/dist/src/core/IdeContextTracker.js +207 -0
  73. package/dist/src/core/IdeContextTracker.js.map +1 -0
  74. package/dist/src/core/MessageConverter.d.ts +78 -0
  75. package/dist/src/core/MessageConverter.js +492 -0
  76. package/dist/src/core/MessageConverter.js.map +1 -0
  77. package/dist/src/core/MessageStreamOrchestrator.d.ts +85 -0
  78. package/dist/src/core/MessageStreamOrchestrator.js +448 -0
  79. package/dist/src/core/MessageStreamOrchestrator.js.map +1 -0
  80. package/dist/src/core/MessageStreamTerminalHandler.d.ts +16 -0
  81. package/dist/src/core/MessageStreamTerminalHandler.js +170 -0
  82. package/dist/src/core/MessageStreamTerminalHandler.js.map +1 -0
  83. package/dist/src/core/StreamProcessor.d.ts +105 -0
  84. package/dist/src/core/StreamProcessor.js +660 -0
  85. package/dist/src/core/StreamProcessor.js.map +1 -0
  86. package/dist/src/core/TodoContinuationService.d.ts +74 -0
  87. package/dist/src/core/TodoContinuationService.js +312 -0
  88. package/dist/src/core/TodoContinuationService.js.map +1 -0
  89. package/dist/src/core/TurnProcessor.d.ts +91 -0
  90. package/dist/src/core/TurnProcessor.js +540 -0
  91. package/dist/src/core/TurnProcessor.js.map +1 -0
  92. package/dist/src/core/baseLlmClient.d.ts +115 -0
  93. package/dist/src/core/baseLlmClient.js +231 -0
  94. package/dist/src/core/baseLlmClient.js.map +1 -0
  95. package/dist/src/core/bucketFailoverIntegration.d.ts +62 -0
  96. package/dist/src/core/bucketFailoverIntegration.js +186 -0
  97. package/dist/src/core/bucketFailoverIntegration.js.map +1 -0
  98. package/dist/src/core/chatSession.d.ts +121 -0
  99. package/dist/src/core/chatSession.js +720 -0
  100. package/dist/src/core/chatSession.js.map +1 -0
  101. package/dist/src/core/client.d.ts +132 -0
  102. package/dist/src/core/client.js +524 -0
  103. package/dist/src/core/client.js.map +1 -0
  104. package/dist/src/core/clientHelpers.d.ts +15 -0
  105. package/dist/src/core/clientHelpers.js +122 -0
  106. package/dist/src/core/clientHelpers.js.map +1 -0
  107. package/dist/src/core/clientLlmUtilities.d.ts +30 -0
  108. package/dist/src/core/clientLlmUtilities.js +125 -0
  109. package/dist/src/core/clientLlmUtilities.js.map +1 -0
  110. package/dist/src/core/clientToolGovernance.d.ts +37 -0
  111. package/dist/src/core/clientToolGovernance.js +104 -0
  112. package/dist/src/core/clientToolGovernance.js.map +1 -0
  113. package/dist/src/core/compression-config.d.ts +12 -0
  114. package/dist/src/core/compression-config.js +21 -0
  115. package/dist/src/core/compression-config.js.map +1 -0
  116. package/dist/src/core/coreToolScheduler.d.ts +105 -0
  117. package/dist/src/core/coreToolScheduler.js +453 -0
  118. package/dist/src/core/coreToolScheduler.js.map +1 -0
  119. package/dist/src/core/hookToolRestrictions.d.ts +18 -0
  120. package/dist/src/core/hookToolRestrictions.js +187 -0
  121. package/dist/src/core/hookToolRestrictions.js.map +1 -0
  122. package/dist/src/core/nonInteractiveToolExecutor.d.ts +17 -0
  123. package/dist/src/core/nonInteractiveToolExecutor.js +102 -0
  124. package/dist/src/core/nonInteractiveToolExecutor.js.map +1 -0
  125. package/dist/src/core/subagent.d.ts +105 -0
  126. package/dist/src/core/subagent.js +650 -0
  127. package/dist/src/core/subagent.js.map +1 -0
  128. package/dist/src/core/subagentExecution.d.ts +104 -0
  129. package/dist/src/core/subagentExecution.js +275 -0
  130. package/dist/src/core/subagentExecution.js.map +1 -0
  131. package/dist/src/core/subagentOrchestrator.d.ts +84 -0
  132. package/dist/src/core/subagentOrchestrator.js +503 -0
  133. package/dist/src/core/subagentOrchestrator.js.map +1 -0
  134. package/dist/src/core/subagentRuntimeSetup.d.ts +75 -0
  135. package/dist/src/core/subagentRuntimeSetup.js +387 -0
  136. package/dist/src/core/subagentRuntimeSetup.js.map +1 -0
  137. package/dist/src/core/subagentScheduler.d.ts +17 -0
  138. package/dist/src/core/subagentScheduler.js +7 -0
  139. package/dist/src/core/subagentScheduler.js.map +1 -0
  140. package/dist/src/core/subagentToolProcessing.d.ts +51 -0
  141. package/dist/src/core/subagentToolProcessing.js +359 -0
  142. package/dist/src/core/subagentToolProcessing.js.map +1 -0
  143. package/dist/src/core/toolGovernance.d.ts +18 -0
  144. package/dist/src/core/toolGovernance.js +50 -0
  145. package/dist/src/core/toolGovernance.js.map +1 -0
  146. package/dist/src/core/turn.d.ts +47 -0
  147. package/dist/src/core/turn.js +455 -0
  148. package/dist/src/core/turn.js.map +1 -0
  149. package/dist/src/core/turnLogging.d.ts +35 -0
  150. package/dist/src/core/turnLogging.js +80 -0
  151. package/dist/src/core/turnLogging.js.map +1 -0
  152. package/dist/src/index.d.ts +32 -0
  153. package/dist/src/index.js +44 -0
  154. package/dist/src/index.js.map +1 -0
  155. package/dist/src/scheduler/confirmation-coordinator.d.ts +139 -0
  156. package/dist/src/scheduler/confirmation-coordinator.js +443 -0
  157. package/dist/src/scheduler/confirmation-coordinator.js.map +1 -0
  158. package/dist/src/scheduler/result-aggregator.d.ts +127 -0
  159. package/dist/src/scheduler/result-aggregator.js +302 -0
  160. package/dist/src/scheduler/result-aggregator.js.map +1 -0
  161. package/dist/src/scheduler/status-transitions.d.ts +39 -0
  162. package/dist/src/scheduler/status-transitions.js +184 -0
  163. package/dist/src/scheduler/status-transitions.js.map +1 -0
  164. package/dist/src/scheduler/tool-dispatcher.d.ts +40 -0
  165. package/dist/src/scheduler/tool-dispatcher.js +120 -0
  166. package/dist/src/scheduler/tool-dispatcher.js.map +1 -0
  167. package/dist/src/scheduler/tool-executor.d.ts +44 -0
  168. package/dist/src/scheduler/tool-executor.js +117 -0
  169. package/dist/src/scheduler/tool-executor.js.map +1 -0
  170. package/dist/src/scheduler/utils.d.ts +11 -0
  171. package/dist/src/scheduler/utils.js +19 -0
  172. package/dist/src/scheduler/utils.js.map +1 -0
  173. package/dist/src/tools/task.d.ts +128 -0
  174. package/dist/src/tools/task.js +932 -0
  175. package/dist/src/tools/task.js.map +1 -0
  176. package/package.json +54 -0
@@ -0,0 +1,932 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Vybestack LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ /* eslint-disable complexity, max-lines -- Phase 5: legacy core boundary retained while larger decomposition continues. */
7
+ import { BaseDeclarativeTool, BaseToolInvocation, Kind, } from '@vybestack/llxprt-code-tools';
8
+ import { SubagentOrchestrator, } from '../core/subagentOrchestrator.js';
9
+ import { ContextState, SubagentTerminateMode, } from '@vybestack/llxprt-code-core/core/subagentTypes.js';
10
+ import { ToolErrorType } from '@vybestack/llxprt-code-tools';
11
+ import { DEFAULT_AGENT_ID } from '@vybestack/llxprt-code-core/core/turn.js';
12
+ import { DebugLogger } from '@vybestack/llxprt-code-core/debug/DebugLogger.js';
13
+ import { canonicalizeToolName, buildToolGovernance, isToolBlocked, } from '../core/toolGovernance.js';
14
+ const taskLogger = new DebugLogger('llxprt:task');
15
+ // Tool timeout settings (Issue #1049)
16
+ const DEFAULT_TASK_TIMEOUT_SECONDS = 900;
17
+ const MAX_TASK_TIMEOUT_SECONDS = 1800;
18
+ const normalizeToolNameForPolicy = (name) => canonicalizeToolName(name);
19
+ function normalizeSubagentStreamingText(text) {
20
+ if (!text) {
21
+ return '';
22
+ }
23
+ const lf = text.replace(/\r\n?/g, '\n');
24
+ return lf.endsWith('\n') ? lf : lf + '\n';
25
+ }
26
+ function launchRequestName(launchResult) {
27
+ return launchResult.config.name;
28
+ }
29
+ /**
30
+ * Formats a human readable summary for successful subagent execution.
31
+ */
32
+ function formatSuccessDisplay(subagentName, agentId, output) {
33
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- BN4-C-P01: preserve defensive runtime boundary guard despite current static types.
34
+ const emittedVars = Object.entries(output.emitted_vars ?? {});
35
+ const finalMessageSection = output.final_message
36
+ ? `Final message:\n${output.final_message}`
37
+ : 'Final message: _(none)_';
38
+ const emittedSection = emittedVars.length === 0
39
+ ? 'Emitted variables: _(none)_'
40
+ : `Emitted variables:\n${emittedVars
41
+ .map(([key, value]) => `- **${key}**: ${value}`)
42
+ .join('\n')}`;
43
+ return [
44
+ `Subagent **${subagentName}** (\`${agentId}\`) completed with status \`${output.terminate_reason}\`.`,
45
+ finalMessageSection,
46
+ emittedSection,
47
+ ].join('\n\n');
48
+ }
49
+ /**
50
+ * Summarizes the subagent output as JSON for inclusion in tool history.
51
+ */
52
+ function formatSuccessContent(agentId, output) {
53
+ const payload = {
54
+ agent_id: agentId,
55
+ terminate_reason: output.terminate_reason,
56
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- BN4-C-P01: preserve defensive runtime boundary guard despite current static types.
57
+ emitted_vars: output.emitted_vars ?? {},
58
+ };
59
+ if (output.final_message !== undefined) {
60
+ payload.final_message = output.final_message;
61
+ }
62
+ return JSON.stringify(payload, null, 2);
63
+ }
64
+ class TaskToolInvocation extends BaseToolInvocation {
65
+ config;
66
+ normalized;
67
+ deps;
68
+ constructor(config, params, normalized, deps, messageBus) {
69
+ super(params, messageBus);
70
+ this.config = config;
71
+ this.normalized = normalized;
72
+ this.deps = deps;
73
+ }
74
+ getDescription() {
75
+ return `Run subagent '${this.normalized.subagentName}' to accomplish: ${this.normalized.goalPrompt}`;
76
+ }
77
+ buildExcludedToolNames() {
78
+ return new Set(['task', 'list_subagents']
79
+ .map((name) => normalizeToolNameForPolicy(name))
80
+ .filter((name) => name.length > 0));
81
+ }
82
+ isExcludedToolName(name, excluded) {
83
+ const canonical = normalizeToolNameForPolicy(name);
84
+ return canonical.length > 0 && excluded.has(canonical);
85
+ }
86
+ buildGovernedToolWhitelist(candidateTools, registry) {
87
+ if (!candidateTools || candidateTools.length === 0) {
88
+ return undefined;
89
+ }
90
+ const excluded = this.buildExcludedToolNames();
91
+ const governance = buildToolGovernance(this.config);
92
+ const allowedRegistryTools = registry
93
+ .getEnabledTools()
94
+ .map((tool) => tool.name)
95
+ .filter((name) => !!name && !this.isExcludedToolName(name, excluded));
96
+ const allowedByCanonical = new Map();
97
+ for (const toolName of allowedRegistryTools) {
98
+ const canonical = normalizeToolNameForPolicy(toolName);
99
+ if (canonical && !allowedByCanonical.has(canonical)) {
100
+ allowedByCanonical.set(canonical, toolName);
101
+ }
102
+ }
103
+ const filteredTools = candidateTools.map((name) => {
104
+ if (!name || this.isExcludedToolName(name, excluded)) {
105
+ return undefined;
106
+ }
107
+ const canonical = normalizeToolNameForPolicy(name);
108
+ if (!canonical || isToolBlocked(canonical, governance)) {
109
+ return undefined;
110
+ }
111
+ return allowedByCanonical.get(canonical);
112
+ });
113
+ const validTools = filteredTools.filter((name) => typeof name === 'string' && name.length > 0);
114
+ if (validTools.length === 0) {
115
+ return undefined;
116
+ }
117
+ const uniqueByCanonical = new Set();
118
+ const deduped = [];
119
+ for (const tool of validTools) {
120
+ const canonical = normalizeToolNameForPolicy(tool);
121
+ if (!canonical || uniqueByCanonical.has(canonical)) {
122
+ continue;
123
+ }
124
+ uniqueByCanonical.add(canonical);
125
+ deduped.push(tool);
126
+ }
127
+ return deduped.length > 0 ? deduped : undefined;
128
+ }
129
+ createLaunchRequest(timeoutMs) {
130
+ const { subagentName, behaviourPrompts, toolWhitelist, outputSpec } = this.normalized;
131
+ const launchRequest = {
132
+ name: subagentName,
133
+ };
134
+ if (timeoutMs !== undefined) {
135
+ launchRequest.runConfig = {
136
+ max_time_minutes: timeoutMs / 60_000,
137
+ };
138
+ }
139
+ if (this.params.grace_period_seconds !== undefined) {
140
+ launchRequest.runConfig = {
141
+ max_time_minutes: launchRequest.runConfig?.max_time_minutes ?? Number.POSITIVE_INFINITY,
142
+ grace_period_seconds: this.params.grace_period_seconds,
143
+ };
144
+ }
145
+ if (behaviourPrompts.length > 0) {
146
+ launchRequest.behaviourPrompts = behaviourPrompts;
147
+ }
148
+ const registry = this.deps.getToolRegistry?.();
149
+ let effectiveWhitelist = toolWhitelist;
150
+ const hasExplicitWhitelist = Array.isArray(this.params.tool_whitelist) ||
151
+ Array.isArray(this.params.toolWhitelist);
152
+ if (registry) {
153
+ if (effectiveWhitelist && effectiveWhitelist.length > 0) {
154
+ effectiveWhitelist = this.buildGovernedToolWhitelist(effectiveWhitelist, registry);
155
+ }
156
+ if (!hasExplicitWhitelist &&
157
+ (!effectiveWhitelist || effectiveWhitelist.length === 0)) {
158
+ effectiveWhitelist = this.buildGovernedToolWhitelist(registry.getEnabledTools().map((tool) => tool.name), registry);
159
+ }
160
+ }
161
+ if (effectiveWhitelist && effectiveWhitelist.length > 0) {
162
+ const whitelistSet = new Set(effectiveWhitelist.filter((name) => Boolean(name)));
163
+ launchRequest.toolConfig = {
164
+ tools: Array.from(whitelistSet),
165
+ };
166
+ }
167
+ taskLogger.debug(() => {
168
+ const summary = launchRequest.toolConfig?.tools &&
169
+ launchRequest.toolConfig.tools.length > 0
170
+ ? `${launchRequest.toolConfig.tools.length} tools`
171
+ : 'no tools provided';
172
+ return `Prepared launch request for '${subagentName}': toolConfig=${summary}`;
173
+ });
174
+ if (outputSpec && Object.keys(outputSpec).length > 0) {
175
+ launchRequest.outputConfig = {
176
+ outputs: outputSpec,
177
+ };
178
+ }
179
+ return launchRequest;
180
+ }
181
+ async execute(signal, updateOutput) {
182
+ if (this.normalized.async) {
183
+ return this.executeAsync(signal, updateOutput);
184
+ }
185
+ const { timeoutMs, timeoutSeconds, timeoutController, timeoutId, onUserAbort, } = this.createTimeoutControllers(signal);
186
+ if (signal.aborted) {
187
+ onUserAbort();
188
+ signal.removeEventListener('abort', onUserAbort);
189
+ if (timeoutId !== null) {
190
+ clearTimeout(timeoutId);
191
+ }
192
+ return this.createCancelledResult('Task execution aborted before launch.');
193
+ }
194
+ let orchestrator;
195
+ try {
196
+ orchestrator = this.deps.createOrchestrator();
197
+ }
198
+ catch (error) {
199
+ taskLogger.warn(() => `Failed to create orchestrator for '${this.normalized.subagentName}': ${error instanceof Error ? error.message : String(error)}`);
200
+ signal.removeEventListener('abort', onUserAbort);
201
+ if (timeoutId !== null) {
202
+ clearTimeout(timeoutId);
203
+ }
204
+ return this.createErrorResult(error, 'Task tool could not initialize subagent orchestrator.');
205
+ }
206
+ const launchRequest = this.createLaunchRequest(timeoutMs);
207
+ taskLogger.debug(() => `Launching subagent '${launchRequest.name}'`);
208
+ const abortResult = await this.launchSubagent(orchestrator, launchRequest, signal, timeoutController, timeoutSeconds, onUserAbort, timeoutId, updateOutput);
209
+ return abortResult;
210
+ }
211
+ async launchSubagent(orchestrator, launchRequest, signal, timeoutController, timeoutSeconds, onUserAbort, timeoutId, updateOutput) {
212
+ const abortState = this.createAbortState(launchRequest, signal);
213
+ signal.addEventListener('abort', abortState.abortHandler, { once: true });
214
+ if (signal.aborted) {
215
+ abortState.abortHandler();
216
+ abortState.removeAbortHandler();
217
+ signal.removeEventListener('abort', onUserAbort);
218
+ if (timeoutId !== null) {
219
+ clearTimeout(timeoutId);
220
+ }
221
+ return this.createCancelledResult('Task execution aborted before launch.');
222
+ }
223
+ const launchResult = await this.attemptLaunch(orchestrator, launchRequest, signal, timeoutController, timeoutSeconds, onUserAbort, timeoutId, abortState);
224
+ if (launchResult === null) {
225
+ if (this.lastLaunchError !== undefined) {
226
+ const error = this.lastLaunchError;
227
+ this.lastLaunchError = undefined;
228
+ return this.createErrorResult(error, `Unable to launch subagent '${this.normalized.subagentName}'.`);
229
+ }
230
+ if (abortState.aborted.timedOut) {
231
+ return this.createTimeoutResult(timeoutSeconds);
232
+ }
233
+ return this.createCancelledResult('Task aborted during launch.');
234
+ }
235
+ return this.runSubagentExecution(launchResult, signal, timeoutController, timeoutSeconds, onUserAbort, timeoutId, abortState, updateOutput);
236
+ }
237
+ createAbortState(launchRequest, signal) {
238
+ const state = { aborted: false, timedOut: false };
239
+ let liveScope;
240
+ const abortHandler = () => {
241
+ if (state.aborted)
242
+ return;
243
+ state.aborted = true;
244
+ taskLogger.warn(() => `Cancellation requested for subagent '${launchRequest.name}'`);
245
+ try {
246
+ const candidate = liveScope?.scope;
247
+ candidate?.cancel?.('User aborted task execution.');
248
+ }
249
+ catch (error) {
250
+ taskLogger.warn(() => `Error while cancelling subagent '${launchRequest.name}': ${error instanceof Error ? error.message : String(error)}`);
251
+ }
252
+ };
253
+ const removeAbortHandler = () => {
254
+ signal.removeEventListener('abort', abortHandler);
255
+ };
256
+ const setLaunchResult = (result) => {
257
+ liveScope = result;
258
+ };
259
+ return {
260
+ aborted: state,
261
+ abortHandler,
262
+ removeAbortHandler,
263
+ setLaunchResult,
264
+ };
265
+ }
266
+ async attemptLaunch(orchestrator, launchRequest, signal, timeoutController, timeoutSeconds, onUserAbort, timeoutId, abortState) {
267
+ let launchResult;
268
+ try {
269
+ launchResult = await orchestrator.launch(launchRequest, timeoutController.signal);
270
+ abortState.setLaunchResult(launchResult);
271
+ return launchResult;
272
+ }
273
+ catch (error) {
274
+ abortState.removeAbortHandler();
275
+ signal.removeEventListener('abort', onUserAbort);
276
+ if (timeoutId !== null) {
277
+ clearTimeout(timeoutId);
278
+ }
279
+ if (this.isTimeoutError(signal, timeoutController, error)) {
280
+ abortState.aborted.timedOut = true;
281
+ return null;
282
+ }
283
+ if (this.isAbortError(error) ||
284
+ abortState.aborted.aborted ||
285
+ signal.aborted) {
286
+ return null;
287
+ }
288
+ taskLogger.warn(() => `Launch failure for '${launchRequest.name}': ${error instanceof Error ? error.message : String(error)}`);
289
+ this.lastLaunchError = error;
290
+ return null;
291
+ }
292
+ }
293
+ lastLaunchError = undefined;
294
+ async runSubagentExecution(launchResult, signal, timeoutController, timeoutSeconds, onUserAbort, timeoutId, abortState, updateOutput) {
295
+ const { scope, agentId, dispose } = launchResult;
296
+ taskLogger.debug(() => `Subagent '${launchRequestName(launchResult)}' started with agentId=${agentId}`);
297
+ const contextState = this.buildContextState();
298
+ const teardown = this.buildTeardown(dispose, abortState.removeAbortHandler, signal, onUserAbort, timeoutId);
299
+ if (signal.aborted || abortState.aborted.aborted) {
300
+ await teardown();
301
+ return this.createCancelledResult('Task aborted during launch.', agentId, scope.output);
302
+ }
303
+ const { emitClosingSubagentTag } = this.setupStreaming(launchResult, agentId, scope, updateOutput);
304
+ try {
305
+ await this.runSubagent(scope, contextState);
306
+ return await this.handleExecutionResult(scope, agentId, signal, timeoutController, timeoutSeconds, teardown, abortState.aborted);
307
+ }
308
+ catch (error) {
309
+ return await this.handleExecutionError(error, signal, timeoutController, timeoutSeconds, agentId, scope, teardown, abortState.aborted);
310
+ }
311
+ finally {
312
+ emitClosingSubagentTag();
313
+ }
314
+ }
315
+ buildTeardown(dispose, removeAbortHandler, signal, onUserAbort, timeoutId) {
316
+ return async () => {
317
+ removeAbortHandler();
318
+ signal.removeEventListener('abort', onUserAbort);
319
+ if (timeoutId !== null) {
320
+ clearTimeout(timeoutId);
321
+ }
322
+ try {
323
+ await dispose();
324
+ }
325
+ catch {
326
+ // Swallow dispose errors to avoid masking primary error.
327
+ }
328
+ };
329
+ }
330
+ setupStreaming(launchResult, agentId, scope, updateOutput) {
331
+ const subagentName = launchRequestName(launchResult) || this.normalized.subagentName;
332
+ let xmlOutputOpen = false;
333
+ const emitClosingSubagentTag = () => {
334
+ if (!xmlOutputOpen || !updateOutput) {
335
+ return;
336
+ }
337
+ updateOutput(`</subagent name="${subagentName}" id="${agentId}">\n`);
338
+ xmlOutputOpen = false;
339
+ };
340
+ if (updateOutput) {
341
+ updateOutput(`<subagent name="${subagentName}" id="${agentId}">\n`);
342
+ xmlOutputOpen = true;
343
+ const existingHandler = scope.onMessage;
344
+ scope.onMessage = (message) => {
345
+ const cleaned = normalizeSubagentStreamingText(message);
346
+ if (cleaned.trim().length > 0) {
347
+ updateOutput(cleaned);
348
+ }
349
+ existingHandler?.(message);
350
+ };
351
+ }
352
+ return { emitClosingSubagentTag };
353
+ }
354
+ async runSubagent(scope, contextState) {
355
+ const environmentInteractive = this.deps.isInteractiveEnvironment?.() ?? true;
356
+ if (environmentInteractive && typeof scope.runInteractive === 'function') {
357
+ const schedulerFactory = this.deps.getSchedulerFactory?.();
358
+ const interactiveOptions = schedulerFactory
359
+ ? { schedulerFactory }
360
+ : undefined;
361
+ await scope.runInteractive(contextState, interactiveOptions);
362
+ }
363
+ else {
364
+ await scope.runNonInteractive(contextState);
365
+ }
366
+ }
367
+ async handleExecutionError(error, signal, timeoutController, timeoutSeconds, agentId, scope, teardown, abortedState) {
368
+ if (this.isTimeoutError(signal, timeoutController, error)) {
369
+ await teardown();
370
+ return this.createTimeoutResult(timeoutSeconds, scope.output, agentId);
371
+ }
372
+ if (this.isAbortError(error) || abortedState.aborted || signal.aborted) {
373
+ await teardown();
374
+ return this.createCancelledResult('Task execution aborted before completion.', agentId, scope.output);
375
+ }
376
+ const result = this.createErrorResult(error, `Subagent '${this.normalized.subagentName}' failed during execution.`, agentId);
377
+ await teardown();
378
+ taskLogger.warn(() => `Subagent execution error: ${result.error?.message ?? 'unknown'}`);
379
+ return result;
380
+ }
381
+ async handleExecutionResult(scope, agentId, signal, timeoutController, timeoutSeconds, teardown, abortedState) {
382
+ if (abortedState.aborted) {
383
+ await teardown();
384
+ taskLogger.warn(() => `Subagent aborted before completion`);
385
+ return this.createCancelledResult('Task execution aborted before completion.', agentId, scope.output);
386
+ }
387
+ if (this.isTimeoutError(signal, timeoutController)) {
388
+ await teardown();
389
+ return this.createTimeoutResult(timeoutSeconds, scope.output);
390
+ }
391
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- BN4-C-P01: preserve defensive runtime boundary guard despite current static types.
392
+ const output = scope.output ?? {
393
+ terminate_reason: SubagentTerminateMode.ERROR,
394
+ emitted_vars: {},
395
+ };
396
+ taskLogger.debug(() =>
397
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- BN4-C-P01: preserve defensive runtime boundary guard despite current static types.
398
+ `Subagent finished with reason=${output.terminate_reason} emittedKeys=${Object.keys(output.emitted_vars ?? {}).join(', ')}`);
399
+ const llmContent = formatSuccessContent(agentId, output);
400
+ const returnDisplay = formatSuccessDisplay(this.normalized.subagentName, agentId, output);
401
+ await teardown();
402
+ return {
403
+ llmContent,
404
+ returnDisplay,
405
+ metadata: {
406
+ agentId,
407
+ terminateReason: output.terminate_reason,
408
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- BN4-C-P01: preserve defensive runtime boundary guard despite current static types.
409
+ emittedVars: output.emitted_vars ?? {},
410
+ ...(output.final_message ? { finalMessage: output.final_message } : {}),
411
+ },
412
+ };
413
+ }
414
+ isAbortError(error) {
415
+ if (error === null || error === undefined || typeof error !== 'object') {
416
+ return false;
417
+ }
418
+ const result = error.name === 'AbortError';
419
+ return result;
420
+ }
421
+ buildContextState() {
422
+ const context = new ContextState();
423
+ context.set('task_goal', this.normalized.goalPrompt);
424
+ context.set('task_name', this.normalized.subagentName);
425
+ const sessionId = this.config.getSessionId();
426
+ if (sessionId.length > 0) {
427
+ context.set('sessionId', sessionId);
428
+ }
429
+ for (const [key, value] of Object.entries(this.normalized.context)) {
430
+ context.set(key, value);
431
+ }
432
+ context.set('task_behaviour_prompts', [
433
+ ...this.normalized.behaviourPrompts,
434
+ ]);
435
+ return context;
436
+ }
437
+ createErrorResult(error, fallbackMessage, agentId) {
438
+ const detail = error instanceof Error && error.message ? error.message : null;
439
+ const displayMessage = detail
440
+ ? `${fallbackMessage}\nDetails: ${detail}`
441
+ : fallbackMessage;
442
+ const message = detail ?? fallbackMessage;
443
+ taskLogger.warn(() => `Task tool error: ${displayMessage}`);
444
+ return {
445
+ llmContent: displayMessage,
446
+ returnDisplay: displayMessage,
447
+ metadata: agentId
448
+ ? {
449
+ agentId,
450
+ error: message,
451
+ }
452
+ : undefined,
453
+ error: {
454
+ message,
455
+ type: ToolErrorType.UNHANDLED_EXCEPTION,
456
+ },
457
+ };
458
+ }
459
+ createCancelledResult(message, agentId, output) {
460
+ taskLogger.warn(() => `Task tool cancelled for agentId=${agentId ?? DEFAULT_AGENT_ID}: ${message}`);
461
+ return {
462
+ llmContent: message,
463
+ returnDisplay: message,
464
+ metadata: {
465
+ agentId: agentId ?? DEFAULT_AGENT_ID,
466
+ terminateReason: output?.terminate_reason,
467
+ emittedVars: output?.emitted_vars ?? {},
468
+ ...(output?.final_message
469
+ ? { finalMessage: output.final_message }
470
+ : {}),
471
+ cancelled: true,
472
+ },
473
+ error: {
474
+ message,
475
+ type: ToolErrorType.EXECUTION_FAILED,
476
+ },
477
+ };
478
+ }
479
+ createTimeoutControllers(signal) {
480
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- BN4-C-P01: preserve defensive runtime boundary guard despite current static types.
481
+ const settings = this.config.getEphemeralSettings?.() ?? {};
482
+ const defaultTimeoutSeconds = settings['task-default-timeout-seconds'] ??
483
+ DEFAULT_TASK_TIMEOUT_SECONDS;
484
+ const maxTimeoutSeconds = settings['task-max-timeout-seconds'] ??
485
+ MAX_TASK_TIMEOUT_SECONDS;
486
+ const timeoutSeconds = this.resolveTimeoutSeconds(this.params.timeout_seconds, defaultTimeoutSeconds, maxTimeoutSeconds);
487
+ // Convert seconds to milliseconds for setTimeout
488
+ const timeoutMs = timeoutSeconds === undefined ? undefined : timeoutSeconds * 1000;
489
+ const timeoutController = new AbortController();
490
+ const timeoutId = timeoutMs === undefined
491
+ ? null
492
+ : setTimeout(() => timeoutController.abort(), timeoutMs);
493
+ const onUserAbort = () => {
494
+ if (timeoutId !== null) {
495
+ clearTimeout(timeoutId);
496
+ }
497
+ timeoutController.abort();
498
+ };
499
+ signal.addEventListener('abort', onUserAbort, { once: true });
500
+ return {
501
+ timeoutMs,
502
+ timeoutSeconds,
503
+ timeoutController,
504
+ timeoutId,
505
+ onUserAbort,
506
+ };
507
+ }
508
+ resolveTimeoutSeconds(requestedTimeoutSeconds, defaultTimeoutSeconds, maxTimeoutSeconds) {
509
+ if (requestedTimeoutSeconds === -1 || defaultTimeoutSeconds === -1) {
510
+ return undefined;
511
+ }
512
+ const effectiveTimeout = requestedTimeoutSeconds ?? defaultTimeoutSeconds;
513
+ if (maxTimeoutSeconds === -1) {
514
+ return effectiveTimeout;
515
+ }
516
+ if (effectiveTimeout > maxTimeoutSeconds) {
517
+ return maxTimeoutSeconds;
518
+ }
519
+ return effectiveTimeout;
520
+ }
521
+ isTimeoutError(signal, timeoutController, error) {
522
+ if (!timeoutController.signal.aborted || signal.aborted) {
523
+ return false;
524
+ }
525
+ if (error === undefined || error === null) {
526
+ return true;
527
+ }
528
+ return this.isAbortError(error);
529
+ }
530
+ createTimeoutResult(timeoutSeconds, output, agentId) {
531
+ const message = `Task timed out after ${timeoutSeconds ?? DEFAULT_TASK_TIMEOUT_SECONDS}s (timeout_seconds).`;
532
+ return {
533
+ llmContent: message,
534
+ returnDisplay: message,
535
+ metadata: {
536
+ agentId: agentId ?? DEFAULT_AGENT_ID,
537
+ terminateReason: output?.terminate_reason,
538
+ emittedVars: output?.emitted_vars ?? {},
539
+ ...(output?.final_message
540
+ ? { finalMessage: output.final_message }
541
+ : {}),
542
+ timedOut: true,
543
+ },
544
+ error: {
545
+ message,
546
+ type: ToolErrorType.TIMEOUT,
547
+ },
548
+ };
549
+ }
550
+ /**
551
+ * @plan PLAN-20260130-ASYNCTASK.P11
552
+ *
553
+ * Note: Async tasks are designed to run independently in the background.
554
+ * Unlike foreground task execution, they do NOT wire cancellation or timeout
555
+ * to the parent agent's signal. This is an intentional design choice to allow
556
+ * async tasks to continue running even if the foreground agent is interrupted.
557
+ */
558
+ async executeAsync(signal, updateOutput) {
559
+ const settingsCheck = this.checkAsyncSettings();
560
+ if (settingsCheck) {
561
+ return settingsCheck;
562
+ }
563
+ const asyncTaskManager = this.deps.getAsyncTaskManager?.();
564
+ if (asyncTaskManager === undefined) {
565
+ return {
566
+ llmContent: 'Async mode requires AsyncTaskManager to be configured.',
567
+ returnDisplay: 'Error: Async mode not available.',
568
+ error: {
569
+ message: 'AsyncTaskManager not configured',
570
+ type: ToolErrorType.EXECUTION_FAILED,
571
+ },
572
+ };
573
+ }
574
+ let orchestrator;
575
+ try {
576
+ orchestrator = this.deps.createOrchestrator();
577
+ }
578
+ catch (error) {
579
+ return this.createErrorResult(error, 'Failed to create orchestrator for async task.');
580
+ }
581
+ const bookingId = asyncTaskManager.tryReserveAsyncSlot();
582
+ if (!bookingId) {
583
+ return this.createAsyncSlotResult(asyncTaskManager);
584
+ }
585
+ const setupResult = await this.setupAsyncInfrastructure(orchestrator, asyncTaskManager, bookingId);
586
+ if ('error' in setupResult && setupResult.error) {
587
+ return setupResult;
588
+ }
589
+ const { launchResult: _launchResult, agentId, scope, contextState, dispose, asyncAbortController, timeoutId, } = setupResult;
590
+ const asyncStreaming = this.setupAsyncStreaming(scope, agentId, updateOutput);
591
+ this.executeInBackground(scope, contextState, agentId, asyncTaskManager, dispose, asyncAbortController.signal, timeoutId, asyncStreaming?.emitAsyncClosingSubagentTag);
592
+ return {
593
+ llmContent: `Async task launched: subagent '${this.normalized.subagentName}' (ID: ${agentId}). ` +
594
+ `Task is running in background. Use 'check_async_tasks' to monitor progress.`,
595
+ returnDisplay: `Async task started: **${this.normalized.subagentName}** (\`${agentId}\`)`,
596
+ metadata: {
597
+ agentId,
598
+ async: true,
599
+ status: 'running',
600
+ },
601
+ };
602
+ }
603
+ checkAsyncSettings() {
604
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- BN4-C-P01: preserve defensive runtime boundary guard despite current static types.
605
+ const settingsService = this.config.getSettingsService?.();
606
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- BN4-C-P01: preserve defensive runtime boundary guard despite current static types.
607
+ const globalSettings = settingsService?.getAllGlobalSettings?.() ?? {};
608
+ const subagentsSettings = globalSettings['subagents'];
609
+ const globalAsyncEnabled = subagentsSettings?.asyncEnabled !== false;
610
+ if (!globalAsyncEnabled) {
611
+ return {
612
+ llmContent: 'Async subagents are globally disabled via /settings. Enable "Async Subagents Enabled" in /settings to use async mode.',
613
+ returnDisplay: 'Error: Async subagents are globally disabled.',
614
+ error: {
615
+ message: 'Async subagents are globally disabled via /settings.',
616
+ type: ToolErrorType.EXECUTION_FAILED,
617
+ },
618
+ };
619
+ }
620
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- BN4-C-P01: preserve defensive runtime boundary guard despite current static types.
621
+ const ephemeralSettings = this.config.getEphemeralSettings?.() ?? {};
622
+ const profileAsyncEnabled = ephemeralSettings['subagents.async.enabled'] !== false;
623
+ if (!profileAsyncEnabled) {
624
+ return {
625
+ llmContent: 'This profile disables async subagents. Re-enable with: /set subagents.async.enabled true',
626
+ returnDisplay: 'Error: Async subagents disabled in profile.',
627
+ error: {
628
+ message: 'Async subagents disabled in active profile.',
629
+ type: ToolErrorType.EXECUTION_FAILED,
630
+ },
631
+ };
632
+ }
633
+ return undefined;
634
+ }
635
+ createAsyncSlotResult(asyncTaskManager) {
636
+ const canLaunch = asyncTaskManager.canLaunchAsync();
637
+ const baseReason = canLaunch.reason ?? 'Async task limit reached';
638
+ const guidance = 'You can: (1) wait for running async tasks to complete using check_async_tasks, ' +
639
+ '(2) launch this subagent synchronously (without async: true), or ' +
640
+ '(3) try again later when a slot is available.';
641
+ const errorMessage = `${baseReason}. ${guidance}`;
642
+ return {
643
+ llmContent: errorMessage,
644
+ returnDisplay: baseReason,
645
+ error: {
646
+ message: baseReason,
647
+ type: ToolErrorType.EXECUTION_FAILED,
648
+ },
649
+ };
650
+ }
651
+ async setupAsyncInfrastructure(orchestrator, asyncTaskManager, bookingId) {
652
+ let launchResult;
653
+ let agentId;
654
+ let scope;
655
+ let contextState;
656
+ let dispose;
657
+ const asyncAbortController = new AbortController();
658
+ let timeoutId = null;
659
+ let taskRegistered = false;
660
+ try {
661
+ const launchRequest = this.createLaunchRequest();
662
+ launchResult = await orchestrator.launch(launchRequest, undefined);
663
+ agentId = launchResult.agentId;
664
+ scope = launchResult.scope;
665
+ dispose = launchResult.dispose;
666
+ contextState = this.buildContextState();
667
+ const timeoutSetup = this.setupAsyncTimeout(asyncAbortController);
668
+ timeoutId = timeoutSetup.timeoutId;
669
+ asyncTaskManager.registerTask({
670
+ id: agentId,
671
+ subagentName: this.normalized.subagentName,
672
+ goalPrompt: this.normalized.goalPrompt,
673
+ abortController: asyncAbortController,
674
+ }, bookingId);
675
+ taskRegistered = true;
676
+ }
677
+ catch (error) {
678
+ if (!taskRegistered && bookingId) {
679
+ asyncTaskManager.cancelReservation(bookingId);
680
+ }
681
+ if (timeoutId) {
682
+ clearTimeout(timeoutId);
683
+ }
684
+ if (dispose) {
685
+ void dispose();
686
+ }
687
+ return this.createErrorResult(error, `Failed to launch async subagent '${this.normalized.subagentName}'.`);
688
+ }
689
+ return {
690
+ launchResult,
691
+ agentId,
692
+ scope,
693
+ contextState,
694
+ dispose,
695
+ asyncAbortController,
696
+ timeoutId,
697
+ };
698
+ }
699
+ setupAsyncTimeout(asyncAbortController) {
700
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- BN4-C-P01: preserve defensive runtime boundary guard despite current static types.
701
+ const settings = this.config.getEphemeralSettings?.() ?? {};
702
+ const defaultTimeoutSeconds = settings['task-default-timeout-seconds'] ??
703
+ DEFAULT_TASK_TIMEOUT_SECONDS;
704
+ const maxTimeoutSeconds = settings['task-max-timeout-seconds'] ??
705
+ MAX_TASK_TIMEOUT_SECONDS;
706
+ const timeoutSeconds = this.resolveTimeoutSeconds(this.params.timeout_seconds, defaultTimeoutSeconds, maxTimeoutSeconds);
707
+ const timeoutMs = timeoutSeconds === undefined ? undefined : timeoutSeconds * 1000;
708
+ const timeoutId = timeoutMs === undefined
709
+ ? null
710
+ : setTimeout(() => asyncAbortController.abort(), timeoutMs);
711
+ return { timeoutId };
712
+ }
713
+ setupAsyncStreaming(scope, agentId, updateOutput) {
714
+ if (!updateOutput)
715
+ return undefined;
716
+ let asyncXmlOutputOpen = false;
717
+ const emitAsyncClosingSubagentTag = () => {
718
+ if (!asyncXmlOutputOpen) {
719
+ return;
720
+ }
721
+ updateOutput(`</subagent name="${this.normalized.subagentName}" id="${agentId}">\n`);
722
+ asyncXmlOutputOpen = false;
723
+ };
724
+ updateOutput(`<subagent name="${this.normalized.subagentName}" id="${agentId}">\n`);
725
+ asyncXmlOutputOpen = true;
726
+ const existingHandler = scope.onMessage;
727
+ scope.onMessage = (message) => {
728
+ const cleaned = normalizeSubagentStreamingText(message);
729
+ if (cleaned.trim().length > 0) {
730
+ updateOutput(cleaned);
731
+ }
732
+ existingHandler?.(message);
733
+ };
734
+ return { emitAsyncClosingSubagentTag };
735
+ }
736
+ /**
737
+ * @plan PLAN-20260130-ASYNCTASK.P11
738
+ *
739
+ * Execute async task in background using the SAME execution path as sync tasks.
740
+ * The only difference is the foreground agent doesn't wait for completion.
741
+ * - Interactive environment → runInteractive() with shared scheduler (tool calls show in UI)
742
+ * - Non-interactive environment → runNonInteractive()
743
+ */
744
+ executeInBackground(scope, contextState, agentId, asyncTaskManager, dispose, signal, timeoutId, emitClosingSubagentTag) {
745
+ void (async () => {
746
+ try {
747
+ const environmentInteractive = this.deps.isInteractiveEnvironment?.() ?? true;
748
+ if (environmentInteractive &&
749
+ typeof scope.runInteractive === 'function') {
750
+ const schedulerFactory = this.deps.getSchedulerFactory?.();
751
+ const interactiveOptions = schedulerFactory
752
+ ? { schedulerFactory }
753
+ : undefined;
754
+ await scope.runInteractive(contextState, interactiveOptions);
755
+ }
756
+ else {
757
+ await scope.runNonInteractive(contextState);
758
+ }
759
+ if (signal.aborted) {
760
+ const task = asyncTaskManager.getTask(agentId);
761
+ if (task?.status === 'running') {
762
+ asyncTaskManager.failTask(agentId, 'Async task timed out');
763
+ }
764
+ return;
765
+ }
766
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- BN4-C-P01: preserve defensive runtime boundary guard despite current static types.
767
+ const output = scope.output ?? {
768
+ terminate_reason: SubagentTerminateMode.ERROR,
769
+ emitted_vars: {},
770
+ };
771
+ asyncTaskManager.completeTask(agentId, output);
772
+ }
773
+ catch (error) {
774
+ const errorMessage = error instanceof Error ? error.message : String(error);
775
+ asyncTaskManager.failTask(agentId, errorMessage);
776
+ }
777
+ finally {
778
+ emitClosingSubagentTag?.();
779
+ if (timeoutId !== null) {
780
+ clearTimeout(timeoutId);
781
+ }
782
+ try {
783
+ await dispose();
784
+ }
785
+ catch {
786
+ // Swallow dispose errors
787
+ }
788
+ }
789
+ })();
790
+ }
791
+ }
792
+ /**
793
+ * Task tool that launches subagents via SubagentOrchestrator.
794
+ *
795
+ * @plan PLAN-20251029-SUBAGENTIC
796
+ * @requirement REQ-SUBAGENTIC-001, REQ-SUBAGENTIC-002
797
+ */
798
+ export class TaskTool extends BaseDeclarativeTool {
799
+ config;
800
+ dependencies;
801
+ static Name = 'task';
802
+ constructor(config, dependencies = {}) {
803
+ super(TaskTool.Name, 'Task', `Launches a named subagent, streams its progress, and returns the emitted variables upon completion. The subagent runs in an isolated runtime and is disposed after it finishes.`, Kind.Think, {
804
+ type: 'object',
805
+ additionalProperties: false,
806
+ required: ['subagent_name', 'goal_prompt'],
807
+ properties: {
808
+ subagent_name: {
809
+ type: 'string',
810
+ description: 'Name of the registered subagent to launch (as defined in ~/.llxprt/subagents).',
811
+ },
812
+ goal_prompt: {
813
+ type: 'string',
814
+ description: 'Primary goal or prompt to pass to the subagent. Included as the first behavioural prompt.',
815
+ },
816
+ behaviour_prompts: {
817
+ type: 'array',
818
+ description: 'Additional behavioural prompts to append after the goal prompt.',
819
+ items: { type: 'string' },
820
+ },
821
+ tool_whitelist: {
822
+ type: 'array',
823
+ items: { type: 'string' },
824
+ description: 'Restrict the subagent to this explicit list of tools. Tool names must match the registry.',
825
+ },
826
+ output_spec: {
827
+ type: 'object',
828
+ description: 'Expected output variables the subagent must emit before completing.',
829
+ additionalProperties: { type: 'string' },
830
+ },
831
+ timeout_seconds: {
832
+ type: 'number',
833
+ description: 'Optional timeout in seconds for the task execution (-1 for unlimited).',
834
+ },
835
+ grace_period_seconds: {
836
+ type: 'number',
837
+ description: 'Optional grace period in seconds for recovery after a termination condition (TIMEOUT, MAX_TURNS, or protocol violation). Falls back to 60s if not specified or invalid.',
838
+ },
839
+ async: {
840
+ type: 'boolean',
841
+ description: 'If true, launch subagent in background and return immediately. Default: false.',
842
+ },
843
+ context: {
844
+ type: 'object',
845
+ description: 'Optional key/value pairs exposed to the subagent via the execution context.',
846
+ additionalProperties: true,
847
+ },
848
+ },
849
+ }, true, true);
850
+ this.config = config;
851
+ this.dependencies = dependencies;
852
+ }
853
+ validateToolParamValues(params) {
854
+ const subagentName = params.subagent_name ?? params.subagentName ?? params.subagentName;
855
+ if (!subagentName || subagentName.trim().length === 0) {
856
+ return 'Task tool requires a subagent_name.';
857
+ }
858
+ const goalPrompt = params.goal_prompt ?? params.goalPrompt ?? params.goalPrompt;
859
+ if (!goalPrompt || goalPrompt.trim().length === 0) {
860
+ return 'Task tool requires a goal_prompt describing the assignment.';
861
+ }
862
+ return null;
863
+ }
864
+ createInvocation(params, messageBus) {
865
+ const normalized = this.normalizeParams(params);
866
+ return new TaskToolInvocation(this.config, params, normalized, {
867
+ createOrchestrator: () => this.ensureOrchestrator(),
868
+ getToolRegistry: typeof this.config.getToolRegistry === 'function'
869
+ ? () => this.config.getToolRegistry()
870
+ : undefined,
871
+ getSchedulerFactory: this.dependencies.schedulerFactoryProvider,
872
+ isInteractiveEnvironment: this.dependencies.isInteractiveEnvironment ??
873
+ (() => this.config.isInteractive()),
874
+ getAsyncTaskManager: this.dependencies.getAsyncTaskManager,
875
+ }, messageBus);
876
+ }
877
+ normalizeParams(params) {
878
+ const subagentName = (params.subagent_name ??
879
+ params.subagentName ??
880
+ '').trim();
881
+ const goalPrompt = (params.goal_prompt ?? params.goalPrompt ?? '').trim();
882
+ const behaviourPrompts = [
883
+ goalPrompt,
884
+ // eslint-disable-next-line sonarjs/expression-complexity -- Existing structure is intentionally preserved; refactoring this boundary is outside the lint slice.
885
+ ...(params.behaviour_prompts ??
886
+ params.behavior_prompts ??
887
+ params.behaviourPrompts ??
888
+ params.behaviorPrompts ??
889
+ []),
890
+ ]
891
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- BN4-C-P01: preserve defensive runtime boundary guard despite current static types.
892
+ .map((prompt) => prompt?.trim())
893
+ .filter((prompt) => Boolean(prompt))
894
+ .filter((prompt, index, array) => array.indexOf(prompt) === index);
895
+ const toolWhitelist = (params.tool_whitelist ?? params.toolWhitelist ?? [])
896
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- BN4-C-P01: preserve defensive runtime boundary guard despite current static types.
897
+ .map((tool) => tool?.trim())
898
+ .filter((tool) => Boolean(tool));
899
+ const outputSpec = params.output_spec ?? params.outputSpec ?? undefined;
900
+ const context = params.context ?? params.context_vars ?? params.contextVars ?? {};
901
+ return {
902
+ subagentName,
903
+ goalPrompt,
904
+ behaviourPrompts,
905
+ toolWhitelist: toolWhitelist.length > 0 ? toolWhitelist : undefined,
906
+ outputSpec,
907
+ context,
908
+ async: params.async ?? false,
909
+ };
910
+ }
911
+ ensureOrchestrator() {
912
+ if (this.dependencies.orchestratorFactory) {
913
+ return this.dependencies.orchestratorFactory();
914
+ }
915
+ const configWithManagers = this.config;
916
+ const profileManager = this.dependencies.profileManager ??
917
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- BN4-C-P01: preserve defensive runtime boundary guard despite current static types.
918
+ configWithManagers.getProfileManager?.();
919
+ const subagentManager = this.dependencies.subagentManager ??
920
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- BN4-C-P01: preserve defensive runtime boundary guard despite current static types.
921
+ configWithManagers.getSubagentManager?.();
922
+ if (!profileManager || !subagentManager) {
923
+ throw new Error('Task tool requires profile and subagent managers to be configured.');
924
+ }
925
+ return new SubagentOrchestrator({
926
+ subagentManager,
927
+ profileManager,
928
+ foregroundConfig: this.config,
929
+ });
930
+ }
931
+ }
932
+ //# sourceMappingURL=task.js.map