@agents-ensemble/core 0.20.0 → 0.22.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/assets/pi/pi-cli-env-wrapper.cmd +28 -0
- package/assets/pi/pi-cli-env-wrapper.sh +19 -0
- package/assets/pi/settings.json +4 -0
- package/dist/acp/acp-preset-bins.d.ts +3 -3
- package/dist/acp/acp-preset-bins.d.ts.map +1 -1
- package/dist/acp/acp-preset-bins.js +3 -3
- package/dist/acp/acp-preset-bins.js.map +1 -1
- package/dist/acp/index.d.ts +3 -1
- package/dist/acp/index.d.ts.map +1 -1
- package/dist/acp/index.js +2 -1
- package/dist/acp/index.js.map +1 -1
- package/dist/acp/pi-acp-wrapper.d.ts +3 -0
- package/dist/acp/pi-acp-wrapper.d.ts.map +1 -0
- package/dist/acp/pi-acp-wrapper.js +41 -0
- package/dist/acp/pi-acp-wrapper.js.map +1 -0
- package/dist/acp/pi-builtin-config.d.ts +10 -0
- package/dist/acp/pi-builtin-config.d.ts.map +1 -0
- package/dist/acp/pi-builtin-config.js +36 -0
- package/dist/acp/pi-builtin-config.js.map +1 -0
- package/dist/acp/pi-runtime.d.ts +23 -0
- package/dist/acp/pi-runtime.d.ts.map +1 -0
- package/dist/acp/pi-runtime.js +160 -0
- package/dist/acp/pi-runtime.js.map +1 -0
- package/dist/acp/pi-session-map.d.ts +36 -0
- package/dist/acp/pi-session-map.d.ts.map +1 -0
- package/dist/acp/pi-session-map.js +266 -0
- package/dist/acp/pi-session-map.js.map +1 -0
- package/dist/acp/resolve-acp-spawn.d.ts +9 -3
- package/dist/acp/resolve-acp-spawn.d.ts.map +1 -1
- package/dist/acp/resolve-acp-spawn.js +41 -9
- package/dist/acp/resolve-acp-spawn.js.map +1 -1
- package/dist/acp/resolve-bundled-acp-bin.d.ts +2 -2
- package/dist/acp/resolve-bundled-acp-bin.d.ts.map +1 -1
- package/dist/acp/resolve-bundled-acp-bin.js +4 -4
- package/dist/acp/resolve-bundled-acp-bin.js.map +1 -1
- package/dist/acp/validate-acp-preset-prerequisites.d.ts +1 -1
- package/dist/acp/validate-acp-preset-prerequisites.d.ts.map +1 -1
- package/dist/acp/validate-acp-preset-prerequisites.js +30 -10
- package/dist/acp/validate-acp-preset-prerequisites.js.map +1 -1
- package/dist/conductor/conductor-agent.d.ts +15 -3
- package/dist/conductor/conductor-agent.d.ts.map +1 -1
- package/dist/conductor/conductor-auth.d.ts +4 -3
- package/dist/conductor/conductor-auth.d.ts.map +1 -1
- package/dist/conductor/conductor-auth.js +20 -4
- package/dist/conductor/conductor-auth.js.map +1 -1
- package/dist/conductor/conductor-dispatch-lifecycle.d.ts +85 -0
- package/dist/conductor/conductor-dispatch-lifecycle.d.ts.map +1 -0
- package/dist/conductor/conductor-dispatch-lifecycle.js +190 -0
- package/dist/conductor/conductor-dispatch-lifecycle.js.map +1 -0
- package/dist/conductor/conductor-history.d.ts +15 -0
- package/dist/conductor/conductor-history.d.ts.map +1 -1
- package/dist/conductor/conductor-history.js +38 -7
- package/dist/conductor/conductor-history.js.map +1 -1
- package/dist/conductor/conductor-path-safety.d.ts +23 -0
- package/dist/conductor/conductor-path-safety.d.ts.map +1 -0
- package/dist/conductor/conductor-path-safety.js +319 -0
- package/dist/conductor/conductor-path-safety.js.map +1 -0
- package/dist/conductor/conductor-pi-auth.d.ts +57 -2
- package/dist/conductor/conductor-pi-auth.d.ts.map +1 -1
- package/dist/conductor/conductor-pi-auth.js +718 -40
- package/dist/conductor/conductor-pi-auth.js.map +1 -1
- package/dist/conductor/conductor-recovery-inbox.d.ts +73 -0
- package/dist/conductor/conductor-recovery-inbox.d.ts.map +1 -0
- package/dist/conductor/conductor-recovery-inbox.js +181 -0
- package/dist/conductor/conductor-recovery-inbox.js.map +1 -0
- package/dist/conductor/conductor-send-reconnect.d.ts +33 -4
- package/dist/conductor/conductor-send-reconnect.d.ts.map +1 -1
- package/dist/conductor/conductor-send-reconnect.js +89 -24
- package/dist/conductor/conductor-send-reconnect.js.map +1 -1
- package/dist/conductor/conductor-session-driver.d.ts +34 -1
- package/dist/conductor/conductor-session-driver.d.ts.map +1 -1
- package/dist/conductor/conductor-session-driver.js +529 -75
- package/dist/conductor/conductor-session-driver.js.map +1 -1
- package/dist/conductor/conductor-session.d.ts +4 -0
- package/dist/conductor/conductor-session.d.ts.map +1 -1
- package/dist/conductor/conductor-session.js +416 -145
- package/dist/conductor/conductor-session.js.map +1 -1
- package/dist/conductor/conductor-setup.d.ts +30 -0
- package/dist/conductor/conductor-setup.d.ts.map +1 -0
- package/dist/conductor/conductor-setup.js +243 -0
- package/dist/conductor/conductor-setup.js.map +1 -0
- package/dist/conductor/cursor-bootstrap-state.d.ts +9 -0
- package/dist/conductor/cursor-bootstrap-state.d.ts.map +1 -0
- package/dist/conductor/cursor-bootstrap-state.js +11 -0
- package/dist/conductor/cursor-bootstrap-state.js.map +1 -0
- package/dist/conductor/cursor-run-snapshots.d.ts +30 -0
- package/dist/conductor/cursor-run-snapshots.d.ts.map +1 -0
- package/dist/conductor/cursor-run-snapshots.js +120 -0
- package/dist/conductor/cursor-run-snapshots.js.map +1 -0
- package/dist/conductor/index.d.ts +18 -7
- package/dist/conductor/index.d.ts.map +1 -1
- package/dist/conductor/index.js +9 -4
- package/dist/conductor/index.js.map +1 -1
- package/dist/conductor/operator-command.d.ts +5 -0
- package/dist/conductor/operator-command.d.ts.map +1 -1
- package/dist/conductor/operator-command.js +6 -0
- package/dist/conductor/operator-command.js.map +1 -1
- package/dist/conductor/operator-input-binding.d.ts +17 -0
- package/dist/conductor/operator-input-binding.d.ts.map +1 -1
- package/dist/conductor/pi-auth-recovery-snapshot-store.d.ts +45 -0
- package/dist/conductor/pi-auth-recovery-snapshot-store.d.ts.map +1 -0
- package/dist/conductor/pi-auth-recovery-snapshot-store.js +116 -0
- package/dist/conductor/pi-auth-recovery-snapshot-store.js.map +1 -0
- package/dist/conductor/pi-conductor-agent.d.ts +6 -3
- package/dist/conductor/pi-conductor-agent.d.ts.map +1 -1
- package/dist/conductor/pi-conductor-agent.js +276 -53
- package/dist/conductor/pi-conductor-agent.js.map +1 -1
- package/dist/conductor/pi-conductor-session-file.d.ts +13 -0
- package/dist/conductor/pi-conductor-session-file.d.ts.map +1 -0
- package/dist/conductor/pi-conductor-session-file.js +21 -0
- package/dist/conductor/pi-conductor-session-file.js.map +1 -0
- package/dist/conductor/pi-resource-loader.d.ts +6 -0
- package/dist/conductor/pi-resource-loader.d.ts.map +1 -1
- package/dist/conductor/pi-resource-loader.js +26 -12
- package/dist/conductor/pi-resource-loader.js.map +1 -1
- package/dist/conductor/pi-user-root-lock.d.ts +8 -0
- package/dist/conductor/pi-user-root-lock.d.ts.map +1 -0
- package/dist/conductor/pi-user-root-lock.js +53 -0
- package/dist/conductor/pi-user-root-lock.js.map +1 -0
- package/dist/conductor/recovery-inbox-command.d.ts +29 -0
- package/dist/conductor/recovery-inbox-command.d.ts.map +1 -0
- package/dist/conductor/recovery-inbox-command.js +248 -0
- package/dist/conductor/recovery-inbox-command.js.map +1 -0
- package/dist/conductor/session/events/index.d.ts +1 -1
- package/dist/conductor/session/events/index.d.ts.map +1 -1
- package/dist/conductor/session/events/index.js.map +1 -1
- package/dist/conductor/session/events/session-event.d.ts +43 -1
- package/dist/conductor/session/events/session-event.d.ts.map +1 -1
- package/dist/conductor/session/events/session-log-event-groups.d.ts +1 -1
- package/dist/conductor/session/events/session-log-event-groups.d.ts.map +1 -1
- package/dist/conductor/session/events/session-log-event-groups.js +2 -0
- package/dist/conductor/session/events/session-log-event-groups.js.map +1 -1
- package/dist/conductor/session/events/session-log-event.d.ts +4 -0
- package/dist/conductor/session/events/session-log-event.d.ts.map +1 -1
- package/dist/conductor/session/format-session-event.js +57 -5
- package/dist/conductor/session/format-session-event.js.map +1 -1
- package/dist/conductor/session/select-dispatch-batch.d.ts +2 -0
- package/dist/conductor/session/select-dispatch-batch.d.ts.map +1 -1
- package/dist/conductor/session/select-dispatch-batch.js +36 -2
- package/dist/conductor/session/select-dispatch-batch.js.map +1 -1
- package/dist/conductor/session/session-event-queue.d.ts +14 -0
- package/dist/conductor/session/session-event-queue.d.ts.map +1 -1
- package/dist/conductor/session/session-event-queue.js +82 -10
- package/dist/conductor/session/session-event-queue.js.map +1 -1
- package/dist/conductor/session/session-event.d.ts +1 -1
- package/dist/conductor/session/session-event.d.ts.map +1 -1
- package/dist/conductor/session-policy.d.ts.map +1 -1
- package/dist/conductor/session-policy.js +8 -4
- package/dist/conductor/session-policy.js.map +1 -1
- package/dist/conductor/submit-operator-input.d.ts +3 -0
- package/dist/conductor/submit-operator-input.d.ts.map +1 -1
- package/dist/conductor/submit-operator-input.js +21 -0
- package/dist/conductor/submit-operator-input.js.map +1 -1
- package/dist/config/deep-merge.d.ts.map +1 -1
- package/dist/config/deep-merge.js +13 -3
- package/dist/config/deep-merge.js.map +1 -1
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +1 -1
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/doctor-settings.d.ts +59 -0
- package/dist/config/doctor-settings.d.ts.map +1 -0
- package/dist/config/doctor-settings.js +343 -0
- package/dist/config/doctor-settings.js.map +1 -0
- package/dist/config/ensemble-config-digest.d.ts +2 -4
- package/dist/config/ensemble-config-digest.d.ts.map +1 -1
- package/dist/config/ensemble-config-digest.js +2 -4
- package/dist/config/ensemble-config-digest.js.map +1 -1
- package/dist/config/index.d.ts +3 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +1 -0
- package/dist/config/index.js.map +1 -1
- package/dist/config/load-ensemble-config.d.ts.map +1 -1
- package/dist/config/load-ensemble-config.js +8 -1
- package/dist/config/load-ensemble-config.js.map +1 -1
- package/dist/config/parse-config.d.ts.map +1 -1
- package/dist/config/parse-config.js +34 -14
- package/dist/config/parse-config.js.map +1 -1
- package/dist/config/resolve-settings.d.ts +3 -1
- package/dist/config/resolve-settings.d.ts.map +1 -1
- package/dist/config/resolve-settings.js +7 -4
- package/dist/config/resolve-settings.js.map +1 -1
- package/dist/config/types.d.ts +14 -5
- package/dist/config/types.d.ts.map +1 -1
- package/dist/dispatch/attach-worker.js +1 -1
- package/dist/dispatch/attach-worker.js.map +1 -1
- package/dist/dispatch/worker-acp-session.d.ts +21 -3
- package/dist/dispatch/worker-acp-session.d.ts.map +1 -1
- package/dist/dispatch/worker-acp-session.js +50 -13
- package/dist/dispatch/worker-acp-session.js.map +1 -1
- package/dist/ensemble-paths.d.ts +19 -19
- package/dist/ensemble-paths.d.ts.map +1 -1
- package/dist/ensemble-paths.js +40 -22
- package/dist/ensemble-paths.js.map +1 -1
- package/dist/issue/resolve-issue-url.d.ts +4 -0
- package/dist/issue/resolve-issue-url.d.ts.map +1 -1
- package/dist/issue/resolve-issue-url.js +2 -2
- package/dist/issue/resolve-issue-url.js.map +1 -1
- package/dist/operator-runtime.d.ts +19 -4
- package/dist/operator-runtime.d.ts.map +1 -1
- package/dist/operator-runtime.js +13 -2
- package/dist/operator-runtime.js.map +1 -1
- package/dist/profile/types.d.ts +4 -0
- package/dist/profile/types.d.ts.map +1 -1
- package/dist/profile/types.js +29 -12
- package/dist/profile/types.js.map +1 -1
- package/dist/representation/escape-terminal-control-characters.d.ts +9 -0
- package/dist/representation/escape-terminal-control-characters.d.ts.map +1 -0
- package/dist/representation/escape-terminal-control-characters.js +34 -0
- package/dist/representation/escape-terminal-control-characters.js.map +1 -0
- package/dist/representation/index.d.ts +1 -0
- package/dist/representation/index.d.ts.map +1 -1
- package/dist/representation/index.js +1 -0
- package/dist/representation/index.js.map +1 -1
- package/dist/runtime/types.d.ts +9 -0
- package/dist/runtime/types.d.ts.map +1 -1
- package/dist/runtime/worker-runtime.d.ts +14 -1
- package/dist/runtime/worker-runtime.d.ts.map +1 -1
- package/dist/runtime/worker-runtime.js +197 -50
- package/dist/runtime/worker-runtime.js.map +1 -1
- package/dist/runtime/worker-session.d.ts +2 -1
- package/dist/runtime/worker-session.d.ts.map +1 -1
- package/dist/runtime/worker-session.js +1 -0
- package/dist/runtime/worker-session.js.map +1 -1
- package/dist/session/export-session-bundle.d.ts +77 -0
- package/dist/session/export-session-bundle.d.ts.map +1 -0
- package/dist/session/export-session-bundle.js +640 -0
- package/dist/session/export-session-bundle.js.map +1 -0
- package/dist/session/index.d.ts +6 -3
- package/dist/session/index.d.ts.map +1 -1
- package/dist/session/index.js +4 -1
- package/dist/session/index.js.map +1 -1
- package/dist/session/open-question-control.d.ts +9 -0
- package/dist/session/open-question-control.d.ts.map +1 -1
- package/dist/session/open-question-control.js +26 -1
- package/dist/session/open-question-control.js.map +1 -1
- package/dist/session/open-questions-store.d.ts +1 -1
- package/dist/session/open-questions-store.d.ts.map +1 -1
- package/dist/session/open-questions-store.js +3 -6
- package/dist/session/open-questions-store.js.map +1 -1
- package/dist/session/persisted-file.d.ts +5 -0
- package/dist/session/persisted-file.d.ts.map +1 -0
- package/dist/session/persisted-file.js +56 -0
- package/dist/session/persisted-file.js.map +1 -0
- package/dist/session/session-resume-store.d.ts +79 -10
- package/dist/session/session-resume-store.d.ts.map +1 -1
- package/dist/session/session-resume-store.js +353 -83
- package/dist/session/session-resume-store.js.map +1 -1
- package/dist/session/session-snapshot-paths.d.ts +11 -0
- package/dist/session/session-snapshot-paths.d.ts.map +1 -0
- package/dist/session/session-snapshot-paths.js +41 -0
- package/dist/session/session-snapshot-paths.js.map +1 -0
- package/dist/session/session-state-store.d.ts +2 -2
- package/dist/session/session-state-store.d.ts.map +1 -1
- package/dist/session/session-state-store.js +5 -16
- package/dist/session/session-state-store.js.map +1 -1
- package/dist/session/tool-call-snapshot-history.d.ts +12 -1
- package/dist/session/tool-call-snapshot-history.d.ts.map +1 -1
- package/dist/session/tool-call-snapshot-history.js +75 -70
- package/dist/session/tool-call-snapshot-history.js.map +1 -1
- package/dist/session/worker-session-history.d.ts +51 -0
- package/dist/session/worker-session-history.d.ts.map +1 -0
- package/dist/session/worker-session-history.js +654 -0
- package/dist/session/worker-session-history.js.map +1 -0
- package/dist/workspace/index.d.ts +1 -1
- package/dist/workspace/index.d.ts.map +1 -1
- package/dist/worktree/worktree.d.ts +6 -1
- package/dist/worktree/worktree.d.ts.map +1 -1
- package/dist/worktree/worktree.js +123 -2
- package/dist/worktree/worktree.js.map +1 -1
- package/package.json +9 -4
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { ensureMaxTurnsOpenQuestion } from '../escalation/enqueue-max-turns-question.js';
|
|
2
|
+
import { ConductorDispatchLifecycle, settleConductorDispatch, } from './conductor-dispatch-lifecycle.js';
|
|
3
|
+
import { ConductorRecoveryInbox } from './conductor-recovery-inbox.js';
|
|
2
4
|
import { reconnectConductorAgent, sendConductorWithReconnect, } from './conductor-send-reconnect.js';
|
|
3
5
|
import { formatSessionEventsForConductor } from './session/format-session-event.js';
|
|
4
6
|
import { bufferDispatchHoldEvents, createDispatchHoldState, pruneStalePermissionEvents, } from './session/dispatch-hold.js';
|
|
@@ -19,9 +21,85 @@ export async function runConductorSessionDriver(options) {
|
|
|
19
21
|
runId: '',
|
|
20
22
|
status: 'finished',
|
|
21
23
|
};
|
|
24
|
+
let cursorBootstrapPending = options.recoveryBackend === 'cursor' &&
|
|
25
|
+
(options.cursorBootstrapState
|
|
26
|
+
? options.cursorBootstrapState.status === 'incomplete'
|
|
27
|
+
: options.cursorBootstrapComplete === undefined
|
|
28
|
+
? !options.skipInitialSend
|
|
29
|
+
: !options.cursorBootstrapComplete);
|
|
30
|
+
let cursorBootstrapCompletedThisDriver = false;
|
|
22
31
|
let inFlightSend;
|
|
23
32
|
let postLoopWaiting = false;
|
|
33
|
+
const lifecycle = options.dispatchLifecycle ??
|
|
34
|
+
new ConductorDispatchLifecycle();
|
|
35
|
+
const recoveryInbox = options.recoveryInbox ?? new ConductorRecoveryInbox();
|
|
24
36
|
const dispatchHoldState = options.dispatchHoldState ?? createDispatchHoldState();
|
|
37
|
+
const cursorBootstrapNeedsDecision = () => options.cursorBootstrapState?.status === 'unknown' ||
|
|
38
|
+
options.cursorBootstrapState?.status === 'saving';
|
|
39
|
+
const isCursorBootstrapPending = () => {
|
|
40
|
+
if (options.recoveryBackend !== 'cursor')
|
|
41
|
+
return false;
|
|
42
|
+
if (options.cursorBootstrapState) {
|
|
43
|
+
return (options.cursorBootstrapState.status === 'incomplete' &&
|
|
44
|
+
!cursorBootstrapCompletedThisDriver);
|
|
45
|
+
}
|
|
46
|
+
return cursorBootstrapPending;
|
|
47
|
+
};
|
|
48
|
+
const markCursorBootstrapCompleted = () => {
|
|
49
|
+
cursorBootstrapPending = false;
|
|
50
|
+
cursorBootstrapCompletedThisDriver = true;
|
|
51
|
+
};
|
|
52
|
+
let automaticReconnect;
|
|
53
|
+
const sendReconnect = withAgentAvailabilityCallbacks(options.sendReconnect, (reason, info) => {
|
|
54
|
+
lifecycle.setAgentAvailable(info.success);
|
|
55
|
+
if (info.source === 'automatic') {
|
|
56
|
+
automaticReconnect = { reason, succeeded: info.success };
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
const settleAttempt = (input) => {
|
|
60
|
+
const outcome = settleConductorDispatch({
|
|
61
|
+
lifecycle,
|
|
62
|
+
lease: input.lease,
|
|
63
|
+
inbox: recoveryInbox,
|
|
64
|
+
...(input.reservation ? { reservation: input.reservation } : {}),
|
|
65
|
+
workflow: 'issue',
|
|
66
|
+
backend: options.recoveryBackend ?? 'pi',
|
|
67
|
+
sendCount: input.sendCount,
|
|
68
|
+
sourceKey: input.sourceKey,
|
|
69
|
+
eventTypes: input.events.map((event) => event.type),
|
|
70
|
+
operatorInputTexts: input.lease.payload.operatorInputTexts,
|
|
71
|
+
result: input.result,
|
|
72
|
+
...(automaticReconnect
|
|
73
|
+
? { automaticReconnect: { ...automaticReconnect } }
|
|
74
|
+
: {}),
|
|
75
|
+
toolCallStarted: input.toolCallStarted,
|
|
76
|
+
captureFailure: !options.shutdownSignal.aborted &&
|
|
77
|
+
options.operatorCanInspectRecovery === true,
|
|
78
|
+
...(input.requiresConfirmation !== undefined
|
|
79
|
+
? { requiresConfirmation: input.requiresConfirmation }
|
|
80
|
+
: {}),
|
|
81
|
+
...(input.result.status === 'send_rejected'
|
|
82
|
+
? { agentAvailable: false }
|
|
83
|
+
: {}),
|
|
84
|
+
});
|
|
85
|
+
automaticReconnect = undefined;
|
|
86
|
+
if (!outcome.completed &&
|
|
87
|
+
!options.shutdownSignal.aborted &&
|
|
88
|
+
outcome.failureClass) {
|
|
89
|
+
options.onRecoveryAttention?.({
|
|
90
|
+
attemptId: input.lease.attemptId,
|
|
91
|
+
sourceKey: input.sourceKey,
|
|
92
|
+
status: input.result.status,
|
|
93
|
+
failureClass: outcome.failureClass,
|
|
94
|
+
confirmationRequired: lifecycle.confirmationRequired,
|
|
95
|
+
pendingOperatorMessageCount: options.eventQueue
|
|
96
|
+
.snapshot()
|
|
97
|
+
.filter((event) => event.type === 'operator.message').length,
|
|
98
|
+
...(outcome.snapshot ? { snapshot: outcome.snapshot } : {}),
|
|
99
|
+
agentAvailable: lifecycle.available,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
};
|
|
25
103
|
const hasDispatchableWorkerOutcome = () => selectDispatchBatch({
|
|
26
104
|
queue: options.eventQueue.snapshot().filter((event) => event.type === 'worker.completed' || event.type === 'worker.failed'),
|
|
27
105
|
state: dispatchBatchState,
|
|
@@ -38,7 +116,14 @@ export async function runConductorSessionDriver(options) {
|
|
|
38
116
|
const holdBlocksStop = dispatchHoldState.heldEvents.length > 0 &&
|
|
39
117
|
options.stopOnUnansweredInput !== true &&
|
|
40
118
|
loopState.lastStatus !== 'error' &&
|
|
119
|
+
loopState.lastStatus !== 'send_rejected' &&
|
|
41
120
|
loopState.lastStatus !== 'cancelled';
|
|
121
|
+
if (lifecycle.confirmationRequired &&
|
|
122
|
+
options.operatorCanInspectRecovery !== true) {
|
|
123
|
+
stopReason =
|
|
124
|
+
loopState.lastStatus === 'cancelled' ? 'cancelled' : 'error';
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
42
127
|
if (holdBlocksStop) {
|
|
43
128
|
postLoopWaiting = false;
|
|
44
129
|
return false;
|
|
@@ -47,6 +132,13 @@ export async function runConductorSessionDriver(options) {
|
|
|
47
132
|
postLoopWaiting = false;
|
|
48
133
|
return false;
|
|
49
134
|
}
|
|
135
|
+
if (options.operatorCanInspectRecovery && lifecycle.confirmationRequired) {
|
|
136
|
+
if (!postLoopWaiting) {
|
|
137
|
+
postLoopWaiting = true;
|
|
138
|
+
options.onIssueLoopStop?.();
|
|
139
|
+
}
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
50
142
|
// A worker publishes its outcome to the inbox before its runtime round
|
|
51
143
|
// leaves the running state. Drain that notification chain before
|
|
52
144
|
// deciding the session is idle, otherwise the driver can tear down in
|
|
@@ -72,17 +164,71 @@ export async function runConductorSessionDriver(options) {
|
|
|
72
164
|
}
|
|
73
165
|
return false;
|
|
74
166
|
};
|
|
167
|
+
if (options.shutdownSignal.aborted) {
|
|
168
|
+
stopReason = 'interrupted';
|
|
169
|
+
return {
|
|
170
|
+
stopReason,
|
|
171
|
+
sendCount,
|
|
172
|
+
lastSendResult,
|
|
173
|
+
autonomousTurns,
|
|
174
|
+
lastDispatchesThisTurn,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
if (options.cursorBootstrapState && !options.skipInitialSend) {
|
|
178
|
+
try {
|
|
179
|
+
await waitForCursorBootstrapDecision({
|
|
180
|
+
state: options.cursorBootstrapState,
|
|
181
|
+
lifecycle,
|
|
182
|
+
eventQueue: options.eventQueue,
|
|
183
|
+
onOperatorQueueChanged: options.onOperatorQueueChanged,
|
|
184
|
+
signal: options.shutdownSignal,
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
if (!isAbortError(error))
|
|
189
|
+
throw error;
|
|
190
|
+
stopReason = 'interrupted';
|
|
191
|
+
return {
|
|
192
|
+
stopReason,
|
|
193
|
+
sendCount,
|
|
194
|
+
lastSendResult,
|
|
195
|
+
autonomousTurns,
|
|
196
|
+
lastDispatchesThisTurn,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
}
|
|
75
200
|
if (!options.skipInitialSend) {
|
|
201
|
+
const initialPrompt = await Promise.resolve(options.initialPrompt);
|
|
202
|
+
if (options.shutdownSignal.aborted) {
|
|
203
|
+
stopReason = 'interrupted';
|
|
204
|
+
return {
|
|
205
|
+
stopReason,
|
|
206
|
+
sendCount,
|
|
207
|
+
lastSendResult,
|
|
208
|
+
autonomousTurns,
|
|
209
|
+
lastDispatchesThisTurn,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
const initialLease = lifecycle.beginSend({
|
|
213
|
+
events: [],
|
|
214
|
+
sourceKey: 'initial',
|
|
215
|
+
operatorInputTexts: [],
|
|
216
|
+
});
|
|
217
|
+
if (!initialLease) {
|
|
218
|
+
throw new Error('Could not acquire the initial conductor send lease.');
|
|
219
|
+
}
|
|
220
|
+
automaticReconnect = undefined;
|
|
221
|
+
let initialToolCallStarted = false;
|
|
76
222
|
options.onConductorInbound?.({
|
|
77
223
|
triggers: 1,
|
|
78
224
|
sources: ['initial'],
|
|
79
225
|
});
|
|
80
|
-
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
message:
|
|
226
|
+
const initialSend = runEventConductorSend({
|
|
227
|
+
// The initial result is consumed before the event loop begins, but the
|
|
228
|
+
// wait still follows the same shutdown interruption contract as sends.
|
|
229
|
+
message: initialPrompt,
|
|
84
230
|
conductorHandle: options.conductorHandle,
|
|
85
|
-
sendReconnect
|
|
231
|
+
sendReconnect,
|
|
86
232
|
signal: options.shutdownSignal,
|
|
87
233
|
workerDispatches: options.workerDispatches,
|
|
88
234
|
workerFailures: options.workerFailures,
|
|
@@ -93,16 +239,80 @@ export async function runConductorSessionDriver(options) {
|
|
|
93
239
|
resetOutboundDispatchesThisSend: options.resetOutboundDispatchesThisSend,
|
|
94
240
|
getOutboundDispatchesThisSend: options.getOutboundDispatchesThisSend,
|
|
95
241
|
onSendStarted: options.onSendStarted,
|
|
96
|
-
onSendProgress:
|
|
242
|
+
onSendProgress: (info) => {
|
|
243
|
+
initialToolCallStarted = true;
|
|
244
|
+
options.onSendProgress?.(info);
|
|
245
|
+
},
|
|
97
246
|
onSendComplete: (info) => {
|
|
98
247
|
sendCount = info.sendCount;
|
|
99
248
|
lastDispatchesThisTurn = info.conductorDispatchesThisTurn;
|
|
100
249
|
autonomousTurns = info.autonomousTurns;
|
|
101
250
|
options.onSendComplete(info);
|
|
102
251
|
},
|
|
252
|
+
}).then((result) => {
|
|
253
|
+
if (options.recoveryBackend === 'cursor' &&
|
|
254
|
+
isIssueDispatchCompleted(result)) {
|
|
255
|
+
markCursorBootstrapCompleted();
|
|
256
|
+
}
|
|
257
|
+
const explicitInterruptQueued = result.status === 'cancelled' &&
|
|
258
|
+
options.eventQueue.snapshot().some((event) => event.type === 'operator.message' &&
|
|
259
|
+
event.delivery === 'interrupt');
|
|
260
|
+
settleAttempt({
|
|
261
|
+
lease: initialLease,
|
|
262
|
+
events: [],
|
|
263
|
+
sourceKey: 'initial',
|
|
264
|
+
sendCount,
|
|
265
|
+
result,
|
|
266
|
+
toolCallStarted: initialToolCallStarted,
|
|
267
|
+
...(explicitInterruptQueued ? { requiresConfirmation: false } : {}),
|
|
268
|
+
});
|
|
269
|
+
return result;
|
|
103
270
|
});
|
|
271
|
+
const awaitedInitialSend = await awaitWithAbort(initialSend, options.shutdownSignal);
|
|
272
|
+
if (awaitedInitialSend.aborted || options.shutdownSignal.aborted) {
|
|
273
|
+
stopReason = 'interrupted';
|
|
274
|
+
return {
|
|
275
|
+
stopReason,
|
|
276
|
+
sendCount,
|
|
277
|
+
lastSendResult,
|
|
278
|
+
autonomousTurns,
|
|
279
|
+
lastDispatchesThisTurn,
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
lastSendResult = awaitedInitialSend.value;
|
|
104
283
|
}
|
|
105
284
|
while (true) {
|
|
285
|
+
if (options.shutdownSignal.aborted) {
|
|
286
|
+
stopReason = 'interrupted';
|
|
287
|
+
break;
|
|
288
|
+
}
|
|
289
|
+
if (options.cursorBootstrapState && cursorBootstrapNeedsDecision()) {
|
|
290
|
+
try {
|
|
291
|
+
await waitForCursorBootstrapDecision({
|
|
292
|
+
state: options.cursorBootstrapState,
|
|
293
|
+
lifecycle,
|
|
294
|
+
eventQueue: options.eventQueue,
|
|
295
|
+
onOperatorQueueChanged: options.onOperatorQueueChanged,
|
|
296
|
+
signal: options.shutdownSignal,
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
catch (error) {
|
|
300
|
+
if (!isAbortError(error))
|
|
301
|
+
throw error;
|
|
302
|
+
stopReason = 'interrupted';
|
|
303
|
+
break;
|
|
304
|
+
}
|
|
305
|
+
if (options.shutdownSignal.aborted) {
|
|
306
|
+
stopReason = 'interrupted';
|
|
307
|
+
break;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
if (lifecycle.confirmationRequired &&
|
|
311
|
+
options.operatorCanInspectRecovery !== true) {
|
|
312
|
+
stopReason =
|
|
313
|
+
lastSendResult.status === 'cancelled' ? 'cancelled' : 'error';
|
|
314
|
+
break;
|
|
315
|
+
}
|
|
106
316
|
if (options.isPermissionPending) {
|
|
107
317
|
pruneStalePermissionEvents({
|
|
108
318
|
state: dispatchHoldState,
|
|
@@ -130,6 +340,10 @@ export async function runConductorSessionDriver(options) {
|
|
|
130
340
|
stopReason = 'interrupted';
|
|
131
341
|
break;
|
|
132
342
|
}
|
|
343
|
+
if (options.shutdownSignal.aborted) {
|
|
344
|
+
stopReason = 'interrupted';
|
|
345
|
+
break;
|
|
346
|
+
}
|
|
133
347
|
lastSendResult = awaited.value;
|
|
134
348
|
inFlightSend = undefined;
|
|
135
349
|
if (lastSendResult.status === 'cancelled' &&
|
|
@@ -167,7 +381,8 @@ export async function runConductorSessionDriver(options) {
|
|
|
167
381
|
const shouldBreak = await shouldBreakAfterLoopState(loopState);
|
|
168
382
|
const idleOperatorMessageReady = queuedDispatch?.batch.events.some((event) => event.type === 'operator.message' && event.delivery === 'idle');
|
|
169
383
|
if (shouldBreak &&
|
|
170
|
-
(!idleOperatorMessageReady ||
|
|
384
|
+
(!idleOperatorMessageReady ||
|
|
385
|
+
options.stopOnUnansweredInput === true)) {
|
|
171
386
|
break;
|
|
172
387
|
}
|
|
173
388
|
continue;
|
|
@@ -199,10 +414,14 @@ export async function runConductorSessionDriver(options) {
|
|
|
199
414
|
dispatchBatchState,
|
|
200
415
|
autonomousTurns,
|
|
201
416
|
maxTurns: options.maxTurns,
|
|
417
|
+
lifecycle,
|
|
418
|
+
recoveryInbox,
|
|
419
|
+
operatorCanInspectRecovery: options.operatorCanInspectRecovery === true,
|
|
202
420
|
signal: options.shutdownSignal,
|
|
203
421
|
dispatchHoldState,
|
|
204
422
|
onDispatchHoldChanged: options.onDispatchHoldChanged,
|
|
205
423
|
onOperatorQueueChanged: options.onOperatorQueueChanged,
|
|
424
|
+
onRecoveryCapacityBlocked: options.onRecoveryCapacityBlocked,
|
|
206
425
|
isPermissionPending: options.isPermissionPending,
|
|
207
426
|
canDispatchIdleOperatorMessage: () => canDispatchIdleOperatorMessage({
|
|
208
427
|
eventQueue: options.eventQueue,
|
|
@@ -224,16 +443,57 @@ export async function runConductorSessionDriver(options) {
|
|
|
224
443
|
if (!dispatchResult) {
|
|
225
444
|
continue;
|
|
226
445
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
446
|
+
if (options.shutdownSignal.aborted) {
|
|
447
|
+
if (dispatchResult.reservation) {
|
|
448
|
+
recoveryInbox.release(dispatchResult.reservation);
|
|
449
|
+
}
|
|
450
|
+
stopReason = 'interrupted';
|
|
451
|
+
break;
|
|
231
452
|
}
|
|
453
|
+
const { events: batch, selected, reservation } = dispatchResult;
|
|
454
|
+
postLoopWaiting = false;
|
|
232
455
|
if (dispatchResult.sourceKey === 'operator.reconnect') {
|
|
456
|
+
const reconnectLease = lifecycle.beginReconnect();
|
|
457
|
+
if (!reconnectLease)
|
|
458
|
+
continue;
|
|
459
|
+
options.eventQueue.removeEvents(batch);
|
|
460
|
+
if (selected) {
|
|
461
|
+
dispatchBatchState = markContinuationConsumed(dispatchBatchState, selected);
|
|
462
|
+
}
|
|
233
463
|
dispatchBatchState = dispatchBatchStateAfterSend(dispatchResult.sourceKey);
|
|
234
|
-
await runOperatorReconnect(options);
|
|
464
|
+
const reconnected = await runOperatorReconnect(options);
|
|
465
|
+
lifecycle.settleReconnect(reconnectLease, reconnected);
|
|
466
|
+
if (reconnected && !options.shutdownSignal.aborted) {
|
|
467
|
+
// 再接続待ちの間に積まれた /reconnect は今回の復旧で満たされた。
|
|
468
|
+
options.eventQueue.findSendEvents((event) => event.type === 'operator.reconnect');
|
|
469
|
+
}
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
const payload = {
|
|
473
|
+
events: batch,
|
|
474
|
+
sourceKey: dispatchResult.sourceKey,
|
|
475
|
+
operatorInputTexts: dispatchResult.operatorInputTexts,
|
|
476
|
+
};
|
|
477
|
+
const lease = lifecycle.beginSend(payload);
|
|
478
|
+
if (!lease) {
|
|
479
|
+
if (reservation)
|
|
480
|
+
recoveryInbox.release(reservation);
|
|
235
481
|
continue;
|
|
236
482
|
}
|
|
483
|
+
if (dispatchResult.fromHeldEvents) {
|
|
484
|
+
const selectedEvents = new Set(batch);
|
|
485
|
+
dispatchHoldState.heldEvents = dispatchHoldState.heldEvents.filter((event) => !selectedEvents.has(event));
|
|
486
|
+
notifyHeldDispatchProgress({
|
|
487
|
+
dispatchHoldState,
|
|
488
|
+
onDispatchHoldChanged: options.onDispatchHoldChanged,
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
else {
|
|
492
|
+
options.eventQueue.removeEvents(batch);
|
|
493
|
+
}
|
|
494
|
+
if (selected) {
|
|
495
|
+
dispatchBatchState = markContinuationConsumed(dispatchBatchState, selected);
|
|
496
|
+
}
|
|
237
497
|
const autonomousTurnsAfter = autonomousTurnsAfterConductorBatch(batch, autonomousTurns);
|
|
238
498
|
const { workerDispatches, workerFailures } = countWorkerOutcomesInBatch(batch);
|
|
239
499
|
options.onOperatorDispatched?.(batch);
|
|
@@ -241,10 +501,17 @@ export async function runConductorSessionDriver(options) {
|
|
|
241
501
|
triggers: batch.filter(isTriggerSessionEvent).length,
|
|
242
502
|
sources: uniqueSources(batch, dispatchResult.sourceKey),
|
|
243
503
|
});
|
|
504
|
+
const eventMessage = formatSessionEventsForConductor(batch);
|
|
505
|
+
const restoreCursorBootstrap = isCursorBootstrapPending();
|
|
506
|
+
const message = restoreCursorBootstrap
|
|
507
|
+
? `${options.initialPrompt}\n\n## 今回のセッションイベント\n\n${eventMessage}`
|
|
508
|
+
: eventMessage;
|
|
509
|
+
let dispatchHadToolCalls = false;
|
|
244
510
|
const dispatchSend = runEventConductorSend({
|
|
245
|
-
message
|
|
511
|
+
message,
|
|
512
|
+
...(restoreCursorBootstrap ? { historyInbound: eventMessage } : {}),
|
|
246
513
|
conductorHandle: options.conductorHandle,
|
|
247
|
-
sendReconnect
|
|
514
|
+
sendReconnect,
|
|
248
515
|
signal: options.shutdownSignal,
|
|
249
516
|
workerDispatches: options.workerDispatches,
|
|
250
517
|
workerFailures: options.workerFailures,
|
|
@@ -259,27 +526,42 @@ export async function runConductorSessionDriver(options) {
|
|
|
259
526
|
resetOutboundDispatchesThisSend: options.resetOutboundDispatchesThisSend,
|
|
260
527
|
getOutboundDispatchesThisSend: options.getOutboundDispatchesThisSend,
|
|
261
528
|
onSendStarted: options.onSendStarted,
|
|
262
|
-
onSendProgress:
|
|
529
|
+
onSendProgress: (info) => {
|
|
530
|
+
dispatchHadToolCalls = true;
|
|
531
|
+
options.onSendProgress?.(info);
|
|
532
|
+
},
|
|
263
533
|
onSendComplete: (info) => {
|
|
264
534
|
sendCount = info.sendCount;
|
|
265
535
|
lastDispatchesThisTurn = info.conductorDispatchesThisTurn;
|
|
266
536
|
autonomousTurns = info.autonomousTurns;
|
|
267
537
|
options.onSendComplete(info);
|
|
268
538
|
},
|
|
269
|
-
})
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
539
|
+
}).then((result) => {
|
|
540
|
+
if (restoreCursorBootstrap && isIssueDispatchCompleted(result)) {
|
|
541
|
+
markCursorBootstrapCompleted();
|
|
542
|
+
}
|
|
543
|
+
const explicitInterruptQueued = result.status === 'cancelled' &&
|
|
544
|
+
options.eventQueue.snapshot().some((event) => event.type === 'operator.message' &&
|
|
545
|
+
event.delivery === 'interrupt');
|
|
546
|
+
settleAttempt({
|
|
547
|
+
lease,
|
|
548
|
+
events: batch,
|
|
549
|
+
sourceKey: dispatchResult.sourceKey,
|
|
550
|
+
sendCount,
|
|
551
|
+
result,
|
|
552
|
+
...(reservation ? { reservation } : {}),
|
|
553
|
+
toolCallStarted: dispatchHadToolCalls,
|
|
554
|
+
...(explicitInterruptQueued
|
|
555
|
+
? { requiresConfirmation: false }
|
|
556
|
+
: {}),
|
|
278
557
|
});
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
558
|
+
if (options.onDispatchBatchSettled &&
|
|
559
|
+
batch.some((event) => event.type === 'operator.message')) {
|
|
560
|
+
options.onDispatchBatchSettled({ events: batch, result });
|
|
561
|
+
}
|
|
562
|
+
return result;
|
|
563
|
+
});
|
|
564
|
+
inFlightSend = dispatchSend;
|
|
283
565
|
dispatchBatchState = dispatchBatchStateAfterSend(dispatchResult.sourceKey);
|
|
284
566
|
}
|
|
285
567
|
if (inFlightSend && stopReason !== 'interrupted') {
|
|
@@ -297,6 +579,19 @@ function uniqueSources(batch, fallback) {
|
|
|
297
579
|
const sources = [...new Set(batch.map((event) => eventSourceKey(event)))];
|
|
298
580
|
return sources.length > 0 ? sources : [fallback];
|
|
299
581
|
}
|
|
582
|
+
function withAgentAvailabilityCallbacks(options, onAvailabilityChanged) {
|
|
583
|
+
return {
|
|
584
|
+
...options,
|
|
585
|
+
onAuthReconnectComplete: (info) => {
|
|
586
|
+
onAvailabilityChanged('auth', info);
|
|
587
|
+
(options.onAuthReconnectComplete ?? options.onReconnectComplete)?.(info);
|
|
588
|
+
},
|
|
589
|
+
onTransportReconnectComplete: (info) => {
|
|
590
|
+
onAvailabilityChanged('transport', info);
|
|
591
|
+
(options.onTransportReconnectComplete ?? options.onReconnectComplete)?.(info);
|
|
592
|
+
},
|
|
593
|
+
};
|
|
594
|
+
}
|
|
300
595
|
function runEventConductorSend(input) {
|
|
301
596
|
const workersBefore = input.workerDispatches.length;
|
|
302
597
|
const failuresBefore = input.workerFailures.length;
|
|
@@ -320,7 +615,14 @@ function runEventConductorSend(input) {
|
|
|
320
615
|
tool: info.tool,
|
|
321
616
|
});
|
|
322
617
|
},
|
|
323
|
-
})
|
|
618
|
+
})
|
|
619
|
+
.catch(() => {
|
|
620
|
+
// Keep sendConductorWithReconnect's public rejection contract. A rejected
|
|
621
|
+
// send has an unknown provider outcome, so this workflow settles it
|
|
622
|
+
// locally without retaining the raw exception or retrying the prompt.
|
|
623
|
+
return { runId: '', status: 'send_rejected' };
|
|
624
|
+
})
|
|
625
|
+
.then((sendResult) => {
|
|
324
626
|
const completedDuringSend = input.workerDispatches.length - workersBefore;
|
|
325
627
|
const outboundDispatches = input.getOutboundDispatchesThisSend?.() ?? 0;
|
|
326
628
|
const conductorDispatches = Math.max(completedDuringSend, outboundDispatches);
|
|
@@ -330,6 +632,9 @@ function runEventConductorSend(input) {
|
|
|
330
632
|
runId: sendResult.runId,
|
|
331
633
|
status: sendResult.status,
|
|
332
634
|
inbound: input.message,
|
|
635
|
+
...(input.historyInbound !== undefined
|
|
636
|
+
? { historyInbound: input.historyInbound }
|
|
637
|
+
: {}),
|
|
333
638
|
dispatchSource: input.dispatchSource,
|
|
334
639
|
result: sendResult.result,
|
|
335
640
|
error: sendResult.error,
|
|
@@ -344,8 +649,33 @@ function runEventConductorSend(input) {
|
|
|
344
649
|
return sendResult;
|
|
345
650
|
});
|
|
346
651
|
}
|
|
652
|
+
function isIssueDispatchCompleted(result) {
|
|
653
|
+
return (result.status === 'finished' ||
|
|
654
|
+
result.status === 'completed' ||
|
|
655
|
+
result.status === 'running');
|
|
656
|
+
}
|
|
347
657
|
async function waitForDispatchBatch(input) {
|
|
658
|
+
let capacityNoticeSent = false;
|
|
348
659
|
for (;;) {
|
|
660
|
+
if (!input.lifecycle.canDispatch) {
|
|
661
|
+
const queuedReconnects = input.eventQueue
|
|
662
|
+
.snapshot()
|
|
663
|
+
.filter((event) => event.type === 'operator.reconnect');
|
|
664
|
+
if (queuedReconnects.length > 0) {
|
|
665
|
+
if (input.eventQueue
|
|
666
|
+
.snapshot()
|
|
667
|
+
.some((event) => event.type === 'operator.message')) {
|
|
668
|
+
input.onOperatorQueueChanged?.();
|
|
669
|
+
}
|
|
670
|
+
return {
|
|
671
|
+
events: queuedReconnects,
|
|
672
|
+
sourceKey: 'operator.reconnect',
|
|
673
|
+
operatorInputTexts: [],
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
await waitForDispatchChange(input);
|
|
677
|
+
continue;
|
|
678
|
+
}
|
|
349
679
|
if (input.dispatchHoldState.dispatchHold) {
|
|
350
680
|
bufferDispatchHoldEvents({
|
|
351
681
|
state: input.dispatchHoldState,
|
|
@@ -368,62 +698,188 @@ async function waitForDispatchBatch(input) {
|
|
|
368
698
|
maxTurns: input.maxTurns,
|
|
369
699
|
allowIdleOperatorMessages: input.canDispatchIdleOperatorMessage(),
|
|
370
700
|
});
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
input.
|
|
701
|
+
const prepareDispatch = (dispatch) => {
|
|
702
|
+
const operatorInputTexts = dispatch.events.flatMap((event) => event.type === 'operator.message'
|
|
703
|
+
? [event.operatorInput ?? event.text]
|
|
704
|
+
: []);
|
|
705
|
+
const reserved = input.operatorCanInspectRecovery
|
|
706
|
+
? input.recoveryInbox.reserve(operatorInputTexts)
|
|
707
|
+
: { ok: true, reservation: undefined };
|
|
708
|
+
if (!reserved.ok) {
|
|
709
|
+
if (reserved.reason === 'input_too_large') {
|
|
710
|
+
throw new Error('An operator input reached dispatch without passing the recovery admission limit.');
|
|
711
|
+
}
|
|
712
|
+
if (!capacityNoticeSent) {
|
|
713
|
+
capacityNoticeSent = true;
|
|
714
|
+
input.onRecoveryCapacityBlocked?.();
|
|
715
|
+
}
|
|
716
|
+
return undefined;
|
|
717
|
+
}
|
|
376
718
|
return {
|
|
719
|
+
events: dispatch.events,
|
|
720
|
+
sourceKey: dispatch.sourceKey,
|
|
721
|
+
operatorInputTexts,
|
|
722
|
+
...(dispatch.selected ? { selected: dispatch.selected } : {}),
|
|
723
|
+
...(reserved.reservation ? { reservation: reserved.reservation } : {}),
|
|
724
|
+
...(dispatch.fromHeldEvents ? { fromHeldEvents: true } : {}),
|
|
725
|
+
};
|
|
726
|
+
};
|
|
727
|
+
// Operator input, reconnect, and permission decisions retain their
|
|
728
|
+
// immediate priority. Lower-priority events must wait while a released hold
|
|
729
|
+
// contains an eligible permission or worker event.
|
|
730
|
+
if (selected && isImmediateDispatchSource(selected.batch.sourceKey)) {
|
|
731
|
+
const dispatch = prepareDispatch({
|
|
377
732
|
events: selected.batch.events,
|
|
378
733
|
sourceKey: selected.batch.sourceKey,
|
|
379
734
|
selected,
|
|
380
|
-
};
|
|
735
|
+
});
|
|
736
|
+
if (dispatch)
|
|
737
|
+
return dispatch;
|
|
738
|
+
await waitForDispatchChange(input);
|
|
739
|
+
continue;
|
|
381
740
|
}
|
|
382
741
|
if (!input.dispatchHoldState.dispatchHold &&
|
|
383
742
|
input.dispatchHoldState.heldEvents.length > 0) {
|
|
384
743
|
const heldEvents = input.dispatchHoldState.heldEvents;
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
744
|
+
const allHeldEventsDispatchable = heldEvents.every((event) => canDispatchConductorSend(event, input.autonomousTurns, input.maxTurns));
|
|
745
|
+
const selectedHeld = allHeldEventsDispatchable
|
|
746
|
+
? undefined
|
|
747
|
+
: selectDispatchBatch({
|
|
748
|
+
queue: heldEvents,
|
|
749
|
+
state: input.dispatchBatchState,
|
|
750
|
+
autonomousTurns: input.autonomousTurns,
|
|
751
|
+
maxTurns: input.maxTurns,
|
|
752
|
+
});
|
|
753
|
+
const selectedHeldBatch = selectedHeld?.batch ??
|
|
754
|
+
(allHeldEventsDispatchable
|
|
755
|
+
? { events: [...heldEvents], sourceKey: 'dispatch-hold' }
|
|
756
|
+
: undefined);
|
|
757
|
+
if (selectedHeldBatch && selectedHeldBatch.events.length > 0) {
|
|
758
|
+
const dispatch = prepareDispatch({
|
|
759
|
+
events: selectedHeldBatch.events,
|
|
760
|
+
sourceKey: selectedHeldBatch.sourceKey,
|
|
761
|
+
...(selectedHeld ? { selected: selectedHeld } : {}),
|
|
762
|
+
fromHeldEvents: true,
|
|
763
|
+
});
|
|
764
|
+
if (dispatch)
|
|
765
|
+
return dispatch;
|
|
766
|
+
await waitForDispatchChange(input);
|
|
767
|
+
continue;
|
|
409
768
|
}
|
|
410
769
|
}
|
|
411
|
-
if (selected) {
|
|
412
|
-
|
|
413
|
-
return {
|
|
770
|
+
if (selected && selected.batch.events.length > 0) {
|
|
771
|
+
const dispatch = prepareDispatch({
|
|
414
772
|
events: selected.batch.events,
|
|
415
773
|
sourceKey: selected.batch.sourceKey,
|
|
416
774
|
selected,
|
|
417
|
-
};
|
|
775
|
+
});
|
|
776
|
+
if (dispatch)
|
|
777
|
+
return dispatch;
|
|
778
|
+
await waitForDispatchChange(input);
|
|
779
|
+
continue;
|
|
780
|
+
}
|
|
781
|
+
await waitForDispatchChange(input);
|
|
782
|
+
}
|
|
783
|
+
}
|
|
784
|
+
function isImmediateDispatchSource(sourceKey) {
|
|
785
|
+
return (sourceKey === 'operator' ||
|
|
786
|
+
sourceKey === 'operator.reconnect' ||
|
|
787
|
+
sourceKey === 'permission');
|
|
788
|
+
}
|
|
789
|
+
function waitForDispatchChange(input) {
|
|
790
|
+
if (input.signal.aborted) {
|
|
791
|
+
return Promise.reject(new DOMException('Conductor dispatch wait aborted', 'AbortError'));
|
|
792
|
+
}
|
|
793
|
+
return new Promise((resolve, reject) => {
|
|
794
|
+
const subscriptions = [];
|
|
795
|
+
const queuedOperatorMessages = input.eventQueue
|
|
796
|
+
.snapshot()
|
|
797
|
+
.filter((event) => event.type === 'operator.message').length;
|
|
798
|
+
const cleanup = () => {
|
|
799
|
+
for (const unsubscribe of subscriptions)
|
|
800
|
+
unsubscribe();
|
|
801
|
+
input.signal.removeEventListener('abort', onAbort);
|
|
802
|
+
};
|
|
803
|
+
const onChange = () => {
|
|
804
|
+
cleanup();
|
|
805
|
+
resolve();
|
|
806
|
+
};
|
|
807
|
+
const onAbort = () => {
|
|
808
|
+
cleanup();
|
|
809
|
+
reject(new DOMException('Conductor dispatch wait aborted', 'AbortError'));
|
|
810
|
+
};
|
|
811
|
+
const onQueueChanged = () => {
|
|
812
|
+
const nextOperatorMessageCount = input.eventQueue
|
|
813
|
+
.snapshot()
|
|
814
|
+
.filter((event) => event.type === 'operator.message').length;
|
|
815
|
+
if (nextOperatorMessageCount > queuedOperatorMessages) {
|
|
816
|
+
input.onOperatorQueueChanged?.();
|
|
817
|
+
}
|
|
818
|
+
onChange();
|
|
819
|
+
};
|
|
820
|
+
subscriptions.push(input.eventQueue.subscribe(onQueueChanged), input.lifecycle.subscribe(onChange), input.recoveryInbox.subscribe(onChange));
|
|
821
|
+
if (input.signal.aborted) {
|
|
822
|
+
onAbort();
|
|
418
823
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
824
|
+
else {
|
|
825
|
+
input.signal.addEventListener('abort', onAbort, { once: true });
|
|
826
|
+
}
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
async function waitForCursorBootstrapDecision(input) {
|
|
830
|
+
while (input.state.status === 'unknown' ||
|
|
831
|
+
input.state.status === 'saving') {
|
|
832
|
+
if (input.signal.aborted) {
|
|
833
|
+
throw new DOMException('Cursor bootstrap decision wait aborted', 'AbortError');
|
|
834
|
+
}
|
|
835
|
+
await new Promise((resolve, reject) => {
|
|
836
|
+
let unsubscribe = () => { };
|
|
837
|
+
let unsubscribeFromEventQueue = () => { };
|
|
838
|
+
let queuedOperatorMessages = input.eventQueue
|
|
839
|
+
.snapshot()
|
|
840
|
+
.filter((event) => event.type === 'operator.message').length;
|
|
841
|
+
if (queuedOperatorMessages > 0) {
|
|
842
|
+
input.onOperatorQueueChanged?.();
|
|
843
|
+
}
|
|
844
|
+
const cleanup = () => {
|
|
845
|
+
unsubscribe();
|
|
846
|
+
unsubscribeFromEventQueue();
|
|
847
|
+
input.signal.removeEventListener('abort', onAbort);
|
|
848
|
+
};
|
|
849
|
+
const finish = () => {
|
|
850
|
+
cleanup();
|
|
851
|
+
resolve();
|
|
852
|
+
};
|
|
853
|
+
const onStatusChanged = () => {
|
|
854
|
+
if (input.state.status === 'unknown' ||
|
|
855
|
+
input.state.status === 'saving') {
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
finish();
|
|
859
|
+
};
|
|
860
|
+
const onQueueChanged = () => {
|
|
861
|
+
const nextOperatorMessageCount = input.eventQueue
|
|
862
|
+
.snapshot()
|
|
863
|
+
.filter((event) => event.type === 'operator.message').length;
|
|
864
|
+
if (nextOperatorMessageCount > queuedOperatorMessages) {
|
|
865
|
+
input.onOperatorQueueChanged?.();
|
|
866
|
+
}
|
|
867
|
+
queuedOperatorMessages = nextOperatorMessageCount;
|
|
868
|
+
};
|
|
869
|
+
const onAbort = () => {
|
|
870
|
+
cleanup();
|
|
871
|
+
reject(new DOMException('Cursor bootstrap decision wait aborted', 'AbortError'));
|
|
872
|
+
};
|
|
873
|
+
unsubscribe = input.lifecycle.subscribe(onStatusChanged);
|
|
874
|
+
unsubscribeFromEventQueue = input.eventQueue.subscribe(onQueueChanged);
|
|
875
|
+
if (input.signal.aborted) {
|
|
876
|
+
onAbort();
|
|
877
|
+
}
|
|
878
|
+
else {
|
|
879
|
+
input.signal.addEventListener('abort', onAbort, { once: true });
|
|
880
|
+
onStatusChanged();
|
|
881
|
+
}
|
|
422
882
|
});
|
|
423
|
-
// waiter 経由のイベントは queue に載らない。到着順を保つため先頭へ戻す。
|
|
424
|
-
input.eventQueue.prependSilent(incoming);
|
|
425
|
-
if (incoming.type === 'operator.message')
|
|
426
|
-
input.onOperatorQueueChanged?.();
|
|
427
883
|
}
|
|
428
884
|
}
|
|
429
885
|
function canDispatchIdleOperatorMessage(input) {
|
|
@@ -451,26 +907,24 @@ function canDispatchIdleOperatorMessage(input) {
|
|
|
451
907
|
maxTurns: input.maxTurns,
|
|
452
908
|
}) === undefined);
|
|
453
909
|
}
|
|
454
|
-
function isImmediateDispatchSource(sourceKey) {
|
|
455
|
-
return (sourceKey === 'operator' ||
|
|
456
|
-
sourceKey === 'operator.reconnect' ||
|
|
457
|
-
sourceKey === 'permission');
|
|
458
|
-
}
|
|
459
910
|
/** `/reconnect` は conductor へ送らず、直近 send 完了後に agent だけ差し替える。 */
|
|
460
911
|
async function runOperatorReconnect(options) {
|
|
461
912
|
try {
|
|
462
913
|
await reconnectConductorAgent(options.conductorHandle, {
|
|
463
914
|
conductorAgentFactory: options.sendReconnect.conductorAgentFactory,
|
|
464
915
|
conductorOptions: options.sendReconnect.conductorOptions,
|
|
916
|
+
signal: options.shutdownSignal,
|
|
465
917
|
onReconnectAttempt: options.sendReconnect.onTransportReconnectAttempt ??
|
|
466
918
|
options.sendReconnect.onReconnectAttempt,
|
|
467
919
|
onReconnectComplete: options.sendReconnect.onTransportReconnectComplete ??
|
|
468
920
|
options.sendReconnect.onReconnectComplete,
|
|
469
921
|
});
|
|
922
|
+
return true;
|
|
470
923
|
}
|
|
471
924
|
catch {
|
|
472
925
|
// Reconnect failures are observable through the callback above. Keep the
|
|
473
926
|
// session alive so the operator can retry or submit another message.
|
|
927
|
+
return false;
|
|
474
928
|
}
|
|
475
929
|
}
|
|
476
930
|
function notifyHeldDispatchProgress(input) {
|