@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,650 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ /* eslint-disable complexity, sonarjs/cognitive-complexity, max-lines -- Phase 5: legacy core boundary retained while larger decomposition continues. */
7
+ /**
8
+ * @plan PLAN-20251028-STATELESS6.P08
9
+ * @requirement REQ-STAT6-001.1, REQ-STAT6-003.1
10
+ * @pseudocode agent-runtime-context.md lines 92-101
11
+ */
12
+ import { DebugLogger } from '@vybestack/llxprt-code-core/debug/DebugLogger.js';
13
+ import { GeminiEventType, Turn } from './turn.js';
14
+ import { createAbortError } from '@vybestack/llxprt-code-core/utils/delay.js';
15
+ import { nextStreamEventWithIdleTimeout, resolveStreamIdleTimeoutMs, } from '@vybestack/llxprt-code-core/utils/streamIdleTimeout.js';
16
+ import { StreamEventType, } from './chatSession.js';
17
+ import { filterHookRestrictedParts, filterHookRestrictedFunctionCalls, getHookRestrictedAllowedTools, getHookRestrictedFunctionCallsFromParts, isHookRestrictedToolCall, mergeHookRestrictedFunctionCalls, } from './hookToolRestrictions.js';
18
+ import { GemmaToolCallParser } from '@vybestack/llxprt-code-core/parsers/TextToolCallParser.js';
19
+ import { filterToolsAgainstRuntime, createToolExecutionConfig, createEmojiFilter, buildRuntimeFunctionDeclarations, getScopeLocalFuncDefs, createSchedulerConfig, createChatObject, } from './subagentRuntimeSetup.js';
20
+ import { isFatalToolError, buildToolUnavailableMessage, resolveToolName, finalizeOutput, handleEmitValueCall, buildPartsFromCompletedCalls, processFunctionCalls, buildTodoCompletionPrompt, } from './subagentToolProcessing.js';
21
+ import { checkTerminationConditions, filterTextWithEmoji, checkGoalCompletion, processNonInteractiveTextResponse, processInteractiveTextResponse, handleExecutionError, initInteractiveScheduler, } from './subagentExecution.js';
22
+ import { SubagentTerminateMode, defaultEnvironmentContextLoader, } from '@vybestack/llxprt-code-core/core/subagentTypes.js';
23
+ // Types, interfaces, enums, and ContextState are now in subagentTypes.ts
24
+ // Runtime setup helpers are now in subagentRuntimeSetup.ts
25
+ /**
26
+ * Represents the scope and execution environment for a subagent.
27
+ * This class orchestrates the subagent's lifecycle, managing its chat interactions,
28
+ * runtime context, and the collection of its outputs.
29
+ *
30
+ * @plan PLAN-20251028-STATELESS6.P08
31
+ * @requirement REQ-STAT6-001.1, REQ-STAT6-001.2, REQ-STAT6-003.1, REQ-STAT6-003.2
32
+ * @pseudocode agent-runtime-context.md line 93 (step 007.1)
33
+ */
34
+ export class SubAgentScope {
35
+ name;
36
+ runtimeContext;
37
+ modelConfig;
38
+ runConfig;
39
+ promptConfig;
40
+ contentGenerator;
41
+ toolExecutorContext;
42
+ environmentContextLoader;
43
+ config;
44
+ messageBus;
45
+ toolConfig;
46
+ outputConfig;
47
+ output = {
48
+ terminate_reason: SubagentTerminateMode.ERROR,
49
+ emitted_vars: {},
50
+ };
51
+ subagentId;
52
+ logger = new DebugLogger('llxprt:subagent');
53
+ textToolParser = new GemmaToolCallParser();
54
+ activeAbortController = null;
55
+ parentAbortSignal;
56
+ parentAbortCleanup;
57
+ /** Emoji filter instance for subagent output */
58
+ emojiFilter;
59
+ /** Optional callback for streaming text messages during execution */
60
+ onMessage;
61
+ timeoutHandle = null;
62
+ /** @plan PLAN-20251028-STATELESS6.P08, PLAN-20260303-MESSAGEBUS.P01 */
63
+ constructor(name, runtimeContext, modelConfig, runConfig, promptConfig, contentGenerator, toolExecutorContext, environmentContextLoader, config, messageBus, toolConfig, outputConfig, settingsSnapshot, parentAbortSignal) {
64
+ this.name = name;
65
+ this.runtimeContext = runtimeContext;
66
+ this.modelConfig = modelConfig;
67
+ this.runConfig = runConfig;
68
+ this.promptConfig = promptConfig;
69
+ this.contentGenerator = contentGenerator;
70
+ this.toolExecutorContext = toolExecutorContext;
71
+ this.environmentContextLoader = environmentContextLoader;
72
+ this.config = config;
73
+ this.messageBus = messageBus;
74
+ this.toolConfig = toolConfig;
75
+ this.outputConfig = outputConfig;
76
+ const randomPart = Math.random().toString(36).slice(2, 8);
77
+ this.subagentId = `${this.name}-${randomPart}`;
78
+ this.parentAbortSignal = parentAbortSignal;
79
+ // Initialize emoji filter based on subagent and foreground settings
80
+ this.emojiFilter = createEmojiFilter(settingsSnapshot);
81
+ }
82
+ /**
83
+ * Returns the unique agent identifier assigned to this subagent scope.
84
+ */
85
+ getAgentId() {
86
+ return this.subagentId;
87
+ }
88
+ /**
89
+ * Creates and validates a new SubAgentScope instance.
90
+ *
91
+ * @plan PLAN-20251028-STATELESS6.P08
92
+ * @requirement REQ-STAT6-001.1, REQ-STAT6-003.1, REQ-STAT6-003.2
93
+ * @plan PLAN-20260309-MESSAGEBUS-DI-REMEDIATION.P05
94
+ * @requirement REQ-D01-001.1
95
+ */
96
+ static async create(name, foregroundConfig, promptConfig, modelConfig, runConfig, toolConfig, outputConfig, overrides = {}, parentSignal) {
97
+ const runtimeBundle = overrides.runtimeBundle;
98
+ if (!runtimeBundle) {
99
+ throw new Error('SubAgentScope.create requires a runtime bundle after initialization.');
100
+ }
101
+ const toolsView =
102
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- Persisted subagent config and runtime tool payloads.
103
+ runtimeBundle.runtimeContext.tools ?? runtimeBundle.toolsView;
104
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- Persisted subagent config and runtime tool payloads.
105
+ if (toolsView == null) {
106
+ throw new Error('SubAgentScope.create requires a ToolRegistryView from the runtime bundle.');
107
+ }
108
+ const toolRegistry = overrides.toolRegistry ?? runtimeBundle.toolRegistry;
109
+ if (!toolRegistry) {
110
+ throw new Error('SubAgentScope.create requires a ToolRegistry in the runtime bundle or overrides.');
111
+ }
112
+ if (toolConfig) {
113
+ const filteredToolConfig = await filterToolsAgainstRuntime({
114
+ toolConfig,
115
+ toolsView,
116
+ });
117
+ // Use undefined if all tools were filtered out
118
+ toolConfig =
119
+ filteredToolConfig.tools.length > 0 ? filteredToolConfig : undefined;
120
+ }
121
+ const settingsSnapshot = overrides.settingsSnapshot ?? runtimeBundle.settingsSnapshot;
122
+ const toolExecutorContext = createToolExecutionConfig(runtimeBundle, toolRegistry, foregroundConfig, overrides.messageBus, settingsSnapshot, toolConfig);
123
+ const environmentContextLoader = overrides.environmentContextLoader ?? defaultEnvironmentContextLoader;
124
+ const runtimeContext = Object.freeze({
125
+ ...runtimeBundle.runtimeContext,
126
+ tools: toolsView,
127
+ });
128
+ return new SubAgentScope(name, runtimeContext, modelConfig, runConfig, promptConfig, runtimeBundle.contentGenerator, toolExecutorContext, environmentContextLoader, foregroundConfig, overrides.messageBus, toolConfig, outputConfig, settingsSnapshot, parentSignal);
129
+ }
130
+ bindParentSignal(abortController) {
131
+ if (!this.parentAbortSignal) {
132
+ return;
133
+ }
134
+ if (this.parentAbortCleanup) {
135
+ this.parentAbortCleanup();
136
+ }
137
+ const relayAbort = () => abortController.abort();
138
+ if (this.parentAbortSignal.aborted) {
139
+ relayAbort();
140
+ return;
141
+ }
142
+ this.parentAbortSignal.addEventListener('abort', relayAbort, {
143
+ once: true,
144
+ });
145
+ this.parentAbortCleanup = () => {
146
+ this.parentAbortSignal?.removeEventListener('abort', relayAbort);
147
+ };
148
+ }
149
+ armTimeout(abortController) {
150
+ if (!Number.isFinite(this.runConfig.max_time_minutes)) {
151
+ return;
152
+ }
153
+ const timeoutMs = this.runConfig.max_time_minutes * 60 * 1000;
154
+ if (timeoutMs <= 0) {
155
+ if (!abortController.signal.aborted) {
156
+ this.output.terminate_reason = SubagentTerminateMode.TIMEOUT;
157
+ abortController.abort(createAbortError());
158
+ }
159
+ return;
160
+ }
161
+ this.clearTimeoutHandle();
162
+ this.timeoutHandle = setTimeout(() => {
163
+ if (abortController.signal.aborted === true) {
164
+ return;
165
+ }
166
+ this.output.terminate_reason = SubagentTerminateMode.TIMEOUT;
167
+ abortController.abort(createAbortError());
168
+ }, timeoutMs);
169
+ }
170
+ clearTimeoutHandle() {
171
+ if (this.timeoutHandle) {
172
+ clearTimeout(this.timeoutHandle);
173
+ this.timeoutHandle = null;
174
+ }
175
+ }
176
+ async prepareRun(context) {
177
+ const chat = await createChatObject({
178
+ promptConfig: this.promptConfig,
179
+ modelConfig: this.modelConfig,
180
+ outputConfig: this.outputConfig,
181
+ toolConfig: this.toolConfig,
182
+ runtimeContext: this.runtimeContext,
183
+ contentGenerator: this.contentGenerator,
184
+ environmentContextLoader: this.environmentContextLoader,
185
+ foregroundConfig: this.config,
186
+ context,
187
+ });
188
+ if (!chat) {
189
+ this.output.terminate_reason = SubagentTerminateMode.ERROR;
190
+ return null;
191
+ }
192
+ const abortController = new AbortController();
193
+ this.activeAbortController = abortController;
194
+ this.bindParentSignal(abortController);
195
+ const functionDeclarations = buildRuntimeFunctionDeclarations(this.runtimeContext.tools, this.toolConfig);
196
+ if (this.outputConfig?.outputs) {
197
+ functionDeclarations.push(...getScopeLocalFuncDefs(this.outputConfig));
198
+ }
199
+ this.armTimeout(abortController);
200
+ return { chat, abortController, functionDeclarations };
201
+ }
202
+ buildInitialMessages(context) {
203
+ const behaviourPrompts = context.get('task_behaviour_prompts') ?? [];
204
+ const initialInstruction = behaviourPrompts.length > 0
205
+ ? behaviourPrompts.join('\n')
206
+ : 'Follow the task directives provided in the system prompt.';
207
+ return [
208
+ {
209
+ role: 'user',
210
+ parts: [{ text: initialInstruction }],
211
+ },
212
+ ];
213
+ }
214
+ /**
215
+ * Executes the subagent in interactive mode by routing tool calls through the
216
+ * shared CoreToolScheduler. Tests may supply a custom schedulerFactory to
217
+ * observe scheduling behaviour without touching the real scheduler.
218
+ */
219
+ async runInteractiveGoalCheckLoop(execCtx, startTime, turnCounter, currentTurn) {
220
+ const recheck = checkTerminationConditions(turnCounter, startTime, execCtx);
221
+ if (recheck.shouldStop)
222
+ return null;
223
+ const todoReminder = await buildTodoCompletionPrompt(this.runtimeContext, this.subagentId, this.logger);
224
+ return checkGoalCompletion(execCtx, todoReminder, currentTurn);
225
+ }
226
+ async runInteractive(context, options) {
227
+ const setup = await this.prepareRun(context);
228
+ if (!setup)
229
+ return;
230
+ const { chat, abortController } = setup;
231
+ let schedulerDispose = async () => { };
232
+ const execCtx = this.buildExecCtx();
233
+ const startTime = Date.now();
234
+ let turnCounter = 0;
235
+ let currentMessages = this.buildInitialMessages(context);
236
+ try {
237
+ const { scheduler, schedulerDispose: disposeScheduler } = await this.initScheduler(options);
238
+ schedulerDispose = disposeScheduler;
239
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition, sonarjs/too-many-break-or-continue-in-loop -- Persisted subagent config and runtime tool payloads.
240
+ while (true) {
241
+ const check = checkTerminationConditions(turnCounter, startTime, execCtx);
242
+ if (check.shouldStop)
243
+ break;
244
+ const { responseParts, textResponse, currentTurn } = await this.runInteractiveTurn(chat, currentMessages, abortController, turnCounter++, execCtx);
245
+ if (abortController.signal.aborted === true)
246
+ return;
247
+ processInteractiveTextResponse(textResponse, execCtx);
248
+ const toolMessages = await this.handleInteractiveToolCalls(responseParts, scheduler, abortController, execCtx);
249
+ if (toolMessages) {
250
+ currentMessages = toolMessages;
251
+ continue;
252
+ }
253
+ const nextMessages = await this.runInteractiveGoalCheckLoop(execCtx, startTime, turnCounter, currentTurn);
254
+ if (!nextMessages)
255
+ break;
256
+ currentMessages = nextMessages;
257
+ }
258
+ finalizeOutput(this.output);
259
+ }
260
+ catch (error) {
261
+ if (this.output.terminate_reason !== SubagentTerminateMode.TIMEOUT) {
262
+ handleExecutionError(error, execCtx);
263
+ }
264
+ finalizeOutput(this.output);
265
+ throw error;
266
+ }
267
+ finally {
268
+ await this.cleanupInteractive(schedulerDispose, abortController);
269
+ }
270
+ }
271
+ async initScheduler(options) {
272
+ return initInteractiveScheduler(options, {
273
+ schedulerConfig: createSchedulerConfig(this.toolExecutorContext, this.config, { interactive: true }),
274
+ onMessage: this.onMessage,
275
+ messageBus: this.messageBus,
276
+ subagentId: this.subagentId,
277
+ logger: this.logger,
278
+ });
279
+ }
280
+ async runInteractiveTurn(chat, currentMessages, abortController, turnIndex, execCtx) {
281
+ const currentTurn = turnIndex;
282
+ const promptId = `${this.runtimeContext.state.sessionId}#${this.subagentId}#${currentTurn}`;
283
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- Persisted subagent config and runtime tool payloads.
284
+ const providerName = this.runtimeContext.state.provider ?? 'backend';
285
+ const turn = new Turn(chat, promptId, this.subagentId, providerName);
286
+ const parts = currentMessages[0]?.parts ?? [];
287
+ let textResponse = '';
288
+ try {
289
+ const stream = turn.run(parts, abortController.signal);
290
+ for await (const event of stream) {
291
+ if (abortController.signal.aborted === true)
292
+ break;
293
+ if (event.type === GeminiEventType.Content && event.value) {
294
+ textResponse += event.value;
295
+ const filtered = filterTextWithEmoji(event.value, execCtx);
296
+ // eslint-disable-next-line sonarjs/nested-control-flow -- Existing structure is intentionally preserved; refactoring this boundary is outside the lint slice.
297
+ if (filtered.blocked) {
298
+ execCtx.output.terminate_reason = SubagentTerminateMode.ERROR;
299
+ throw new Error(filtered.error ?? 'Content blocked by emoji filter');
300
+ }
301
+ // eslint-disable-next-line sonarjs/nested-control-flow -- Existing structure is intentionally preserved; refactoring this boundary is outside the lint slice.
302
+ if (execCtx.onMessage && filtered.text) {
303
+ execCtx.onMessage(filtered.text);
304
+ }
305
+ }
306
+ else if (event.type === GeminiEventType.Error) {
307
+ const eventError = event.value?.error;
308
+ // eslint-disable-next-line sonarjs/nested-control-flow -- Existing structure is intentionally preserved; refactoring this boundary is outside the lint slice.
309
+ if (eventError != null) {
310
+ execCtx.output.terminate_reason = SubagentTerminateMode.ERROR;
311
+ throw new Error(eventError.message);
312
+ }
313
+ }
314
+ }
315
+ }
316
+ catch (error) {
317
+ if (abortController.signal.aborted === true) {
318
+ throw createAbortError();
319
+ }
320
+ throw error;
321
+ }
322
+ return {
323
+ responseParts: [...turn.pendingToolCalls],
324
+ textResponse,
325
+ currentTurn,
326
+ };
327
+ }
328
+ partitionInteractiveToolRequests(toolRequests) {
329
+ const manualParts = [];
330
+ const schedulerRequests = [];
331
+ for (const request of toolRequests) {
332
+ const hookRestrictedAllowedTools = request.hookRestrictedAllowedTools;
333
+ const functionCall = {
334
+ name: request.name,
335
+ args: request.args,
336
+ id: request.callId,
337
+ };
338
+ if (isHookRestrictedToolCall(functionCall, hookRestrictedAllowedTools)) {
339
+ continue;
340
+ }
341
+ if (request.name === 'self_emitvalue') {
342
+ manualParts.push(...handleEmitValueCall(request, {
343
+ output: this.output,
344
+ onMessage: this.onMessage,
345
+ subagentId: this.subagentId,
346
+ logger: this.logger,
347
+ }));
348
+ }
349
+ else {
350
+ schedulerRequests.push(request);
351
+ }
352
+ }
353
+ return { manualParts, schedulerRequests };
354
+ }
355
+ async handleInteractiveToolCalls(toolRequests, scheduler, abortController, execCtx) {
356
+ if (toolRequests.length === 0)
357
+ return null;
358
+ const { manualParts, schedulerRequests } = this.partitionInteractiveToolRequests(toolRequests);
359
+ let responseParts = [...manualParts];
360
+ if (schedulerRequests.length > 0) {
361
+ const completionPromise = scheduler.awaitCompletedCalls(abortController.signal);
362
+ // Prevent unhandled rejection if both schedule() and
363
+ // completionPromise reject on the same abort signal.
364
+ completionPromise.catch(() => { });
365
+ await scheduler.schedule(schedulerRequests, abortController.signal);
366
+ const completedCalls = await completionPromise;
367
+ responseParts = responseParts.concat(buildPartsFromCompletedCalls(completedCalls, {
368
+ onMessage: this.onMessage,
369
+ subagentId: this.subagentId,
370
+ logger: this.logger,
371
+ }));
372
+ const fatalCall = completedCalls.find((call) => call.status === 'error' && isFatalToolError(call.response.errorType));
373
+ if (fatalCall) {
374
+ const fatalMessage = buildToolUnavailableMessage(fatalCall.request.name, fatalCall.response.resultDisplay, fatalCall.response.error);
375
+ this.logger.warn(() => `Subagent ${this.subagentId} cannot use tool '${fatalCall.request.name}': ${fatalMessage}`);
376
+ responseParts.push({ text: fatalMessage });
377
+ execCtx.output.final_message = fatalMessage;
378
+ }
379
+ }
380
+ if (responseParts.length === 0) {
381
+ if (manualParts.length === 0 && schedulerRequests.length === 0) {
382
+ return null;
383
+ }
384
+ responseParts.push({
385
+ text: 'All tool calls failed. Please analyze the errors and try an alternative approach.',
386
+ });
387
+ }
388
+ return [{ role: 'user', parts: responseParts }];
389
+ }
390
+ async cleanupInteractive(schedulerDispose, _abortController) {
391
+ this.clearTimeoutHandle();
392
+ try {
393
+ await schedulerDispose();
394
+ }
395
+ catch (error) {
396
+ this.logger.warn(() => `Subagent ${this.subagentId} failed to dispose scheduler: ${error instanceof Error ? error.message : String(error)}`);
397
+ }
398
+ this.parentAbortCleanup?.();
399
+ this.parentAbortCleanup = undefined;
400
+ this.activeAbortController = null;
401
+ }
402
+ /**
403
+ * Runs the subagent in a non-interactive mode.
404
+ *
405
+ * @plan PLAN-20251028-STATELESS6.P08
406
+ * @requirement REQ-STAT6-001.1
407
+ */
408
+ async runNonInteractive(context) {
409
+ const setup = await this.prepareRun(context);
410
+ if (!setup)
411
+ return;
412
+ const { chat, abortController, functionDeclarations: toolsList } = setup;
413
+ this.logger.debug(() => {
414
+ const outputs = this.outputConfig
415
+ ? Object.keys(this.outputConfig.outputs).join(', ')
416
+ : 'none';
417
+ return `Subagent ${this.subagentId} (${this.name}) starting run with toolCount=${toolsList.length} requestedOutputs=${outputs} runConfig=${JSON.stringify(this.runConfig)}`;
418
+ });
419
+ const execCtx = this.buildExecCtx();
420
+ let currentMessages = this.buildInitialMessages(context);
421
+ const startTime = Date.now();
422
+ let turnCounter = 0;
423
+ try {
424
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition, sonarjs/too-many-break-or-continue-in-loop -- Persisted subagent config and runtime tool payloads.
425
+ while (true) {
426
+ const check = checkTerminationConditions(turnCounter, startTime, execCtx);
427
+ if (check.shouldStop)
428
+ break;
429
+ const currentTurn = turnCounter++;
430
+ const promptId = `${this.runtimeContext.state.sessionId}#${this.subagentId}#${currentTurn}`;
431
+ this.logger.debug(() => `Subagent ${this.subagentId} turn=${currentTurn} promptId=${promptId}`);
432
+ const { functionCalls } = await this.runNonInteractiveTurn(chat, currentMessages, toolsList, abortController, currentTurn, execCtx);
433
+ if (abortController.signal.aborted === true)
434
+ return;
435
+ // Post-send timeout recheck
436
+ const recheck = checkTerminationConditions(turnCounter, startTime, execCtx);
437
+ if (recheck.shouldStop)
438
+ break;
439
+ const nextMessages = await this.dispatchNonInteractiveTurnResult(functionCalls, abortController, promptId, currentTurn, execCtx);
440
+ if (!nextMessages)
441
+ break;
442
+ currentMessages = nextMessages;
443
+ }
444
+ finalizeOutput(this.output);
445
+ }
446
+ catch (error) {
447
+ if (this.output.terminate_reason !== SubagentTerminateMode.TIMEOUT) {
448
+ handleExecutionError(error, execCtx);
449
+ }
450
+ finalizeOutput(this.output);
451
+ throw error;
452
+ }
453
+ finally {
454
+ this.clearTimeoutHandle();
455
+ this.parentAbortCleanup?.();
456
+ this.parentAbortCleanup = undefined;
457
+ this.activeAbortController = null;
458
+ }
459
+ }
460
+ async runNonInteractiveTurn(chat, currentMessages, toolsList, abortController, currentTurn, execCtx) {
461
+ const messageParams = {
462
+ message: currentMessages[0]?.parts ?? [],
463
+ config: {
464
+ abortSignal: abortController.signal,
465
+ tools: [{ functionDeclarations: toolsList }],
466
+ },
467
+ };
468
+ const responseStream = await chat.sendMessageStream(messageParams, `${this.runtimeContext.state.sessionId}#${this.subagentId}#${currentTurn}`);
469
+ const { functionCalls: rawCalls, textResponse, parseableTextResponse, hookRestrictedAllowedTools, } = await this.consumeNonInteractiveStream(responseStream, abortController, currentTurn);
470
+ if (abortController.signal.aborted === true) {
471
+ return { functionCalls: [], textResponse: '' };
472
+ }
473
+ let functionCalls = rawCalls;
474
+ if (parseableTextResponse) {
475
+ const result = processNonInteractiveTextResponse(parseableTextResponse, functionCalls, execCtx, resolveToolName, hookRestrictedAllowedTools);
476
+ functionCalls = result.functionCalls;
477
+ }
478
+ return { functionCalls, textResponse };
479
+ }
480
+ async consumeNonInteractiveStream(responseStream, abortController, currentTurn) {
481
+ const timeoutController = new AbortController();
482
+ const timeoutSignal = timeoutController.signal;
483
+ const onAbort = () => timeoutController.abort();
484
+ abortController.signal.addEventListener('abort', onAbort, { once: true });
485
+ if (abortController.signal.aborted === true) {
486
+ onAbort();
487
+ abortController.signal.removeEventListener('abort', onAbort);
488
+ return {
489
+ functionCalls: [],
490
+ textResponse: '',
491
+ parseableTextResponse: '',
492
+ hookRestrictedAllowedTools: undefined,
493
+ };
494
+ }
495
+ const functionCalls = [];
496
+ let textResponse = '';
497
+ let parseableTextResponse = '';
498
+ let hookRestrictedAllowedTools;
499
+ const iterator = responseStream[Symbol.asyncIterator]();
500
+ const effectiveTimeoutMs = resolveStreamIdleTimeoutMs(this.config);
501
+ try {
502
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- Persisted subagent config and runtime tool payloads.
503
+ while (true) {
504
+ const result = await this.readNextNonInteractiveEvent(iterator, abortController, timeoutController, timeoutSignal, effectiveTimeoutMs);
505
+ if (result.done === true) {
506
+ break;
507
+ }
508
+ const resp = result.value;
509
+ const isRuntimeAborted = Boolean(abortController.signal.aborted);
510
+ if (isRuntimeAborted) {
511
+ return {
512
+ functionCalls: [],
513
+ textResponse: '',
514
+ parseableTextResponse: '',
515
+ hookRestrictedAllowedTools,
516
+ };
517
+ }
518
+ if (resp.type === StreamEventType.CHUNK) {
519
+ const chunkResult = this.collectNonInteractiveChunk(resp, functionCalls, currentTurn);
520
+ hookRestrictedAllowedTools =
521
+ chunkResult.hookRestrictedAllowedTools ??
522
+ hookRestrictedAllowedTools;
523
+ textResponse += chunkResult.text;
524
+ parseableTextResponse += chunkResult.text;
525
+ }
526
+ }
527
+ }
528
+ finally {
529
+ iterator.return?.(undefined).catch(() => { });
530
+ timeoutController.abort();
531
+ abortController.signal.removeEventListener('abort', onAbort);
532
+ }
533
+ return {
534
+ functionCalls,
535
+ textResponse,
536
+ parseableTextResponse,
537
+ hookRestrictedAllowedTools,
538
+ };
539
+ }
540
+ async readNextNonInteractiveEvent(iterator, abortController, timeoutController, timeoutSignal, effectiveTimeoutMs) {
541
+ if (effectiveTimeoutMs > 0) {
542
+ return nextStreamEventWithIdleTimeout({
543
+ iterator,
544
+ timeoutMs: effectiveTimeoutMs,
545
+ signal: timeoutSignal,
546
+ onTimeout: () => {
547
+ if (abortController.signal.aborted === true) {
548
+ return;
549
+ }
550
+ this.output.terminate_reason = SubagentTerminateMode.TIMEOUT;
551
+ timeoutController.abort();
552
+ abortController.abort(createAbortError());
553
+ },
554
+ createTimeoutError: () => createAbortError(),
555
+ });
556
+ }
557
+ return iterator.next();
558
+ }
559
+ collectNonInteractiveChunk(resp, functionCalls, currentTurn) {
560
+ const allowedTools = getHookRestrictedAllowedTools(resp.value);
561
+ const parts = resp.value.candidates?.[0]?.content?.parts ?? [];
562
+ const partCalls = getHookRestrictedFunctionCallsFromParts(parts, allowedTools);
563
+ const topLevelCalls = filterHookRestrictedFunctionCalls(resp.value.functionCalls ?? [], allowedTools);
564
+ const chunkCalls = mergeHookRestrictedFunctionCalls(partCalls, topLevelCalls);
565
+ if (chunkCalls.length > 0) {
566
+ functionCalls.push(...chunkCalls);
567
+ this.logger.debug(() => `Subagent ${this.subagentId} received ${chunkCalls.length} function calls on turn ${currentTurn}`);
568
+ }
569
+ if (allowedTools === undefined) {
570
+ return {
571
+ text: resp.value.text ?? '',
572
+ hookRestrictedAllowedTools: undefined,
573
+ };
574
+ }
575
+ const filteredParts = filterHookRestrictedParts(parts, allowedTools);
576
+ const filteredText = filteredParts
577
+ .map((part) => part.text)
578
+ .filter((text) => typeof text === 'string')
579
+ .join('');
580
+ return { text: filteredText, hookRestrictedAllowedTools: allowedTools };
581
+ }
582
+ async dispatchNonInteractiveTurnResult(functionCalls, abortController, promptId, currentTurn, execCtx) {
583
+ if (functionCalls.length > 0) {
584
+ return processFunctionCalls(functionCalls, abortController, promptId, {
585
+ output: this.output,
586
+ subagentId: this.subagentId,
587
+ logger: this.logger,
588
+ toolExecutorContext: this.toolExecutorContext,
589
+ config: this.config,
590
+ messageBus: this.messageBus,
591
+ });
592
+ }
593
+ const todoReminder = await buildTodoCompletionPrompt(this.runtimeContext, this.subagentId, this.logger);
594
+ return checkGoalCompletion(execCtx, todoReminder, currentTurn);
595
+ }
596
+ buildExecCtx() {
597
+ return {
598
+ output: this.output,
599
+ subagentId: this.subagentId,
600
+ runConfig: this.runConfig,
601
+ outputConfig: this.outputConfig,
602
+ emojiFilter: this.emojiFilter,
603
+ textToolParser: this.textToolParser,
604
+ toolsView: this.runtimeContext.tools,
605
+ logger: this.logger,
606
+ onMessage: this.onMessage,
607
+ };
608
+ }
609
+ cancel(reason) {
610
+ if (this.activeAbortController !== null &&
611
+ this.activeAbortController.signal.aborted === true) {
612
+ return;
613
+ }
614
+ if (this.activeAbortController === null) {
615
+ return;
616
+ }
617
+ this.logger.warn(() => {
618
+ const suffix = reason ? `: ${reason}` : '';
619
+ return `Subagent ${this.subagentId} cancellation requested${suffix}`;
620
+ });
621
+ this.activeAbortController.abort();
622
+ }
623
+ /**
624
+ * Dispose of the subagent scope, cleaning up resources and references.
625
+ * Call this when the subagent is no longer needed to prevent memory leaks.
626
+ *
627
+ * Cleanup performed:
628
+ * - Aborts any active operations
629
+ * - Removes parent abort signal event listeners
630
+ * - Nullifies references to allow garbage collection
631
+ *
632
+ * Safe to call multiple times.
633
+ */
634
+ dispose() {
635
+ // 1. Cancel any active operations
636
+ if (this.activeAbortController &&
637
+ !this.activeAbortController.signal.aborted) {
638
+ this.logger.debug(() => `Disposing subagent ${this.subagentId}, aborting active operations`);
639
+ this.activeAbortController.abort();
640
+ }
641
+ this.activeAbortController = null;
642
+ // 2. Clean up parent abort signal event listeners
643
+ if (this.parentAbortCleanup) {
644
+ this.parentAbortCleanup();
645
+ this.parentAbortCleanup = undefined;
646
+ }
647
+ this.logger.debug(() => `Subagent ${this.subagentId} disposed`);
648
+ }
649
+ }
650
+ //# sourceMappingURL=subagent.js.map