@qduc/term2 0.13.0 → 0.14.0
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.
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +1 -0
- package/dist/app.js.map +1 -1
- package/dist/commands/compact-command.d.ts +6 -0
- package/dist/commands/compact-command.d.ts.map +1 -0
- package/dist/commands/compact-command.js +12 -0
- package/dist/commands/compact-command.js.map +1 -0
- package/dist/components/input/ApplicationInputSurface.d.ts.map +1 -1
- package/dist/components/input/ApplicationInputSurface.js +2 -0
- package/dist/components/input/ApplicationInputSurface.js.map +1 -1
- package/dist/components/input/MentorPoolMenuSession.d.ts +9 -0
- package/dist/components/input/MentorPoolMenuSession.d.ts.map +1 -0
- package/dist/components/input/MentorPoolMenuSession.js +131 -0
- package/dist/components/input/MentorPoolMenuSession.js.map +1 -0
- package/dist/components/input/MenuSurface.d.ts.map +1 -1
- package/dist/components/input/MenuSurface.js +16 -3
- package/dist/components/input/MenuSurface.js.map +1 -1
- package/dist/components/input/SettingsMenuSession.d.ts.map +1 -1
- package/dist/components/input/SettingsMenuSession.js +35 -22
- package/dist/components/input/SettingsMenuSession.js.map +1 -1
- package/dist/components/input/menu-controller.d.ts.map +1 -1
- package/dist/components/input/menu-controller.js +18 -1
- package/dist/components/input/menu-controller.js.map +1 -1
- package/dist/components/input/menu-registry.d.ts.map +1 -1
- package/dist/components/input/menu-registry.js +2 -0
- package/dist/components/input/menu-registry.js.map +1 -1
- package/dist/components/input/menu-types.d.ts +5 -0
- package/dist/components/input/menu-types.d.ts.map +1 -1
- package/dist/components/input/triggers.d.ts.map +1 -1
- package/dist/components/input/triggers.js +33 -0
- package/dist/components/input/triggers.js.map +1 -1
- package/dist/components/layout/BackgroundTasksPanel.d.ts +2 -0
- package/dist/components/layout/BackgroundTasksPanel.d.ts.map +1 -1
- package/dist/components/layout/BackgroundTasksPanel.js +25 -4
- package/dist/components/layout/BackgroundTasksPanel.js.map +1 -1
- package/dist/components/menu/CopyMenu.d.ts +1 -0
- package/dist/components/menu/CopyMenu.d.ts.map +1 -1
- package/dist/components/menu/CopyMenu.js +15 -2
- package/dist/components/menu/CopyMenu.js.map +1 -1
- package/dist/components/menu/MentorPoolSelectionMenu.d.ts +13 -0
- package/dist/components/menu/MentorPoolSelectionMenu.d.ts.map +1 -0
- package/dist/components/menu/MentorPoolSelectionMenu.js +98 -0
- package/dist/components/menu/MentorPoolSelectionMenu.js.map +1 -0
- package/dist/components/menu/ModelSelectionMenu.d.ts +1 -0
- package/dist/components/menu/ModelSelectionMenu.d.ts.map +1 -1
- package/dist/components/menu/ModelSelectionMenu.js +4 -2
- package/dist/components/menu/ModelSelectionMenu.js.map +1 -1
- package/dist/components/menu/SlashCommandMenu.d.ts.map +1 -1
- package/dist/components/menu/SlashCommandMenu.js +12 -9
- package/dist/components/menu/SlashCommandMenu.js.map +1 -1
- package/dist/components/message/SubagentActivityMessage.d.ts +1 -0
- package/dist/components/message/SubagentActivityMessage.d.ts.map +1 -1
- package/dist/components/message/SubagentActivityMessage.js +14 -1
- package/dist/components/message/SubagentActivityMessage.js.map +1 -1
- package/dist/context/InputContext.d.ts +1 -1
- package/dist/context/InputContext.d.ts.map +1 -1
- package/dist/context/InputContext.js +2 -0
- package/dist/context/InputContext.js.map +1 -1
- package/dist/contracts/application-stream.d.ts +7 -0
- package/dist/contracts/application-stream.d.ts.map +1 -1
- package/dist/contracts/provider-input.d.ts +15 -0
- package/dist/contracts/provider-input.d.ts.map +1 -1
- package/dist/contracts/provider-input.js +6 -1
- package/dist/contracts/provider-input.js.map +1 -1
- package/dist/hooks/settings-completion-config.d.ts.map +1 -1
- package/dist/hooks/settings-completion-config.js +10 -2
- package/dist/hooks/settings-completion-config.js.map +1 -1
- package/dist/hooks/use-app-commands.d.ts +2 -1
- package/dist/hooks/use-app-commands.d.ts.map +1 -1
- package/dist/hooks/use-app-commands.js +4 -1
- package/dist/hooks/use-app-commands.js.map +1 -1
- package/dist/hooks/use-conversation.d.ts.map +1 -1
- package/dist/hooks/use-conversation.js +9 -2
- package/dist/hooks/use-conversation.js.map +1 -1
- package/dist/hooks/use-mentor-pool-selection.d.ts +103 -0
- package/dist/hooks/use-mentor-pool-selection.d.ts.map +1 -0
- package/dist/hooks/use-mentor-pool-selection.js +642 -0
- package/dist/hooks/use-mentor-pool-selection.js.map +1 -0
- package/dist/lib/agent-chat-service.d.ts +3 -6
- package/dist/lib/agent-chat-service.d.ts.map +1 -1
- package/dist/lib/agent-chat-service.js +11 -1
- package/dist/lib/agent-chat-service.js.map +1 -1
- package/dist/lib/agent-client.d.ts +13 -0
- package/dist/lib/agent-client.d.ts.map +1 -1
- package/dist/lib/agent-client.js +115 -0
- package/dist/lib/agent-client.js.map +1 -1
- package/dist/lib/agent-configuration.d.ts.map +1 -1
- package/dist/lib/agent-configuration.js +2 -0
- package/dist/lib/agent-configuration.js.map +1 -1
- package/dist/lib/agent-factory.d.ts.map +1 -1
- package/dist/lib/agent-factory.js +9 -1
- package/dist/lib/agent-factory.js.map +1 -1
- package/dist/prompts/context-compaction.d.ts +4 -0
- package/dist/prompts/context-compaction.d.ts.map +1 -0
- package/dist/prompts/context-compaction.js +18 -0
- package/dist/prompts/context-compaction.js.map +1 -0
- package/dist/prompts/context-compaction.ts +23 -0
- package/dist/prompts/explorer-delegation-contract.test.ts +22 -0
- package/dist/prompts/gpt-5.6.md +1 -1
- package/dist/prompts/kimi.md +1 -1
- package/dist/prompts/plan-mode-info.md +2 -2
- package/dist/prompts/subagent-delegation.d.ts.map +1 -1
- package/dist/prompts/subagent-delegation.js +4 -2
- package/dist/prompts/subagent-delegation.js.map +1 -1
- package/dist/prompts/subagent-delegation.test.ts +10 -0
- package/dist/prompts/subagent-delegation.ts +4 -2
- package/dist/prompts/subagents/explorer.md +10 -5
- package/dist/prompts/subagents/librarian.md +1 -1
- package/dist/prompts/subagents/worker.md +1 -1
- package/dist/providers/aborted-stream-recorder.d.ts +20 -0
- package/dist/providers/aborted-stream-recorder.d.ts.map +1 -0
- package/dist/providers/aborted-stream-recorder.js +63 -0
- package/dist/providers/aborted-stream-recorder.js.map +1 -0
- package/dist/providers/codex-responses-model.d.ts +7 -2
- package/dist/providers/codex-responses-model.d.ts.map +1 -1
- package/dist/providers/codex-responses-model.js +72 -41
- package/dist/providers/codex-responses-model.js.map +1 -1
- package/dist/providers/codex.provider.d.ts +3 -2
- package/dist/providers/codex.provider.d.ts.map +1 -1
- package/dist/providers/codex.provider.js +24 -11
- package/dist/providers/codex.provider.js.map +1 -1
- package/dist/providers/model-catalog/catalog.generated.d.ts +406 -166
- package/dist/providers/model-catalog/catalog.generated.d.ts.map +1 -1
- package/dist/providers/model-catalog/catalog.generated.js +406 -166
- package/dist/providers/model-catalog/catalog.generated.js.map +1 -1
- package/dist/providers/openai-responses-model.d.ts +9 -1
- package/dist/providers/openai-responses-model.d.ts.map +1 -1
- package/dist/providers/openai-responses-model.js +34 -12
- package/dist/providers/openai-responses-model.js.map +1 -1
- package/dist/scripts/eval-context-compaction/runner.d.ts +2 -0
- package/dist/scripts/eval-context-compaction/runner.d.ts.map +1 -0
- package/dist/scripts/eval-context-compaction/runner.js +159 -0
- package/dist/scripts/eval-context-compaction/runner.js.map +1 -0
- package/dist/services/agent-runtime/application-run-loop.d.ts +30 -0
- package/dist/services/agent-runtime/application-run-loop.d.ts.map +1 -1
- package/dist/services/agent-runtime/application-run-loop.js +221 -99
- package/dist/services/agent-runtime/application-run-loop.js.map +1 -1
- package/dist/services/agent-runtime/context-compaction/index.d.ts +56 -0
- package/dist/services/agent-runtime/context-compaction/index.d.ts.map +1 -0
- package/dist/services/agent-runtime/context-compaction/index.js +131 -0
- package/dist/services/agent-runtime/context-compaction/index.js.map +1 -0
- package/dist/services/agent-runtime/context-compaction/local-context-compactor.d.ts +76 -0
- package/dist/services/agent-runtime/context-compaction/local-context-compactor.d.ts.map +1 -0
- package/dist/services/agent-runtime/context-compaction/local-context-compactor.js +146 -0
- package/dist/services/agent-runtime/context-compaction/local-context-compactor.js.map +1 -0
- package/dist/services/agent-runtime/generation-guard.d.ts +15 -1
- package/dist/services/agent-runtime/generation-guard.d.ts.map +1 -1
- package/dist/services/agent-runtime/generation-guard.js +34 -2
- package/dist/services/agent-runtime/generation-guard.js.map +1 -1
- package/dist/services/conversation/conversation-events.d.ts +3 -0
- package/dist/services/conversation/conversation-events.d.ts.map +1 -1
- package/dist/services/conversation/conversation-message-projection.d.ts.map +1 -1
- package/dist/services/conversation/conversation-message-projection.js +7 -3
- package/dist/services/conversation/conversation-message-projection.js.map +1 -1
- package/dist/services/conversation/conversation-service.d.ts +1 -0
- package/dist/services/conversation/conversation-service.d.ts.map +1 -1
- package/dist/services/conversation/conversation-service.js +74 -0
- package/dist/services/conversation/conversation-service.js.map +1 -1
- package/dist/services/conversation/conversation-state-projector.d.ts +7 -1
- package/dist/services/conversation/conversation-state-projector.d.ts.map +1 -1
- package/dist/services/conversation/conversation-state-projector.js +20 -3
- package/dist/services/conversation/conversation-state-projector.js.map +1 -1
- package/dist/services/conversation/conversation-state-schema.d.ts +8 -4
- package/dist/services/conversation/conversation-state-schema.d.ts.map +1 -1
- package/dist/services/conversation/conversation-state-schema.js +24 -2
- package/dist/services/conversation/conversation-state-schema.js.map +1 -1
- package/dist/services/conversation/conversation-store.d.ts +2 -0
- package/dist/services/conversation/conversation-store.d.ts.map +1 -1
- package/dist/services/conversation/conversation-store.js +8 -0
- package/dist/services/conversation/conversation-store.js.map +1 -1
- package/dist/services/conversation-agent-client.d.ts +9 -0
- package/dist/services/conversation-agent-client.d.ts.map +1 -1
- package/dist/services/logging/conversation-log-events.d.ts +1 -1
- package/dist/services/logging/conversation-log-events.d.ts.map +1 -1
- package/dist/services/logging/provider-traffic.d.ts +1 -1
- package/dist/services/logging/provider-traffic.d.ts.map +1 -1
- package/dist/services/logging/provider-traffic.js +10 -3
- package/dist/services/logging/provider-traffic.js.map +1 -1
- package/dist/services/retry/recovery-executor.d.ts.map +1 -1
- package/dist/services/retry/recovery-executor.js +8 -9
- package/dist/services/retry/recovery-executor.js.map +1 -1
- package/dist/services/service-interfaces.d.ts +15 -0
- package/dist/services/service-interfaces.d.ts.map +1 -1
- package/dist/services/session/session-composition.d.ts +7 -0
- package/dist/services/session/session-composition.d.ts.map +1 -1
- package/dist/services/session/session-composition.js +79 -0
- package/dist/services/session/session-composition.js.map +1 -1
- package/dist/services/session/session-manager.d.ts +3 -0
- package/dist/services/session/session-manager.d.ts.map +1 -1
- package/dist/services/session/session-manager.js +6 -0
- package/dist/services/session/session-manager.js.map +1 -1
- package/dist/services/session/session-stream-processor.d.ts.map +1 -1
- package/dist/services/session/session-stream-processor.js +3 -1
- package/dist/services/session/session-stream-processor.js.map +1 -1
- package/dist/services/session/session-tool-tracker.d.ts +17 -6
- package/dist/services/session/session-tool-tracker.d.ts.map +1 -1
- package/dist/services/session/session-tool-tracker.js +19 -7
- package/dist/services/session/session-tool-tracker.js.map +1 -1
- package/dist/services/settings/settings-schema.d.ts +52 -0
- package/dist/services/settings/settings-schema.d.ts.map +1 -1
- package/dist/services/settings/settings-schema.js +31 -2
- package/dist/services/settings/settings-schema.js.map +1 -1
- package/dist/services/settings/settings-sources.d.ts.map +1 -1
- package/dist/services/settings/settings-sources.js +2 -0
- package/dist/services/settings/settings-sources.js.map +1 -1
- package/dist/services/stream-event-processor.d.ts.map +1 -1
- package/dist/services/stream-event-processor.js +27 -1
- package/dist/services/stream-event-processor.js.map +1 -1
- package/dist/services/subagents/execution-runner.d.ts.map +1 -1
- package/dist/services/subagents/execution-runner.js +26 -1
- package/dist/services/subagents/execution-runner.js.map +1 -1
- package/dist/services/subagents/mentor-runner.d.ts.map +1 -1
- package/dist/services/subagents/mentor-runner.js +111 -13
- package/dist/services/subagents/mentor-runner.js.map +1 -1
- package/dist/services/subagents/nested-runner.d.ts.map +1 -1
- package/dist/services/subagents/nested-runner.js +54 -25
- package/dist/services/subagents/nested-runner.js.map +1 -1
- package/dist/services/subagents/role-loader.d.ts +6 -0
- package/dist/services/subagents/role-loader.d.ts.map +1 -1
- package/dist/services/subagents/role-loader.js +6 -1
- package/dist/services/subagents/role-loader.js.map +1 -1
- package/dist/services/subagents/utils.d.ts +17 -0
- package/dist/services/subagents/utils.d.ts.map +1 -1
- package/dist/services/subagents/utils.js +36 -0
- package/dist/services/subagents/utils.js.map +1 -1
- package/dist/services/tool-execution-ledger.d.ts +26 -1
- package/dist/services/tool-execution-ledger.d.ts.map +1 -1
- package/dist/services/tool-execution-ledger.js +103 -33
- package/dist/services/tool-execution-ledger.js.map +1 -1
- package/dist/tools/agent/activate-skill.d.ts.map +1 -1
- package/dist/tools/agent/activate-skill.js +1 -0
- package/dist/tools/agent/activate-skill.js.map +1 -1
- package/dist/tools/agent/ask-mentor.d.ts.map +1 -1
- package/dist/tools/agent/ask-mentor.js +5 -1
- package/dist/tools/agent/ask-mentor.js.map +1 -1
- package/dist/tools/agent/run-subagent-async.d.ts.map +1 -1
- package/dist/tools/agent/run-subagent-async.js +2 -0
- package/dist/tools/agent/run-subagent-async.js.map +1 -1
- package/dist/tools/agent/run-subagent.d.ts.map +1 -1
- package/dist/tools/agent/run-subagent.js +15 -6
- package/dist/tools/agent/run-subagent.js.map +1 -1
- package/dist/tools/file/apply-patch.d.ts.map +1 -1
- package/dist/tools/file/apply-patch.js +6 -3
- package/dist/tools/file/apply-patch.js.map +1 -1
- package/dist/tools/file/code-context.d.ts.map +1 -1
- package/dist/tools/file/code-context.js +2 -0
- package/dist/tools/file/code-context.js.map +1 -1
- package/dist/tools/file/glob.d.ts.map +1 -1
- package/dist/tools/file/glob.js +1 -0
- package/dist/tools/file/glob.js.map +1 -1
- package/dist/tools/file/grep.d.ts.map +1 -1
- package/dist/tools/file/grep.js +1 -0
- package/dist/tools/file/grep.js.map +1 -1
- package/dist/tools/file/read-file.d.ts +2 -0
- package/dist/tools/file/read-file.d.ts.map +1 -1
- package/dist/tools/file/read-file.js +36 -15
- package/dist/tools/file/read-file.js.map +1 -1
- package/dist/tools/system/shell.d.ts.map +1 -1
- package/dist/tools/system/shell.js +1 -0
- package/dist/tools/system/shell.js.map +1 -1
- package/dist/tools/types.d.ts +5 -0
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/tools/types.js.map +1 -1
- package/dist/tools/web/web-fetch.d.ts.map +1 -1
- package/dist/tools/web/web-fetch.js +10 -12
- package/dist/tools/web/web-fetch.js.map +1 -1
- package/dist/tools/web/web-search.d.ts.map +1 -1
- package/dist/tools/web/web-search.js +1 -0
- package/dist/tools/web/web-search.js.map +1 -1
- package/dist/utils/conversation/conversation-event-handler.d.ts.map +1 -1
- package/dist/utils/conversation/conversation-event-handler.js +13 -1
- package/dist/utils/conversation/conversation-event-handler.js.map +1 -1
- package/dist/utils/output/bound-tool-result.d.ts +42 -0
- package/dist/utils/output/bound-tool-result.d.ts.map +1 -0
- package/dist/utils/output/bound-tool-result.js +81 -0
- package/dist/utils/output/bound-tool-result.js.map +1 -0
- package/dist/utils/settings-command.d.ts.map +1 -1
- package/dist/utils/settings-command.js +17 -2
- package/dist/utils/settings-command.js.map +1 -1
- package/dist/utils/shell/shell-output.d.ts +11 -0
- package/dist/utils/shell/shell-output.d.ts.map +1 -1
- package/dist/utils/shell/shell-output.js +18 -8
- package/dist/utils/shell/shell-output.js.map +1 -1
- package/package.json +1 -1
|
@@ -65,6 +65,8 @@ class EventQueue {
|
|
|
65
65
|
* turns and silently drop later turns' records.
|
|
66
66
|
*/
|
|
67
67
|
let nextCostRequestSeq = 0;
|
|
68
|
+
let nextToolBatchSeq = 0;
|
|
69
|
+
const DEFAULT_MAX_PARALLEL_TOOL_CALLS = 4;
|
|
68
70
|
/**
|
|
69
71
|
* Small provider-neutral agent loop. It owns model-turn sequencing and tool
|
|
70
72
|
* execution; providers only implement `StreamedModelTurn`.
|
|
@@ -72,8 +74,10 @@ let nextCostRequestSeq = 0;
|
|
|
72
74
|
export class ApplicationRunLoop {
|
|
73
75
|
#deps;
|
|
74
76
|
#contextCompactionSessionState;
|
|
77
|
+
#maxParallelToolCalls;
|
|
75
78
|
#activeAbortController = null;
|
|
76
79
|
#runInFlight = false;
|
|
80
|
+
#segmentGeneration = 0;
|
|
77
81
|
/**
|
|
78
82
|
* Set when a segment ends holding approvals, meaning the turn is pausing
|
|
79
83
|
* rather than finishing. Injections offered during the pause wait here for
|
|
@@ -96,6 +100,7 @@ export class ApplicationRunLoop {
|
|
|
96
100
|
constructor(deps) {
|
|
97
101
|
this.#deps = deps;
|
|
98
102
|
this.#contextCompactionSessionState = deps.contextCompactionSessionState ?? { disabled: false };
|
|
103
|
+
this.#maxParallelToolCalls = Math.max(1, Math.floor(deps.maxParallelToolCalls ?? DEFAULT_MAX_PARALLEL_TOOL_CALLS));
|
|
99
104
|
}
|
|
100
105
|
/**
|
|
101
106
|
* Declare a turn open, making it steerable before its first run exists.
|
|
@@ -267,6 +272,7 @@ export class ApplicationRunLoop {
|
|
|
267
272
|
approvals: options.approvals ?? new ApprovalLedger(),
|
|
268
273
|
turnCount: 0,
|
|
269
274
|
maxTurns: options.maxTurns,
|
|
275
|
+
automaticCompactionsThisRun: 0,
|
|
270
276
|
};
|
|
271
277
|
state.approve = (interruption) => {
|
|
272
278
|
state.approvalDecision = 'approved';
|
|
@@ -346,6 +352,7 @@ export class ApplicationRunLoop {
|
|
|
346
352
|
}
|
|
347
353
|
#run(state, options) {
|
|
348
354
|
const queue = new EventQueue();
|
|
355
|
+
const segmentGeneration = ++this.#segmentGeneration;
|
|
349
356
|
this.abortSegment();
|
|
350
357
|
const controller = new AbortController();
|
|
351
358
|
this.#activeAbortController = controller;
|
|
@@ -397,6 +404,8 @@ export class ApplicationRunLoop {
|
|
|
397
404
|
throw error;
|
|
398
405
|
})
|
|
399
406
|
.finally(() => {
|
|
407
|
+
if (segmentGeneration !== this.#segmentGeneration)
|
|
408
|
+
return;
|
|
400
409
|
if (this.#activeAbortController === controller)
|
|
401
410
|
this.#activeAbortController = null;
|
|
402
411
|
this.#runInFlight = false;
|
|
@@ -445,27 +454,20 @@ export class ApplicationRunLoop {
|
|
|
445
454
|
else {
|
|
446
455
|
state.approvals.rejectTool({ toolName: pending.toolName, callId: pending.callId }, { message: state.approvalMessage });
|
|
447
456
|
}
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
const result = typeof rawResult === 'string' ? rawResult : JSON.stringify(rawResult);
|
|
452
|
-
const resultItem = {
|
|
453
|
-
type: 'function_call_result',
|
|
454
|
-
callId: pending.callId,
|
|
455
|
-
name: pending.toolName,
|
|
456
|
-
output: result,
|
|
457
|
-
};
|
|
458
|
-
state.history.push(resultItem);
|
|
459
|
-
state.input.push({ type: 'tool_result', id: pending.callId, output: result });
|
|
460
|
-
outputPush(stream, queue, { type: 'item', item: resultItem });
|
|
457
|
+
pending.plan.status = 'ready';
|
|
458
|
+
if (!approved)
|
|
459
|
+
pending.plan.output = state.approvalMessage ?? 'rejected';
|
|
461
460
|
state.pendingApprovals.splice(pendingIndex, 1);
|
|
462
461
|
state.pendingApproval = state.pendingApprovals[0];
|
|
463
462
|
state.approvalDecision = undefined;
|
|
464
463
|
state.approvalDecisionCallId = undefined;
|
|
465
464
|
state.approvalMessage = undefined;
|
|
465
|
+
await this.#settleToolPlan(state, stream, queue, toolContext);
|
|
466
466
|
stream.interruptions = state.pendingApprovals.map((item) => item.interruption);
|
|
467
|
-
if (state.pendingApprovals.length > 0)
|
|
467
|
+
if (state.pendingApprovals.length > 0) {
|
|
468
|
+
this.#turnPaused = true;
|
|
468
469
|
return finish(stream, state, queue);
|
|
470
|
+
}
|
|
469
471
|
// Nothing is outstanding, so this segment is terminal — see
|
|
470
472
|
// `stopAfterApprovalResolution`. Stopping here rather than at the
|
|
471
473
|
// request boundary below is what keeps the just-recorded tool result
|
|
@@ -477,12 +479,52 @@ export class ApplicationRunLoop {
|
|
|
477
479
|
// history, and the next request has not been built. A user message
|
|
478
480
|
// admitted here reaches the model in sequence, mid-turn.
|
|
479
481
|
this.#admitPendingSteers(state, stream, queue);
|
|
482
|
+
if (options.boundaryCompaction) {
|
|
483
|
+
const compactionStartedAt = Date.now();
|
|
484
|
+
const compaction = await options.boundaryCompaction.compact({
|
|
485
|
+
history: state.history,
|
|
486
|
+
automaticCompactionsThisRun: state.automaticCompactionsThisRun ?? 0,
|
|
487
|
+
signal: options.signal,
|
|
488
|
+
onStarted: (provider) => outputPush(stream, queue, { type: 'context_compaction_started', provider, strategy: 'local' }),
|
|
489
|
+
});
|
|
490
|
+
if (compaction.kind === 'compacted') {
|
|
491
|
+
state.history.splice(0, state.history.length, ...compaction.history);
|
|
492
|
+
state.input.splice(0, state.input.length, ...normalizeApplicationInput(compaction.modelInput));
|
|
493
|
+
state.responseId = undefined;
|
|
494
|
+
state.responseProviderId = undefined;
|
|
495
|
+
if (compaction.costRecords?.length) {
|
|
496
|
+
state.costRecords ??= [];
|
|
497
|
+
state.costRecords.push(...compaction.costRecords);
|
|
498
|
+
}
|
|
499
|
+
state.automaticCompactionsThisRun = (state.automaticCompactionsThisRun ?? 0) + 1;
|
|
500
|
+
outputPush(stream, queue, {
|
|
501
|
+
type: 'context_compaction_completed',
|
|
502
|
+
provider: state.currentProviderId ?? 'unknown',
|
|
503
|
+
strategy: 'local',
|
|
504
|
+
durationMs: Math.max(0, Date.now() - compactionStartedAt),
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
else if (compaction.kind === 'failed') {
|
|
508
|
+
outputPush(stream, queue, {
|
|
509
|
+
type: 'context_compaction_failed',
|
|
510
|
+
provider: compaction.provider,
|
|
511
|
+
strategy: 'local',
|
|
512
|
+
durationMs: Math.max(0, Date.now() - compactionStartedAt),
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
// Summarization is an asynchronous part of this same request
|
|
516
|
+
// boundary. Admit anything that arrived while it was in flight after
|
|
517
|
+
// applying a replacement, so the steer cannot be overwritten by the
|
|
518
|
+
// checkpoint or miss a terminal model request.
|
|
519
|
+
this.#admitPendingSteers(state, stream, queue);
|
|
520
|
+
}
|
|
480
521
|
state.turnCount += 1;
|
|
481
522
|
if (state.maxTurns !== undefined && state.turnCount > state.maxTurns) {
|
|
482
523
|
throw new MaxTurnsExceededError(state.maxTurns);
|
|
483
524
|
}
|
|
484
525
|
const model = await this.#deps.resolveModel(state.agent.model);
|
|
485
526
|
let sawToolCall = false;
|
|
527
|
+
const streamedToolCalls = [];
|
|
486
528
|
let completion;
|
|
487
529
|
let pendingNativeReasoning;
|
|
488
530
|
// Stable process-unique request id, allocated immediately before dispatch
|
|
@@ -527,7 +569,7 @@ export class ApplicationRunLoop {
|
|
|
527
569
|
...options.generationGuard,
|
|
528
570
|
});
|
|
529
571
|
const consume = async () => {
|
|
530
|
-
const deadline = new GenerationDeadline(generationGuard.requestDeadlineMs, () => requestAbortController.abort());
|
|
572
|
+
const deadline = new GenerationDeadline(generationGuard.requestDeadlineMs, () => requestAbortController.abort(), () => generationGuard.progress);
|
|
531
573
|
let iterator;
|
|
532
574
|
let iteratorFinished = false;
|
|
533
575
|
try {
|
|
@@ -572,7 +614,7 @@ export class ApplicationRunLoop {
|
|
|
572
614
|
generationGuard.observeToolCall(event.arguments);
|
|
573
615
|
pendingNativeReasoning = commitPendingNativeReasoning(state, stream, queue, pendingNativeReasoning);
|
|
574
616
|
sawToolCall = true;
|
|
575
|
-
|
|
617
|
+
streamedToolCalls.push(event);
|
|
576
618
|
}
|
|
577
619
|
}
|
|
578
620
|
}
|
|
@@ -612,8 +654,22 @@ export class ApplicationRunLoop {
|
|
|
612
654
|
queue.push({ type: 'cost_update', record });
|
|
613
655
|
throw error;
|
|
614
656
|
}
|
|
615
|
-
if (!completion)
|
|
616
|
-
|
|
657
|
+
if (!completion) {
|
|
658
|
+
// A few application-owned model fixtures (and legacy adapters) end a
|
|
659
|
+
// tool-call stream immediately after the call and use the next request
|
|
660
|
+
// as the continuation boundary. Preserve that contract while routing
|
|
661
|
+
// the calls through the same ordered dispatcher. An empty stream still
|
|
662
|
+
// fails closed as an incomplete model turn.
|
|
663
|
+
if (streamedToolCalls.length === 0)
|
|
664
|
+
throw new Error('Application model turn ended without completion');
|
|
665
|
+
await this.#dispatchToolCalls(state, stream, queue, streamedToolCalls, toolContext);
|
|
666
|
+
if (state.pendingApprovals && state.pendingApprovals.length > 0) {
|
|
667
|
+
stream.interruptions = state.pendingApprovals.map((item) => item.interruption);
|
|
668
|
+
this.#turnPaused = true;
|
|
669
|
+
return finish(stream, state, queue);
|
|
670
|
+
}
|
|
671
|
+
continue;
|
|
672
|
+
}
|
|
617
673
|
// Commit the authoritative terminal state before handling terminal-only
|
|
618
674
|
// tool calls. Approval may pause immediately after this point, and the
|
|
619
675
|
// continuation must still carry the response that produced the calls.
|
|
@@ -680,6 +736,7 @@ export class ApplicationRunLoop {
|
|
|
680
736
|
// Some provider adapters report function calls only in the terminal
|
|
681
737
|
// completion rather than as separate stream events. Their reasoning may
|
|
682
738
|
// likewise be terminal-only, so associate it before replaying calls.
|
|
739
|
+
const toolCalls = [...streamedToolCalls];
|
|
683
740
|
if (!sawToolCall) {
|
|
684
741
|
for (const item of completion.output) {
|
|
685
742
|
if (item.type === 'reasoning')
|
|
@@ -690,9 +747,11 @@ export class ApplicationRunLoop {
|
|
|
690
747
|
continue;
|
|
691
748
|
pendingNativeReasoning = commitPendingNativeReasoning(state, stream, queue, pendingNativeReasoning);
|
|
692
749
|
sawToolCall = true;
|
|
693
|
-
|
|
750
|
+
toolCalls.push(item);
|
|
694
751
|
}
|
|
695
752
|
}
|
|
753
|
+
if (toolCalls.length > 0)
|
|
754
|
+
await this.#dispatchToolCalls(state, stream, queue, toolCalls, toolContext);
|
|
696
755
|
// A native reasoning item belongs to the completed assistant turn even
|
|
697
756
|
// when no tool call follows it. Commit it before assistant text so both
|
|
698
757
|
// stateless continuation and persisted canonical history retain the
|
|
@@ -732,105 +791,154 @@ export class ApplicationRunLoop {
|
|
|
732
791
|
}
|
|
733
792
|
if (state.pendingApprovals.length > 0) {
|
|
734
793
|
stream.interruptions = state.pendingApprovals.map((item) => item.interruption);
|
|
794
|
+
this.#turnPaused = true;
|
|
735
795
|
return finish(stream, state, queue);
|
|
736
796
|
}
|
|
737
797
|
if (!sawToolCall)
|
|
738
798
|
return finish(stream, state, queue);
|
|
739
799
|
}
|
|
740
800
|
}
|
|
741
|
-
async #
|
|
742
|
-
const
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
callId: event.id,
|
|
747
|
-
name: event.name,
|
|
748
|
-
arguments: event.arguments,
|
|
749
|
-
};
|
|
750
|
-
state.history.push(callItem);
|
|
751
|
-
state.input.push({ type: 'tool_call', id: event.id, name: event.name, arguments: event.arguments });
|
|
752
|
-
outputPush(stream, queue, { type: 'item', item: callItem });
|
|
753
|
-
if (!definition) {
|
|
754
|
-
const output = `Unknown tool: ${event.name}`;
|
|
755
|
-
const resultItem = {
|
|
756
|
-
type: 'function_call_result',
|
|
757
|
-
callId: event.id,
|
|
758
|
-
name: event.name,
|
|
759
|
-
output,
|
|
760
|
-
};
|
|
761
|
-
state.history.push(resultItem);
|
|
762
|
-
state.input.push({ type: 'tool_result', id: event.id, output });
|
|
763
|
-
outputPush(stream, queue, { type: 'item', item: resultItem });
|
|
764
|
-
return;
|
|
765
|
-
}
|
|
766
|
-
// Keep the raw-model invocation contract: normalization repairs the
|
|
767
|
-
// accepted object shape but intentionally does not Zod-parse it, which
|
|
768
|
-
// would apply schema defaults before execute. web_fetch relies on its
|
|
769
|
-
// executor fallbacks on the strict JSON-schema path.
|
|
770
|
-
const params = normalizeToolParameters(args, definition.parameters);
|
|
771
|
-
// Consult this run's ledger before prompting: a decision already taken
|
|
772
|
-
// (in the parent run and replayed in, or earlier in this run) must not
|
|
773
|
-
// prompt again. This is what makes approval replay observable.
|
|
774
|
-
const alreadyDecided = state.approvals.isToolApproved({ toolName: event.name, callId: event.id });
|
|
775
|
-
if (alreadyDecided === false) {
|
|
776
|
-
const message = state.approvals.getRejectionMessage(event.name, event.id) ?? 'Tool execution was not approved.';
|
|
777
|
-
const resultItem = {
|
|
778
|
-
type: 'function_call_result',
|
|
779
|
-
callId: event.id,
|
|
780
|
-
name: event.name,
|
|
781
|
-
output: message,
|
|
782
|
-
};
|
|
783
|
-
state.history.push(resultItem);
|
|
784
|
-
state.input.push({ type: 'tool_result', id: event.id, output: message });
|
|
785
|
-
outputPush(stream, queue, { type: 'item', item: resultItem });
|
|
786
|
-
return;
|
|
787
|
-
}
|
|
788
|
-
if (alreadyDecided === true) {
|
|
789
|
-
const result = await this.#invokeTool(definition, params, toolContext, event.id, state);
|
|
790
|
-
const output = typeof result === 'string' ? result : JSON.stringify(result);
|
|
791
|
-
const resultItem = {
|
|
792
|
-
type: 'function_call_result',
|
|
801
|
+
async #dispatchToolCalls(state, stream, queue, events, toolContext) {
|
|
802
|
+
const plan = events.map((event) => {
|
|
803
|
+
const definition = state.agent.tools.find((tool) => tool.name === event.name);
|
|
804
|
+
const callItem = {
|
|
805
|
+
type: 'function_call',
|
|
793
806
|
callId: event.id,
|
|
794
807
|
name: event.name,
|
|
795
|
-
|
|
808
|
+
arguments: event.arguments,
|
|
796
809
|
};
|
|
797
|
-
state.history.push(
|
|
798
|
-
state.input.push({ type: '
|
|
799
|
-
outputPush(stream, queue, { type: 'item', item:
|
|
800
|
-
return
|
|
801
|
-
|
|
802
|
-
if (await definition.needsApproval(params, toolContext)) {
|
|
803
|
-
const pending = {
|
|
804
|
-
callId: event.id,
|
|
805
|
-
toolName: event.name,
|
|
806
|
-
argumentsText: event.arguments,
|
|
807
|
-
interruption: {
|
|
808
|
-
type: 'tool_approval_item',
|
|
809
|
-
rawItem: { type: 'function_call', callId: event.id, name: event.name, arguments: event.arguments },
|
|
810
|
-
callId: event.id,
|
|
811
|
-
name: event.name,
|
|
812
|
-
arguments: event.arguments,
|
|
813
|
-
},
|
|
810
|
+
state.history.push(callItem);
|
|
811
|
+
state.input.push({ type: 'tool_call', id: event.id, name: event.name, arguments: event.arguments });
|
|
812
|
+
outputPush(stream, queue, { type: 'item', item: callItem });
|
|
813
|
+
return {
|
|
814
|
+
event,
|
|
814
815
|
definition,
|
|
815
|
-
|
|
816
|
+
parallelSafe: false,
|
|
817
|
+
status: 'ready',
|
|
816
818
|
};
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
819
|
+
});
|
|
820
|
+
state.toolPlan = plan;
|
|
821
|
+
for (const entry of plan) {
|
|
822
|
+
const { event, definition } = entry;
|
|
823
|
+
if (!definition) {
|
|
824
|
+
entry.output = `Unknown tool: ${event.name}`;
|
|
825
|
+
continue;
|
|
826
|
+
}
|
|
827
|
+
// Keep the raw-model invocation contract: normalization repairs the
|
|
828
|
+
// accepted object shape but intentionally does not Zod-parse it, which
|
|
829
|
+
// would apply schema defaults before execute. web_fetch relies on its
|
|
830
|
+
// executor fallbacks on the strict JSON-schema path.
|
|
831
|
+
entry.params = normalizeToolParameters(parseArguments(event.arguments), definition.parameters);
|
|
832
|
+
// Consult this run's ledger before prompting: a decision already taken
|
|
833
|
+
// (in the parent run and replayed in, or earlier in this run) must not
|
|
834
|
+
// prompt again. This is what makes approval replay observable.
|
|
835
|
+
const alreadyDecided = state.approvals.isToolApproved({ toolName: event.name, callId: event.id });
|
|
836
|
+
if (alreadyDecided === false) {
|
|
837
|
+
entry.output = state.approvals.getRejectionMessage(event.name, event.id) ?? 'Tool execution was not approved.';
|
|
838
|
+
continue;
|
|
839
|
+
}
|
|
840
|
+
if (alreadyDecided !== true && (await definition.needsApproval(entry.params, toolContext))) {
|
|
841
|
+
entry.status = 'approval_pending';
|
|
842
|
+
const pending = {
|
|
843
|
+
callId: event.id,
|
|
844
|
+
toolName: event.name,
|
|
845
|
+
argumentsText: event.arguments,
|
|
846
|
+
interruption: {
|
|
847
|
+
type: 'tool_approval_item',
|
|
848
|
+
rawItem: { type: 'function_call', callId: event.id, name: event.name, arguments: event.arguments },
|
|
849
|
+
callId: event.id,
|
|
850
|
+
name: event.name,
|
|
851
|
+
arguments: event.arguments,
|
|
852
|
+
},
|
|
853
|
+
definition,
|
|
854
|
+
params: entry.params,
|
|
855
|
+
plan: entry,
|
|
856
|
+
};
|
|
857
|
+
state.pendingApprovals ??= [];
|
|
858
|
+
state.pendingApprovals.push(pending);
|
|
859
|
+
continue;
|
|
860
|
+
}
|
|
861
|
+
entry.parallelSafe = await isParallelSafe(definition, entry.params, toolContext);
|
|
862
|
+
}
|
|
863
|
+
state.pendingApproval = state.pendingApprovals?.[0];
|
|
864
|
+
stream.interruptions = (state.pendingApprovals ?? []).map((item) => item.interruption);
|
|
865
|
+
this.#deps.logDiagnostic?.('tool parallel eligibility', {
|
|
866
|
+
decisions: plan.map((entry) => ({
|
|
867
|
+
callId: entry.event.id,
|
|
868
|
+
toolName: entry.event.name,
|
|
869
|
+
parallelSafe: entry.parallelSafe,
|
|
870
|
+
approvalPending: entry.status === 'approval_pending',
|
|
871
|
+
})),
|
|
872
|
+
});
|
|
873
|
+
await this.#settleToolPlan(state, stream, queue, toolContext);
|
|
874
|
+
}
|
|
875
|
+
async #settleToolPlan(state, stream, queue, toolContext) {
|
|
876
|
+
const plan = state.toolPlan;
|
|
877
|
+
if (!plan)
|
|
822
878
|
return;
|
|
879
|
+
while (true) {
|
|
880
|
+
const firstPending = plan.find((entry) => entry.status !== 'completed');
|
|
881
|
+
if (!firstPending) {
|
|
882
|
+
state.toolPlan = undefined;
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
if (firstPending.status === 'approval_pending')
|
|
886
|
+
return;
|
|
887
|
+
const group = [];
|
|
888
|
+
for (const entry of plan) {
|
|
889
|
+
if (entry.status === 'completed')
|
|
890
|
+
continue;
|
|
891
|
+
if (entry.status === 'approval_pending')
|
|
892
|
+
break;
|
|
893
|
+
if (group.length > 0 &&
|
|
894
|
+
(!entry.parallelSafe || !group[0].parallelSafe || group.length >= this.#maxParallelToolCalls))
|
|
895
|
+
break;
|
|
896
|
+
group.push(entry);
|
|
897
|
+
if (!entry.parallelSafe)
|
|
898
|
+
break;
|
|
899
|
+
}
|
|
900
|
+
const batchId = `tool-batch-${++nextToolBatchSeq}`;
|
|
901
|
+
this.#deps.logDiagnostic?.('tool batch dispatched', {
|
|
902
|
+
batchId,
|
|
903
|
+
callIds: group.map((entry) => entry.event.id),
|
|
904
|
+
parallel: group.length > 1,
|
|
905
|
+
dispatchOrder: group.map((entry) => entry.event.id),
|
|
906
|
+
});
|
|
907
|
+
if (group.length > 1) {
|
|
908
|
+
const results = await Promise.allSettled(group.map((entry) => this.#invokePlannedTool(entry, toolContext, state)));
|
|
909
|
+
const rejected = results.find((result) => result.status === 'rejected');
|
|
910
|
+
if (rejected)
|
|
911
|
+
throw rejected.reason;
|
|
912
|
+
for (const [index, result] of results.entries()) {
|
|
913
|
+
this.#appendToolResult(state, stream, queue, group[index], result.value);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
else {
|
|
917
|
+
const result = await this.#invokePlannedTool(group[0], toolContext, state);
|
|
918
|
+
this.#appendToolResult(state, stream, queue, group[0], result);
|
|
919
|
+
}
|
|
920
|
+
this.#deps.logDiagnostic?.('tool batch settled', {
|
|
921
|
+
batchId,
|
|
922
|
+
settlementOrder: group.map((entry) => entry.event.id),
|
|
923
|
+
});
|
|
823
924
|
}
|
|
824
|
-
|
|
925
|
+
}
|
|
926
|
+
async #invokePlannedTool(entry, toolContext, state) {
|
|
927
|
+
entry.status = 'completed';
|
|
928
|
+
if (entry.output !== undefined)
|
|
929
|
+
return entry.output;
|
|
930
|
+
return this.#invokeTool(entry.definition, entry.params, toolContext, entry.event.id, state);
|
|
931
|
+
}
|
|
932
|
+
#appendToolResult(state, stream, queue, entry, result) {
|
|
825
933
|
const output = typeof result === 'string' ? result : JSON.stringify(result);
|
|
826
934
|
const resultItem = {
|
|
827
935
|
type: 'function_call_result',
|
|
828
|
-
callId: event.id,
|
|
829
|
-
name: event.name,
|
|
936
|
+
callId: entry.event.id,
|
|
937
|
+
name: entry.event.name,
|
|
830
938
|
output,
|
|
831
939
|
};
|
|
832
940
|
state.history.push(resultItem);
|
|
833
|
-
state.input.push({ type: 'tool_result', id: event.id, output });
|
|
941
|
+
state.input.push({ type: 'tool_result', id: entry.event.id, output });
|
|
834
942
|
outputPush(stream, queue, { type: 'item', item: resultItem });
|
|
835
943
|
}
|
|
836
944
|
/**
|
|
@@ -864,6 +972,9 @@ export class ApplicationRunLoop {
|
|
|
864
972
|
attempt,
|
|
865
973
|
scope: state?.hookScope ?? 'root',
|
|
866
974
|
};
|
|
975
|
+
// Dispatch mark must run before execute so a mid-tool stream failure can
|
|
976
|
+
// settle the ledger as unknown rather than aborted.
|
|
977
|
+
this.#deps.getOnToolDispatch?.()?.(callId);
|
|
867
978
|
await this.#notifyToolLifecycle(() => this.#deps.toolLifecycle?.before(lifecycleContext));
|
|
868
979
|
try {
|
|
869
980
|
const result = await definition.execute(params, toolContext, { toolCall: { callId } });
|
|
@@ -1043,6 +1154,17 @@ function parseArguments(value) {
|
|
|
1043
1154
|
return {};
|
|
1044
1155
|
}
|
|
1045
1156
|
}
|
|
1157
|
+
async function isParallelSafe(definition, params, context) {
|
|
1158
|
+
if (typeof definition.parallelSafe === 'function') {
|
|
1159
|
+
try {
|
|
1160
|
+
return await definition.parallelSafe(params, context);
|
|
1161
|
+
}
|
|
1162
|
+
catch {
|
|
1163
|
+
return false;
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
return definition.parallelSafe === true;
|
|
1167
|
+
}
|
|
1046
1168
|
function getInterruptionCallId(value) {
|
|
1047
1169
|
if (typeof value !== 'object' || value === null)
|
|
1048
1170
|
return undefined;
|