@agents-ensemble/core 0.21.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 +2 -3
- package/dist/conductor/conductor-auth.d.ts.map +1 -1
- package/dist/conductor/conductor-auth.js +5 -2
- 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 +6 -0
- package/dist/conductor/conductor-history.d.ts.map +1 -1
- package/dist/conductor/conductor-history.js +11 -6
- 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 +27 -5
- package/dist/conductor/conductor-send-reconnect.d.ts.map +1 -1
- package/dist/conductor/conductor-send-reconnect.js +88 -39
- package/dist/conductor/conductor-send-reconnect.js.map +1 -1
- package/dist/conductor/conductor-session-driver.d.ts +32 -7
- package/dist/conductor/conductor-session-driver.d.ts.map +1 -1
- package/dist/conductor/conductor-session-driver.js +474 -233
- package/dist/conductor/conductor-session-driver.js.map +1 -1
- package/dist/conductor/conductor-session.d.ts +2 -0
- package/dist/conductor/conductor-session.d.ts.map +1 -1
- package/dist/conductor/conductor-session.js +404 -156
- 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 +15 -6
- package/dist/conductor/index.d.ts.map +1 -1
- package/dist/conductor/index.js +8 -4
- package/dist/conductor/index.js.map +1 -1
- package/dist/conductor/operator-command.d.ts +1 -1
- package/dist/conductor/operator-command.js +1 -1
- package/dist/conductor/operator-command.js.map +1 -1
- package/dist/conductor/operator-input-binding.d.ts +12 -0
- package/dist/conductor/operator-input-binding.d.ts.map +1 -1
- package/dist/conductor/pi-auth-recovery-snapshot-store.d.ts +4 -1
- package/dist/conductor/pi-auth-recovery-snapshot-store.d.ts.map +1 -1
- package/dist/conductor/pi-auth-recovery-snapshot-store.js +4 -1
- package/dist/conductor/pi-auth-recovery-snapshot-store.js.map +1 -1
- 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 +272 -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/session-event.d.ts +15 -2
- 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 +1 -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 +25 -4
- 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 +31 -2
- package/dist/conductor/session/select-dispatch-batch.js.map +1 -1
- package/dist/conductor/session/session-event-queue.d.ts +5 -0
- package/dist/conductor/session/session-event-queue.d.ts.map +1 -1
- package/dist/conductor/session/session-event-queue.js +41 -4
- 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 +4 -2
- 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 +17 -4
- package/dist/operator-runtime.d.ts.map +1 -1
- package/dist/operator-runtime.js +12 -3
- 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 +4 -3
- package/dist/session/index.d.ts.map +1 -1
- package/dist/session/index.js +2 -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 +77 -10
- package/dist/session/session-resume-store.d.ts.map +1 -1
- package/dist/session/session-resume-store.js +346 -83
- package/dist/session/session-resume-store.js.map +1 -1
- package/dist/session/session-snapshot-paths.d.ts +7 -1
- package/dist/session/session-snapshot-paths.d.ts.map +1 -1
- package/dist/session/session-snapshot-paths.js +29 -26
- package/dist/session/session-snapshot-paths.js.map +1 -1
- 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 +13 -1
- package/dist/session/worker-session-history.d.ts.map +1 -1
- package/dist/session/worker-session-history.js +171 -50
- package/dist/session/worker-session-history.js.map +1 -1
- 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,5 +1,6 @@
|
|
|
1
1
|
import { ensureMaxTurnsOpenQuestion } from '../escalation/enqueue-max-turns-question.js';
|
|
2
|
-
import {
|
|
2
|
+
import { ConductorDispatchLifecycle, settleConductorDispatch, } from './conductor-dispatch-lifecycle.js';
|
|
3
|
+
import { ConductorRecoveryInbox } from './conductor-recovery-inbox.js';
|
|
3
4
|
import { reconnectConductorAgent, sendConductorWithReconnect, } from './conductor-send-reconnect.js';
|
|
4
5
|
import { formatSessionEventsForConductor } from './session/format-session-event.js';
|
|
5
6
|
import { bufferDispatchHoldEvents, createDispatchHoldState, pruneStalePermissionEvents, } from './session/dispatch-hold.js';
|
|
@@ -20,15 +21,85 @@ export async function runConductorSessionDriver(options) {
|
|
|
20
21
|
runId: '',
|
|
21
22
|
status: 'finished',
|
|
22
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;
|
|
23
31
|
let inFlightSend;
|
|
24
32
|
let postLoopWaiting = false;
|
|
33
|
+
const lifecycle = options.dispatchLifecycle ??
|
|
34
|
+
new ConductorDispatchLifecycle();
|
|
35
|
+
const recoveryInbox = options.recoveryInbox ?? new ConductorRecoveryInbox();
|
|
25
36
|
const dispatchHoldState = options.dispatchHoldState ?? createDispatchHoldState();
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
+
}
|
|
31
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
|
+
};
|
|
32
103
|
const hasDispatchableWorkerOutcome = () => selectDispatchBatch({
|
|
33
104
|
queue: options.eventQueue.snapshot().filter((event) => event.type === 'worker.completed' || event.type === 'worker.failed'),
|
|
34
105
|
state: dispatchBatchState,
|
|
@@ -45,7 +116,14 @@ export async function runConductorSessionDriver(options) {
|
|
|
45
116
|
const holdBlocksStop = dispatchHoldState.heldEvents.length > 0 &&
|
|
46
117
|
options.stopOnUnansweredInput !== true &&
|
|
47
118
|
loopState.lastStatus !== 'error' &&
|
|
119
|
+
loopState.lastStatus !== 'send_rejected' &&
|
|
48
120
|
loopState.lastStatus !== 'cancelled';
|
|
121
|
+
if (lifecycle.confirmationRequired &&
|
|
122
|
+
options.operatorCanInspectRecovery !== true) {
|
|
123
|
+
stopReason =
|
|
124
|
+
loopState.lastStatus === 'cancelled' ? 'cancelled' : 'error';
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
49
127
|
if (holdBlocksStop) {
|
|
50
128
|
postLoopWaiting = false;
|
|
51
129
|
return false;
|
|
@@ -54,6 +132,13 @@ export async function runConductorSessionDriver(options) {
|
|
|
54
132
|
postLoopWaiting = false;
|
|
55
133
|
return false;
|
|
56
134
|
}
|
|
135
|
+
if (options.operatorCanInspectRecovery && lifecycle.confirmationRequired) {
|
|
136
|
+
if (!postLoopWaiting) {
|
|
137
|
+
postLoopWaiting = true;
|
|
138
|
+
options.onIssueLoopStop?.();
|
|
139
|
+
}
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
57
142
|
// A worker publishes its outcome to the inbox before its runtime round
|
|
58
143
|
// leaves the running state. Drain that notification chain before
|
|
59
144
|
// deciding the session is idle, otherwise the driver can tear down in
|
|
@@ -79,15 +164,69 @@ export async function runConductorSessionDriver(options) {
|
|
|
79
164
|
}
|
|
80
165
|
return false;
|
|
81
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
|
+
}
|
|
82
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;
|
|
83
222
|
options.onConductorInbound?.({
|
|
84
223
|
triggers: 1,
|
|
85
224
|
sources: ['initial'],
|
|
86
225
|
});
|
|
87
|
-
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
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,
|
|
91
230
|
conductorHandle: options.conductorHandle,
|
|
92
231
|
sendReconnect,
|
|
93
232
|
signal: options.shutdownSignal,
|
|
@@ -100,20 +239,80 @@ export async function runConductorSessionDriver(options) {
|
|
|
100
239
|
resetOutboundDispatchesThisSend: options.resetOutboundDispatchesThisSend,
|
|
101
240
|
getOutboundDispatchesThisSend: options.getOutboundDispatchesThisSend,
|
|
102
241
|
onSendStarted: options.onSendStarted,
|
|
103
|
-
onSendProgress:
|
|
242
|
+
onSendProgress: (info) => {
|
|
243
|
+
initialToolCallStarted = true;
|
|
244
|
+
options.onSendProgress?.(info);
|
|
245
|
+
},
|
|
104
246
|
onSendComplete: (info) => {
|
|
105
247
|
sendCount = info.sendCount;
|
|
106
248
|
lastDispatchesThisTurn = info.conductorDispatchesThisTurn;
|
|
107
249
|
autonomousTurns = info.autonomousTurns;
|
|
108
250
|
options.onSendComplete(info);
|
|
109
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;
|
|
110
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;
|
|
111
283
|
}
|
|
112
284
|
while (true) {
|
|
113
285
|
if (options.shutdownSignal.aborted) {
|
|
114
286
|
stopReason = 'interrupted';
|
|
115
287
|
break;
|
|
116
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
|
+
}
|
|
117
316
|
if (options.isPermissionPending) {
|
|
118
317
|
pruneStalePermissionEvents({
|
|
119
318
|
state: dispatchHoldState,
|
|
@@ -182,7 +381,8 @@ export async function runConductorSessionDriver(options) {
|
|
|
182
381
|
const shouldBreak = await shouldBreakAfterLoopState(loopState);
|
|
183
382
|
const idleOperatorMessageReady = queuedDispatch?.batch.events.some((event) => event.type === 'operator.message' && event.delivery === 'idle');
|
|
184
383
|
if (shouldBreak &&
|
|
185
|
-
(!idleOperatorMessageReady ||
|
|
384
|
+
(!idleOperatorMessageReady ||
|
|
385
|
+
options.stopOnUnansweredInput === true)) {
|
|
186
386
|
break;
|
|
187
387
|
}
|
|
188
388
|
continue;
|
|
@@ -214,13 +414,15 @@ export async function runConductorSessionDriver(options) {
|
|
|
214
414
|
dispatchBatchState,
|
|
215
415
|
autonomousTurns,
|
|
216
416
|
maxTurns: options.maxTurns,
|
|
417
|
+
lifecycle,
|
|
418
|
+
recoveryInbox,
|
|
419
|
+
operatorCanInspectRecovery: options.operatorCanInspectRecovery === true,
|
|
217
420
|
signal: options.shutdownSignal,
|
|
218
421
|
dispatchHoldState,
|
|
219
422
|
onDispatchHoldChanged: options.onDispatchHoldChanged,
|
|
220
423
|
onOperatorQueueChanged: options.onOperatorQueueChanged,
|
|
424
|
+
onRecoveryCapacityBlocked: options.onRecoveryCapacityBlocked,
|
|
221
425
|
isPermissionPending: options.isPermissionPending,
|
|
222
|
-
isConductorAvailable: () => conductorAvailable,
|
|
223
|
-
hasPendingRecoveryReport: () => pendingRecoveryReport !== undefined,
|
|
224
426
|
canDispatchIdleOperatorMessage: () => canDispatchIdleOperatorMessage({
|
|
225
427
|
eventQueue: options.eventQueue,
|
|
226
428
|
dispatchHoldState,
|
|
@@ -242,38 +444,56 @@ export async function runConductorSessionDriver(options) {
|
|
|
242
444
|
continue;
|
|
243
445
|
}
|
|
244
446
|
if (options.shutdownSignal.aborted) {
|
|
447
|
+
if (dispatchResult.reservation) {
|
|
448
|
+
recoveryInbox.release(dispatchResult.reservation);
|
|
449
|
+
}
|
|
245
450
|
stopReason = 'interrupted';
|
|
246
451
|
break;
|
|
247
452
|
}
|
|
248
|
-
const { events:
|
|
453
|
+
const { events: batch, selected, reservation } = dispatchResult;
|
|
249
454
|
postLoopWaiting = false;
|
|
250
|
-
if (selected) {
|
|
251
|
-
dispatchBatchState = markContinuationConsumed(dispatchBatchState, selected);
|
|
252
|
-
}
|
|
253
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
|
+
}
|
|
254
463
|
dispatchBatchState = dispatchBatchStateAfterSend(dispatchResult.sourceKey);
|
|
255
464
|
const reconnected = await runOperatorReconnect(options);
|
|
256
|
-
|
|
465
|
+
lifecycle.settleReconnect(reconnectLease, reconnected);
|
|
257
466
|
if (reconnected && !options.shutdownSignal.aborted) {
|
|
258
|
-
//
|
|
259
|
-
// 新しい handle を重ねて close / resume しないよう、ここでまとめて消費する。
|
|
467
|
+
// 再接続待ちの間に積まれた /reconnect は今回の復旧で満たされた。
|
|
260
468
|
options.eventQueue.findSendEvents((event) => event.type === 'operator.reconnect');
|
|
261
|
-
if (pendingAuthRecoveryCandidate) {
|
|
262
|
-
pendingRecoveryReport = {
|
|
263
|
-
type: 'conductor.recovery.report',
|
|
264
|
-
...pendingAuthRecoveryCandidate,
|
|
265
|
-
reconnect: { kind: 'manual', succeeded: true },
|
|
266
|
-
};
|
|
267
|
-
pendingAuthRecoveryCandidate = undefined;
|
|
268
|
-
}
|
|
269
469
|
}
|
|
270
470
|
continue;
|
|
271
471
|
}
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
:
|
|
276
|
-
|
|
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);
|
|
481
|
+
continue;
|
|
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
|
+
}
|
|
277
497
|
const autonomousTurnsAfter = autonomousTurnsAfterConductorBatch(batch, autonomousTurns);
|
|
278
498
|
const { workerDispatches, workerFailures } = countWorkerOutcomesInBatch(batch);
|
|
279
499
|
options.onOperatorDispatched?.(batch);
|
|
@@ -281,18 +501,15 @@ export async function runConductorSessionDriver(options) {
|
|
|
281
501
|
triggers: batch.filter(isTriggerSessionEvent).length,
|
|
282
502
|
sources: uniqueSources(batch, dispatchResult.sourceKey),
|
|
283
503
|
});
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
const message =
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
: message;
|
|
290
|
-
pendingRecoveryReport = undefined;
|
|
291
|
-
pendingAuthRecoveryCandidate = undefined;
|
|
504
|
+
const eventMessage = formatSessionEventsForConductor(batch);
|
|
505
|
+
const restoreCursorBootstrap = isCursorBootstrapPending();
|
|
506
|
+
const message = restoreCursorBootstrap
|
|
507
|
+
? `${options.initialPrompt}\n\n## 今回のセッションイベント\n\n${eventMessage}`
|
|
508
|
+
: eventMessage;
|
|
292
509
|
let dispatchHadToolCalls = false;
|
|
293
|
-
let automaticRetryAttempts = 0;
|
|
294
510
|
const dispatchSend = runEventConductorSend({
|
|
295
511
|
message,
|
|
512
|
+
...(restoreCursorBootstrap ? { historyInbound: eventMessage } : {}),
|
|
296
513
|
conductorHandle: options.conductorHandle,
|
|
297
514
|
sendReconnect,
|
|
298
515
|
signal: options.shutdownSignal,
|
|
@@ -313,9 +530,6 @@ export async function runConductorSessionDriver(options) {
|
|
|
313
530
|
dispatchHadToolCalls = true;
|
|
314
531
|
options.onSendProgress?.(info);
|
|
315
532
|
},
|
|
316
|
-
onAutomaticRetryAttempt: ({ attempt }) => {
|
|
317
|
-
automaticRetryAttempts = attempt;
|
|
318
|
-
},
|
|
319
533
|
onSendComplete: (info) => {
|
|
320
534
|
sendCount = info.sendCount;
|
|
321
535
|
lastDispatchesThisTurn = info.conductorDispatchesThisTurn;
|
|
@@ -323,38 +537,31 @@ export async function runConductorSessionDriver(options) {
|
|
|
323
537
|
options.onSendComplete(info);
|
|
324
538
|
},
|
|
325
539
|
}).then((result) => {
|
|
326
|
-
if (
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
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
|
+
: {}),
|
|
557
|
+
});
|
|
558
|
+
if (options.onDispatchBatchSettled &&
|
|
559
|
+
batch.some((event) => event.type === 'operator.message')) {
|
|
560
|
+
options.onDispatchBatchSettled({ events: batch, result });
|
|
342
561
|
}
|
|
343
562
|
return result;
|
|
344
563
|
});
|
|
345
|
-
|
|
346
|
-
batch.some((event) => event.type === 'operator.message')) {
|
|
347
|
-
inFlightSend = dispatchSend.then((result) => {
|
|
348
|
-
options.onDispatchBatchSettled?.({ events: batch, result });
|
|
349
|
-
return result;
|
|
350
|
-
}, (error) => {
|
|
351
|
-
options.onDispatchBatchSettled?.({ events: batch, error });
|
|
352
|
-
throw error;
|
|
353
|
-
});
|
|
354
|
-
}
|
|
355
|
-
else {
|
|
356
|
-
inFlightSend = dispatchSend;
|
|
357
|
-
}
|
|
564
|
+
inFlightSend = dispatchSend;
|
|
358
565
|
dispatchBatchState = dispatchBatchStateAfterSend(dispatchResult.sourceKey);
|
|
359
566
|
}
|
|
360
567
|
if (inFlightSend && stopReason !== 'interrupted') {
|
|
@@ -372,95 +579,15 @@ function uniqueSources(batch, fallback) {
|
|
|
372
579
|
const sources = [...new Set(batch.map((event) => eventSourceKey(event)))];
|
|
373
580
|
return sources.length > 0 ? sources : [fallback];
|
|
374
581
|
}
|
|
375
|
-
const MAX_RECOVERY_EVENT_REFERENCES = 20;
|
|
376
|
-
function createPiAuthRecoveryCandidate(input) {
|
|
377
|
-
const eventReferences = input.events.map((event, index) => ({
|
|
378
|
-
eventIndex: index + 1,
|
|
379
|
-
...recoveryEventReference(event),
|
|
380
|
-
}));
|
|
381
|
-
return {
|
|
382
|
-
recoveryId: `pi-auth-recovery-${input.sendCount}`,
|
|
383
|
-
failedDispatch: {
|
|
384
|
-
sendCount: input.sendCount,
|
|
385
|
-
runId: input.runId,
|
|
386
|
-
sourceKey: input.sourceKey,
|
|
387
|
-
eventTypes: [...new Set(input.events.map((event) => event.type))],
|
|
388
|
-
eventCount: input.events.length,
|
|
389
|
-
eventReferences: eventReferences.slice(0, MAX_RECOVERY_EVENT_REFERENCES),
|
|
390
|
-
eventReferencesTruncated: eventReferences.length > MAX_RECOVERY_EVENT_REFERENCES,
|
|
391
|
-
},
|
|
392
|
-
failure: {
|
|
393
|
-
kind: 'pi_api_key_resolution',
|
|
394
|
-
automaticRetryAttempts: input.automaticRetryAttempts,
|
|
395
|
-
toolCallStarted: input.toolCallStarted,
|
|
396
|
-
},
|
|
397
|
-
};
|
|
398
|
-
}
|
|
399
|
-
function recoveryEventReference(event) {
|
|
400
|
-
switch (event.type) {
|
|
401
|
-
case 'operator.message':
|
|
402
|
-
return {
|
|
403
|
-
type: event.type,
|
|
404
|
-
reference: `delivery:${event.delivery ?? 'next'}`,
|
|
405
|
-
};
|
|
406
|
-
case 'operator.reconnect':
|
|
407
|
-
return { type: event.type };
|
|
408
|
-
case 'worker.completed':
|
|
409
|
-
return {
|
|
410
|
-
type: event.type,
|
|
411
|
-
reference: `worker:${event.result.name}:${event.result.kind}`,
|
|
412
|
-
};
|
|
413
|
-
case 'worker.failed':
|
|
414
|
-
return {
|
|
415
|
-
type: event.type,
|
|
416
|
-
reference: `worker:${event.failure.name}:${event.failure.kind}`,
|
|
417
|
-
};
|
|
418
|
-
case 'permission.pending':
|
|
419
|
-
return {
|
|
420
|
-
type: event.type,
|
|
421
|
-
reference: `permission:${event.permission.id}`,
|
|
422
|
-
};
|
|
423
|
-
case 'permission.stall':
|
|
424
|
-
return {
|
|
425
|
-
type: event.type,
|
|
426
|
-
reference: summarizeRecoveryReferences('permissions', event.pendingPermissionIds),
|
|
427
|
-
};
|
|
428
|
-
case 'harness.connectivity.recovered':
|
|
429
|
-
return {
|
|
430
|
-
type: event.type,
|
|
431
|
-
reference: `${event.source}:${event.suppressedErrorCount}`,
|
|
432
|
-
};
|
|
433
|
-
case 'conductor.recovery.report':
|
|
434
|
-
return {
|
|
435
|
-
type: event.type,
|
|
436
|
-
reference: `recovery:${event.recoveryId}`,
|
|
437
|
-
};
|
|
438
|
-
case 'github.update':
|
|
439
|
-
return {
|
|
440
|
-
type: event.type,
|
|
441
|
-
reference: summarizeRecoveryReferences('items', event.items.map((item) => item.id)),
|
|
442
|
-
};
|
|
443
|
-
default: {
|
|
444
|
-
const _exhaustive = event;
|
|
445
|
-
return { type: String(_exhaustive) };
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
function summarizeRecoveryReferences(prefix, values) {
|
|
450
|
-
const sampleCount = 5;
|
|
451
|
-
const sample = values.slice(0, sampleCount).join(',');
|
|
452
|
-
const remaining = values.length - sampleCount;
|
|
453
|
-
return `${prefix}:${sample}${remaining > 0 ? `,+${remaining} more` : ''}`;
|
|
454
|
-
}
|
|
455
582
|
function withAgentAvailabilityCallbacks(options, onAvailabilityChanged) {
|
|
456
583
|
return {
|
|
457
584
|
...options,
|
|
458
585
|
onAuthReconnectComplete: (info) => {
|
|
459
|
-
onAvailabilityChanged(info
|
|
586
|
+
onAvailabilityChanged('auth', info);
|
|
460
587
|
(options.onAuthReconnectComplete ?? options.onReconnectComplete)?.(info);
|
|
461
588
|
},
|
|
462
589
|
onTransportReconnectComplete: (info) => {
|
|
463
|
-
onAvailabilityChanged(info
|
|
590
|
+
onAvailabilityChanged('transport', info);
|
|
464
591
|
(options.onTransportReconnectComplete ?? options.onReconnectComplete)?.(info);
|
|
465
592
|
},
|
|
466
593
|
};
|
|
@@ -488,11 +615,14 @@ function runEventConductorSend(input) {
|
|
|
488
615
|
tool: info.tool,
|
|
489
616
|
});
|
|
490
617
|
},
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
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) => {
|
|
496
626
|
const completedDuringSend = input.workerDispatches.length - workersBefore;
|
|
497
627
|
const outboundDispatches = input.getOutboundDispatchesThisSend?.() ?? 0;
|
|
498
628
|
const conductorDispatches = Math.max(completedDuringSend, outboundDispatches);
|
|
@@ -502,6 +632,9 @@ function runEventConductorSend(input) {
|
|
|
502
632
|
runId: sendResult.runId,
|
|
503
633
|
status: sendResult.status,
|
|
504
634
|
inbound: input.message,
|
|
635
|
+
...(input.historyInbound !== undefined
|
|
636
|
+
? { historyInbound: input.historyInbound }
|
|
637
|
+
: {}),
|
|
505
638
|
dispatchSource: input.dispatchSource,
|
|
506
639
|
result: sendResult.result,
|
|
507
640
|
error: sendResult.error,
|
|
@@ -516,10 +649,18 @@ function runEventConductorSend(input) {
|
|
|
516
649
|
return sendResult;
|
|
517
650
|
});
|
|
518
651
|
}
|
|
652
|
+
function isIssueDispatchCompleted(result) {
|
|
653
|
+
return (result.status === 'finished' ||
|
|
654
|
+
result.status === 'completed' ||
|
|
655
|
+
result.status === 'running');
|
|
656
|
+
}
|
|
519
657
|
async function waitForDispatchBatch(input) {
|
|
658
|
+
let capacityNoticeSent = false;
|
|
520
659
|
for (;;) {
|
|
521
|
-
if (!input.
|
|
522
|
-
const queuedReconnects = input.eventQueue
|
|
660
|
+
if (!input.lifecycle.canDispatch) {
|
|
661
|
+
const queuedReconnects = input.eventQueue
|
|
662
|
+
.snapshot()
|
|
663
|
+
.filter((event) => event.type === 'operator.reconnect');
|
|
523
664
|
if (queuedReconnects.length > 0) {
|
|
524
665
|
if (input.eventQueue
|
|
525
666
|
.snapshot()
|
|
@@ -529,20 +670,11 @@ async function waitForDispatchBatch(input) {
|
|
|
529
670
|
return {
|
|
530
671
|
events: queuedReconnects,
|
|
531
672
|
sourceKey: 'operator.reconnect',
|
|
673
|
+
operatorInputTexts: [],
|
|
532
674
|
};
|
|
533
675
|
}
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
reconnect,
|
|
537
|
-
...input.eventQueue.findSendEvents((event) => event.type === 'operator.reconnect'),
|
|
538
|
-
];
|
|
539
|
-
if (input.eventQueue.snapshot().some((event) => event.type === 'operator.message')) {
|
|
540
|
-
input.onOperatorQueueChanged?.();
|
|
541
|
-
}
|
|
542
|
-
return {
|
|
543
|
-
events: reconnects,
|
|
544
|
-
sourceKey: 'operator.reconnect',
|
|
545
|
-
};
|
|
676
|
+
await waitForDispatchChange(input);
|
|
677
|
+
continue;
|
|
546
678
|
}
|
|
547
679
|
if (input.dispatchHoldState.dispatchHold) {
|
|
548
680
|
bufferDispatchHoldEvents({
|
|
@@ -566,75 +698,188 @@ async function waitForDispatchBatch(input) {
|
|
|
566
698
|
maxTurns: input.maxTurns,
|
|
567
699
|
allowIdleOperatorMessages: input.canDispatchIdleOperatorMessage(),
|
|
568
700
|
});
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
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
|
+
}
|
|
574
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({
|
|
575
732
|
events: selected.batch.events,
|
|
576
733
|
sourceKey: selected.batch.sourceKey,
|
|
577
734
|
selected,
|
|
578
|
-
};
|
|
735
|
+
});
|
|
736
|
+
if (dispatch)
|
|
737
|
+
return dispatch;
|
|
738
|
+
await waitForDispatchChange(input);
|
|
739
|
+
continue;
|
|
579
740
|
}
|
|
580
741
|
if (!input.dispatchHoldState.dispatchHold &&
|
|
581
742
|
input.dispatchHoldState.heldEvents.length > 0) {
|
|
582
743
|
const heldEvents = input.dispatchHoldState.heldEvents;
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
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;
|
|
607
768
|
}
|
|
608
769
|
}
|
|
609
|
-
if (selected) {
|
|
610
|
-
|
|
611
|
-
return {
|
|
770
|
+
if (selected && selected.batch.events.length > 0) {
|
|
771
|
+
const dispatch = prepareDispatch({
|
|
612
772
|
events: selected.batch.events,
|
|
613
773
|
sourceKey: selected.batch.sourceKey,
|
|
614
774
|
selected,
|
|
615
|
-
};
|
|
775
|
+
});
|
|
776
|
+
if (dispatch)
|
|
777
|
+
return dispatch;
|
|
778
|
+
await waitForDispatchChange(input);
|
|
779
|
+
continue;
|
|
616
780
|
}
|
|
617
|
-
|
|
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
|
|
618
796
|
.snapshot()
|
|
619
|
-
.
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
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();
|
|
823
|
+
}
|
|
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');
|
|
629
834
|
}
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
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
|
+
}
|
|
633
882
|
});
|
|
634
|
-
// waiter 経由のイベントは queue に載らない。到着順を保つため先頭へ戻す。
|
|
635
|
-
input.eventQueue.prependSilent(incoming);
|
|
636
|
-
if (incoming.type === 'operator.message')
|
|
637
|
-
input.onOperatorQueueChanged?.();
|
|
638
883
|
}
|
|
639
884
|
}
|
|
640
885
|
function canDispatchIdleOperatorMessage(input) {
|
|
@@ -662,17 +907,13 @@ function canDispatchIdleOperatorMessage(input) {
|
|
|
662
907
|
maxTurns: input.maxTurns,
|
|
663
908
|
}) === undefined);
|
|
664
909
|
}
|
|
665
|
-
function isImmediateDispatchSource(sourceKey) {
|
|
666
|
-
return (sourceKey === 'operator' ||
|
|
667
|
-
sourceKey === 'operator.reconnect' ||
|
|
668
|
-
sourceKey === 'permission');
|
|
669
|
-
}
|
|
670
910
|
/** `/reconnect` は conductor へ送らず、直近 send 完了後に agent だけ差し替える。 */
|
|
671
911
|
async function runOperatorReconnect(options) {
|
|
672
912
|
try {
|
|
673
913
|
await reconnectConductorAgent(options.conductorHandle, {
|
|
674
914
|
conductorAgentFactory: options.sendReconnect.conductorAgentFactory,
|
|
675
915
|
conductorOptions: options.sendReconnect.conductorOptions,
|
|
916
|
+
signal: options.shutdownSignal,
|
|
676
917
|
onReconnectAttempt: options.sendReconnect.onTransportReconnectAttempt ??
|
|
677
918
|
options.sendReconnect.onReconnectAttempt,
|
|
678
919
|
onReconnectComplete: options.sendReconnect.onTransportReconnectComplete ??
|