@agents-ensemble/core 0.13.0 → 0.14.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/acp/acp-client.d.ts.map +1 -1
- package/dist/acp/acp-client.js +19 -1
- package/dist/acp/acp-client.js.map +1 -1
- package/dist/acp/testing/fake-acp-server.d.ts +3 -2
- package/dist/acp/testing/fake-acp-server.d.ts.map +1 -1
- package/dist/acp/testing/fake-acp-server.js +1 -0
- package/dist/acp/testing/fake-acp-server.js.map +1 -1
- package/dist/conductor/conductor-agent.d.ts +2 -0
- package/dist/conductor/conductor-agent.d.ts.map +1 -1
- package/dist/conductor/conductor-send-reconnect.d.ts +3 -0
- package/dist/conductor/conductor-send-reconnect.d.ts.map +1 -1
- package/dist/conductor/conductor-send-reconnect.js +7 -1
- package/dist/conductor/conductor-send-reconnect.js.map +1 -1
- package/dist/conductor/conductor-session-driver.d.ts +6 -0
- package/dist/conductor/conductor-session-driver.d.ts.map +1 -1
- package/dist/conductor/conductor-session-driver.js +76 -2
- 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 +414 -62
- package/dist/conductor/conductor-session.js.map +1 -1
- package/dist/conductor/cursor-sdk-conductor-agent.d.ts +3 -0
- package/dist/conductor/cursor-sdk-conductor-agent.d.ts.map +1 -1
- package/dist/conductor/cursor-sdk-conductor-agent.js +48 -15
- package/dist/conductor/cursor-sdk-conductor-agent.js.map +1 -1
- package/dist/conductor/index.d.ts +3 -1
- package/dist/conductor/index.d.ts.map +1 -1
- package/dist/conductor/index.js +1 -0
- package/dist/conductor/index.js.map +1 -1
- package/dist/conductor/operator-command.d.ts +22 -0
- package/dist/conductor/operator-command.d.ts.map +1 -1
- package/dist/conductor/operator-command.js +29 -0
- package/dist/conductor/operator-command.js.map +1 -1
- package/dist/conductor/operator-input-binding.d.ts +3 -0
- package/dist/conductor/operator-input-binding.d.ts.map +1 -1
- package/dist/conductor/pi-conductor-agent.d.ts +1 -0
- package/dist/conductor/pi-conductor-agent.d.ts.map +1 -1
- package/dist/conductor/pi-conductor-agent.js +5 -0
- package/dist/conductor/pi-conductor-agent.js.map +1 -1
- 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 +2 -0
- package/dist/conductor/session/events/session-event.d.ts.map +1 -1
- package/dist/conductor/session/events/session-log-event.d.ts +3 -1
- package/dist/conductor/session/events/session-log-event.d.ts.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 +8 -5
- package/dist/conductor/session/select-dispatch-batch.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/session-logger.d.ts +1 -0
- package/dist/conductor/session/session-logger.d.ts.map +1 -1
- package/dist/conductor/session/session-logger.js +4 -0
- package/dist/conductor/session/session-logger.js.map +1 -1
- package/dist/conductor/submit-operator-input.d.ts +15 -1
- package/dist/conductor/submit-operator-input.d.ts.map +1 -1
- package/dist/conductor/submit-operator-input.js +29 -5
- package/dist/conductor/submit-operator-input.js.map +1 -1
- package/dist/dispatch/attach-worker.d.ts +2 -1
- package/dist/dispatch/attach-worker.d.ts.map +1 -1
- package/dist/dispatch/attach-worker.js +1 -0
- package/dist/dispatch/attach-worker.js.map +1 -1
- package/dist/dispatch/report-session-state-tool.d.ts +3 -1
- package/dist/dispatch/report-session-state-tool.d.ts.map +1 -1
- package/dist/dispatch/report-session-state-tool.js +2 -1
- package/dist/dispatch/report-session-state-tool.js.map +1 -1
- package/dist/escalation/apply-operator-message.d.ts +2 -0
- package/dist/escalation/apply-operator-message.d.ts.map +1 -1
- package/dist/escalation/apply-operator-message.js +2 -0
- package/dist/escalation/apply-operator-message.js.map +1 -1
- package/dist/escalation/open-question.d.ts +1 -0
- package/dist/escalation/open-question.d.ts.map +1 -1
- package/dist/escalation/open-question.js +1 -1
- package/dist/escalation/open-question.js.map +1 -1
- package/dist/escalation/record-open-question-answer.d.ts +1 -0
- package/dist/escalation/record-open-question-answer.d.ts.map +1 -1
- package/dist/escalation/record-open-question-answer.js +1 -0
- package/dist/escalation/record-open-question-answer.js.map +1 -1
- package/dist/github/github-auth.d.ts +4 -0
- package/dist/github/github-auth.d.ts.map +1 -1
- package/dist/github/github-auth.js +37 -0
- package/dist/github/github-auth.js.map +1 -1
- package/dist/github/github-monitor-error.d.ts +1 -1
- package/dist/github/github-monitor-error.d.ts.map +1 -1
- package/dist/github/github-monitor-error.js +13 -1
- package/dist/github/github-monitor-error.js.map +1 -1
- package/dist/github/github-monitor.d.ts +3 -3
- package/dist/github/github-monitor.d.ts.map +1 -1
- package/dist/github/github-monitor.js +3 -3
- package/dist/github/github-monitor.js.map +1 -1
- package/dist/github/index.d.ts +1 -1
- package/dist/github/index.d.ts.map +1 -1
- package/dist/github/index.js +1 -1
- package/dist/github/index.js.map +1 -1
- package/dist/github/register-github-watch-tool.d.ts +4 -1
- package/dist/github/register-github-watch-tool.d.ts.map +1 -1
- package/dist/github/register-github-watch-tool.js +17 -1
- package/dist/github/register-github-watch-tool.js.map +1 -1
- package/dist/github/unregister-github-watch-tool.d.ts +4 -1
- package/dist/github/unregister-github-watch-tool.d.ts.map +1 -1
- package/dist/github/unregister-github-watch-tool.js +14 -3
- package/dist/github/unregister-github-watch-tool.js.map +1 -1
- package/dist/operator-runtime.d.ts +9 -2
- package/dist/operator-runtime.d.ts.map +1 -1
- package/dist/operator-runtime.js +6 -1
- package/dist/operator-runtime.js.map +1 -1
- package/dist/prompt/build-worker-attach-prompt.d.ts +2 -1
- package/dist/prompt/build-worker-attach-prompt.d.ts.map +1 -1
- package/dist/prompt/build-worker-attach-prompt.js +4 -1
- package/dist/prompt/build-worker-attach-prompt.js.map +1 -1
- package/dist/prompt/build-worker-prompt.d.ts +2 -1
- package/dist/prompt/build-worker-prompt.d.ts.map +1 -1
- package/dist/prompt/build-worker-prompt.js +4 -1
- package/dist/prompt/build-worker-prompt.js.map +1 -1
- package/dist/prompt/load-user-instructions.d.ts +1 -1
- package/dist/prompt/load-user-instructions.js +1 -1
- package/dist/prompt/modules/ensemble/worker-base-module.d.ts.map +1 -1
- package/dist/prompt/modules/ensemble/worker-base-module.js +7 -0
- package/dist/prompt/modules/ensemble/worker-base-module.js.map +1 -1
- package/dist/runtime/worker-runtime.d.ts +2 -0
- package/dist/runtime/worker-runtime.d.ts.map +1 -1
- package/dist/runtime/worker-runtime.js +1 -0
- 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 +3 -0
- package/dist/runtime/worker-session.js.map +1 -1
- package/dist/session/index.d.ts +5 -1
- package/dist/session/index.d.ts.map +1 -1
- package/dist/session/index.js +2 -0
- package/dist/session/index.js.map +1 -1
- package/dist/session/open-question-control.d.ts +73 -0
- package/dist/session/open-question-control.d.ts.map +1 -0
- package/dist/session/open-question-control.js +440 -0
- package/dist/session/open-question-control.js.map +1 -0
- package/dist/session/open-questions-store.d.ts.map +1 -1
- package/dist/session/open-questions-store.js +6 -3
- package/dist/session/open-questions-store.js.map +1 -1
- package/dist/session/session-sidecar.d.ts +5 -0
- package/dist/session/session-sidecar.d.ts.map +1 -1
- package/dist/session/session-sidecar.js +24 -3
- package/dist/session/session-sidecar.js.map +1 -1
- package/dist/session/session-state-store.d.ts +21 -0
- package/dist/session/session-state-store.d.ts.map +1 -0
- package/dist/session/session-state-store.js +131 -0
- package/dist/session/session-state-store.js.map +1 -0
- package/package.json +2 -2
|
@@ -4,7 +4,7 @@ import { createAnswerOpenQuestionTool } from '../escalation/answer-open-question
|
|
|
4
4
|
import { createAskHumanTool } from '../escalation/ask-human-tool.js';
|
|
5
5
|
import { createOpenQuestionListTools } from '../escalation/open-question-list-tools.js';
|
|
6
6
|
import { openQuestionToEscalationRecord } from '../escalation/open-question-to-escalation.js';
|
|
7
|
-
import { OpenQuestionRegistry } from '../escalation/open-question.js';
|
|
7
|
+
import { OpenQuestionRegistry, } from '../escalation/open-question.js';
|
|
8
8
|
import { deny } from '../permission/permission-broker.js';
|
|
9
9
|
import { PermissionPipeline } from '../permission/permission-pipeline.js';
|
|
10
10
|
import { createPermissionDeadlockMonitor, } from '../permission/permission-deadlock-monitor.js';
|
|
@@ -36,13 +36,16 @@ import { runConductorSessionDriver } from './conductor-session-driver.js';
|
|
|
36
36
|
import { isOperatorExitCommand } from './operator-exit.js';
|
|
37
37
|
import { isOperatorCloseCommand } from './operator-close.js';
|
|
38
38
|
import { isOperatorReconnectCommand } from './operator-reconnect.js';
|
|
39
|
+
import { parseOperatorMessageDelivery } from './operator-command.js';
|
|
39
40
|
import { assertSessionSidecarMatches, requireSessionSidecarForResume, saveSessionSidecar, SESSION_SIDECAR_VERSION, sessionSidecarPath, } from '../session/session-sidecar.js';
|
|
41
|
+
import { saveSessionStatusLine, sessionStateStorePath, loadSessionStateStore, } from '../session/session-state-store.js';
|
|
40
42
|
import { loadToolCallSnapshotHistory, pruneExpiredToolCallSnapshotHistoryFiles, saveToolCallSnapshotHistory, toolCallSnapshotHistoryPath, } from '../session/tool-call-snapshot-history.js';
|
|
41
43
|
import { loadOpenQuestionsStore, openQuestionsStorePath, saveOpenQuestionsStore, } from '../session/open-questions-store.js';
|
|
44
|
+
import { OpenQuestionControlOperationError, startOpenQuestionControlServer, } from '../session/open-question-control.js';
|
|
42
45
|
import { formatPermissionSummaryForOperator } from '../permission/format-permission-summary-for-operator.js';
|
|
43
46
|
import { enrichSessionUsageWithCost } from '../usage/enrich-session-usage-cost.js';
|
|
44
47
|
import { SessionUsageTracker } from '../usage/session-usage-tracker.js';
|
|
45
|
-
import { submitOperatorInput } from './submit-operator-input.js';
|
|
48
|
+
import { enqueueOperatorMessage as enqueueOperatorMessageEvent, submitOperatorInput, } from './submit-operator-input.js';
|
|
46
49
|
import { createGitHubMonitor, } from '../github/github-monitor.js';
|
|
47
50
|
import { resolveConductorBackendSetting, resolveConductorPiResourcePaths, resolveGitHubMonitorEnabled, } from '../config/resolve-settings.js';
|
|
48
51
|
import { GitHubMonitorError } from '../github/github-monitor-error.js';
|
|
@@ -54,6 +57,12 @@ import { createRegisterGitHubWatchTool } from '../github/register-github-watch-t
|
|
|
54
57
|
import { createUnregisterGitHubWatchTool } from '../github/unregister-github-watch-tool.js';
|
|
55
58
|
import { compileConductorSystemPrompt } from '../prompt/compile-system-prompt.js';
|
|
56
59
|
import { loadUserInstructions } from '../prompt/load-user-instructions.js';
|
|
60
|
+
const OPEN_QUESTION_CONTROL_DRAIN_TIMEOUT_MS = 5_000;
|
|
61
|
+
const RECORDED_ANSWER_UNKNOWN_DISPATCH = {
|
|
62
|
+
outcome: 'unknown',
|
|
63
|
+
answerStatus: 'recorded',
|
|
64
|
+
dispatchStatus: 'unknown',
|
|
65
|
+
};
|
|
57
66
|
export async function runConductorSession(options) {
|
|
58
67
|
const userInstructions = await loadUserInstructions({
|
|
59
68
|
userEnsembleRoot: options.mcpConfigOptions?.userEnsembleRoot,
|
|
@@ -79,7 +88,12 @@ export async function runConductorSession(options) {
|
|
|
79
88
|
}
|
|
80
89
|
const escalations = [];
|
|
81
90
|
const openQuestions = new OpenQuestionRegistry();
|
|
91
|
+
let harnessSocketPath;
|
|
92
|
+
let openQuestionControlServer;
|
|
93
|
+
let lastConductorMessage = null;
|
|
82
94
|
const eventQueue = new SessionEventQueue();
|
|
95
|
+
const openQuestionDispatchReceipts = new WeakMap();
|
|
96
|
+
const pendingOpenQuestionDispatchReceipts = new Set();
|
|
83
97
|
const dispatchHoldState = createDispatchHoldState();
|
|
84
98
|
const onDispatchHoldChanged = (change) => {
|
|
85
99
|
sessionLogger.emit({
|
|
@@ -105,11 +119,11 @@ export async function runConductorSession(options) {
|
|
|
105
119
|
let resumeToolCallSnapshots = [];
|
|
106
120
|
let toolCallHistoryNeedsRewrite = false;
|
|
107
121
|
if (options.resumeAgentId) {
|
|
108
|
-
const
|
|
122
|
+
const loadedSidecar = await requireSessionSidecarForResume({
|
|
109
123
|
repoRoot: options.repoRoot,
|
|
110
124
|
conductorAgentId: options.resumeAgentId,
|
|
111
125
|
});
|
|
112
|
-
assertSessionSidecarMatches(
|
|
126
|
+
assertSessionSidecarMatches(loadedSidecar, {
|
|
113
127
|
conductorAgentId: options.resumeAgentId,
|
|
114
128
|
issueUrl: options.issueUrl,
|
|
115
129
|
repoRoot: options.repoRoot,
|
|
@@ -117,6 +131,20 @@ export async function runConductorSession(options) {
|
|
|
117
131
|
mcpConfigDigest,
|
|
118
132
|
mcpConfigConfigured,
|
|
119
133
|
});
|
|
134
|
+
// A previously completed lifecycle no longer describes this instance once
|
|
135
|
+
// resume begins. Persist unknown before restoring or attaching anything so
|
|
136
|
+
// a crash before the first regular sidecar flush cannot leave stale
|
|
137
|
+
// `closed` / `exited` data behind.
|
|
138
|
+
const sidecar = {
|
|
139
|
+
...loadedSidecar,
|
|
140
|
+
lifecycle: 'unknown',
|
|
141
|
+
harnessSocketPath: undefined,
|
|
142
|
+
updatedAt: Date.now(),
|
|
143
|
+
};
|
|
144
|
+
await saveSessionSidecar(sessionSidecarPath({
|
|
145
|
+
repoRoot: options.repoRoot,
|
|
146
|
+
conductorAgentId: options.resumeAgentId,
|
|
147
|
+
}), sidecar);
|
|
120
148
|
const history = await loadToolCallSnapshotHistory({
|
|
121
149
|
repoRoot: options.repoRoot,
|
|
122
150
|
conductorAgentId: options.resumeAgentId,
|
|
@@ -195,12 +223,62 @@ export async function runConductorSession(options) {
|
|
|
195
223
|
? new AbortController()
|
|
196
224
|
: undefined;
|
|
197
225
|
const shutdownSignal = options.shutdownSignal ?? shutdownController.signal;
|
|
226
|
+
const shutdownIntentController = new AbortController();
|
|
198
227
|
const operatorTerminationController = new AbortController();
|
|
228
|
+
const drainedShutdownController = new AbortController();
|
|
199
229
|
const driverShutdownSignal = AbortSignal.any([
|
|
200
|
-
|
|
230
|
+
drainedShutdownController.signal,
|
|
201
231
|
operatorTerminationController.signal,
|
|
202
232
|
]);
|
|
233
|
+
const abortAfterOpenQuestionDrain = (controller) => {
|
|
234
|
+
const activeControlServer = openQuestionControlServer;
|
|
235
|
+
if (!activeControlServer) {
|
|
236
|
+
controller.abort();
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
const reportDrainFailure = (error) => {
|
|
240
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
241
|
+
sessionLogger.emit({
|
|
242
|
+
type: 'harness.warning',
|
|
243
|
+
message: `Open Question control drain failed: ${detail}`,
|
|
244
|
+
});
|
|
245
|
+
};
|
|
246
|
+
const quiescing = activeControlServer.quiesce();
|
|
247
|
+
if (!activeControlServer.hasInFlightRequests()) {
|
|
248
|
+
void quiescing.catch(reportDrainFailure);
|
|
249
|
+
controller.abort();
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
let drainTimeout;
|
|
253
|
+
const drainDeadline = new Promise((resolve) => {
|
|
254
|
+
drainTimeout = setTimeout(() => resolve('timeout'), OPEN_QUESTION_CONTROL_DRAIN_TIMEOUT_MS);
|
|
255
|
+
});
|
|
256
|
+
const drainResult = quiescing.then(() => 'drained', (error) => {
|
|
257
|
+
reportDrainFailure(error);
|
|
258
|
+
return 'failed';
|
|
259
|
+
});
|
|
260
|
+
void Promise.race([drainResult, drainDeadline]).then((result) => {
|
|
261
|
+
if (drainTimeout)
|
|
262
|
+
clearTimeout(drainTimeout);
|
|
263
|
+
if (result === 'timeout') {
|
|
264
|
+
sessionLogger.emit({
|
|
265
|
+
type: 'harness.warning',
|
|
266
|
+
message: `Open Question control drain exceeded ${OPEN_QUESTION_CONTROL_DRAIN_TIMEOUT_MS}ms; aborting conductor dispatch. Accepted answers may have unknown notification outcomes.`,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
controller.abort();
|
|
270
|
+
});
|
|
271
|
+
};
|
|
272
|
+
const onShutdownSignal = () => {
|
|
273
|
+
shutdownIntentController.abort();
|
|
274
|
+
abortAfterOpenQuestionDrain(drainedShutdownController);
|
|
275
|
+
};
|
|
276
|
+
shutdownSignal.addEventListener('abort', onShutdownSignal, { once: true });
|
|
277
|
+
if (shutdownSignal.aborted)
|
|
278
|
+
onShutdownSignal();
|
|
203
279
|
let unregisterProcessSignalHandlers = () => { };
|
|
280
|
+
const removeShutdownDrainListener = () => shutdownSignal.removeEventListener('abort', onShutdownSignal);
|
|
281
|
+
unregisterProcessSignalHandlers = removeShutdownDrainListener;
|
|
204
282
|
if (ownsShutdownController &&
|
|
205
283
|
options.registerProcessSignalHandlers !== false) {
|
|
206
284
|
const onSignal = () => shutdownController.abort();
|
|
@@ -209,11 +287,13 @@ export async function runConductorSession(options) {
|
|
|
209
287
|
unregisterProcessSignalHandlers = () => {
|
|
210
288
|
process.off('SIGINT', onSignal);
|
|
211
289
|
process.off('SIGTERM', onSignal);
|
|
290
|
+
removeShutdownDrainListener();
|
|
212
291
|
};
|
|
213
292
|
}
|
|
214
293
|
const permissionPipeline = options.permissionPipeline ??
|
|
215
294
|
new PermissionPipeline({ policy: options.permissionPolicy });
|
|
216
295
|
let scheduleSidecarFlush = () => { };
|
|
296
|
+
let flushSidecar = async () => { };
|
|
217
297
|
let scheduleToolCallHistoryFlush = () => { };
|
|
218
298
|
let flushToolCallHistory = async () => { };
|
|
219
299
|
let toolCallHistoryDirty = toolCallHistoryNeedsRewrite;
|
|
@@ -279,6 +359,7 @@ export async function runConductorSession(options) {
|
|
|
279
359
|
workers,
|
|
280
360
|
lazyWorkers: activeLazyWorkers,
|
|
281
361
|
sessionState,
|
|
362
|
+
...(userInstructions ? { userInstructions } : {}),
|
|
282
363
|
restoredWorkerSessions: Object.fromEntries(workerSessions),
|
|
283
364
|
permissionPipeline,
|
|
284
365
|
...(options.connectAcp ? { connectAcp: options.connectAcp } : {}),
|
|
@@ -535,9 +616,12 @@ export async function runConductorSession(options) {
|
|
|
535
616
|
githubMonitorCursor ??= emptyGitHubMonitorCursor();
|
|
536
617
|
return githubMonitorCursor;
|
|
537
618
|
},
|
|
538
|
-
onRegistered: (registration) => {
|
|
619
|
+
onRegistered: async (registration) => {
|
|
539
620
|
githubMonitor?.registerPullRequest?.(registration);
|
|
540
|
-
|
|
621
|
+
// Do not report success to the conductor until the explicit watch is
|
|
622
|
+
// durable. This closes the crash window between the tool result and a
|
|
623
|
+
// later best-effort flush.
|
|
624
|
+
await flushSidecar();
|
|
541
625
|
},
|
|
542
626
|
});
|
|
543
627
|
const unregisterGitHubWatchTools = createUnregisterGitHubWatchTool({
|
|
@@ -546,9 +630,9 @@ export async function runConductorSession(options) {
|
|
|
546
630
|
githubMonitorCursor ??= emptyGitHubMonitorCursor();
|
|
547
631
|
return githubMonitorCursor;
|
|
548
632
|
},
|
|
549
|
-
onUnregistered: (prNumber) => {
|
|
633
|
+
onUnregistered: async (prNumber) => {
|
|
550
634
|
githubMonitor?.unregisterPullRequest?.(prNumber);
|
|
551
|
-
|
|
635
|
+
await flushSidecar();
|
|
552
636
|
},
|
|
553
637
|
});
|
|
554
638
|
let conductorAgent;
|
|
@@ -560,7 +644,20 @@ export async function runConductorSession(options) {
|
|
|
560
644
|
return usage.cost;
|
|
561
645
|
},
|
|
562
646
|
});
|
|
563
|
-
|
|
647
|
+
let sessionStateWriteQueue = Promise.resolve();
|
|
648
|
+
const reportSessionStateTool = createReportSessionStateTool({
|
|
649
|
+
onReport: (statusLine) => {
|
|
650
|
+
if (teardownStarted) {
|
|
651
|
+
throw new Error('report_session_state is unavailable during teardown');
|
|
652
|
+
}
|
|
653
|
+
const write = sessionStateWriteQueue.then(() => saveSessionStatusLine(sessionStateStorePath({
|
|
654
|
+
repoRoot: options.repoRoot,
|
|
655
|
+
conductorAgentId: conductorAgent.agentId,
|
|
656
|
+
}), statusLine));
|
|
657
|
+
sessionStateWriteQueue = write.catch(() => { });
|
|
658
|
+
return write;
|
|
659
|
+
},
|
|
660
|
+
});
|
|
564
661
|
const dispatchHoldTools = createSetDispatchHoldTool({
|
|
565
662
|
state: dispatchHoldState,
|
|
566
663
|
onChanged: onDispatchHoldChanged,
|
|
@@ -632,6 +729,7 @@ export async function runConductorSession(options) {
|
|
|
632
729
|
const sendReconnect = {
|
|
633
730
|
conductorAgentFactory,
|
|
634
731
|
conductorOptions,
|
|
732
|
+
retrySuppressionSignal: shutdownIntentController.signal,
|
|
635
733
|
onAuthReconnectAttempt: ({ agentId }) => {
|
|
636
734
|
sessionLogger.emit({
|
|
637
735
|
type: 'conductor.auth.reconnect',
|
|
@@ -655,46 +753,59 @@ export async function runConductorSession(options) {
|
|
|
655
753
|
},
|
|
656
754
|
};
|
|
657
755
|
let sidecarFlushQueue = Promise.resolve();
|
|
658
|
-
let
|
|
659
|
-
|
|
660
|
-
const
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
version: SESSION_SIDECAR_VERSION,
|
|
672
|
-
conductorAgentId: conductorHandle.conductor.agentId,
|
|
673
|
-
conductorBackend,
|
|
674
|
-
lazyWorkers: activeLazyWorkers,
|
|
675
|
-
issueUrl: options.issueUrl,
|
|
676
|
-
repoRoot: options.repoRoot,
|
|
677
|
-
profile: structuredClone(activeProfile),
|
|
678
|
-
...(options.profilePath ? { profilePath: options.profilePath } : {}),
|
|
679
|
-
openQuestions: snapshot.openQuestions,
|
|
680
|
-
sequence: snapshot.sequence,
|
|
681
|
-
workers,
|
|
682
|
-
mcpConfigDigest,
|
|
683
|
-
...(githubMonitorCursor ? { githubMonitor: githubMonitorCursor } : {}),
|
|
684
|
-
updatedAt: Date.now(),
|
|
756
|
+
let terminalLifecycle;
|
|
757
|
+
const enqueueSidecarWrite = (write) => {
|
|
758
|
+
const nextWrite = sidecarFlushQueue.then(write);
|
|
759
|
+
sidecarFlushQueue = nextWrite.catch(() => { });
|
|
760
|
+
return nextWrite;
|
|
761
|
+
};
|
|
762
|
+
const createSessionSidecar = (snapshot, lifecycle) => {
|
|
763
|
+
const workers = {};
|
|
764
|
+
for (const [name, worker] of workerSessions) {
|
|
765
|
+
workers[name] = {
|
|
766
|
+
acpSessionId: worker.acpSessionId,
|
|
767
|
+
...(worker.acpCwd ? { acpCwd: worker.acpCwd } : {}),
|
|
768
|
+
...(worker.acpSpawn ? { acpSpawn: worker.acpSpawn } : {}),
|
|
685
769
|
};
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
770
|
+
}
|
|
771
|
+
return {
|
|
772
|
+
version: SESSION_SIDECAR_VERSION,
|
|
773
|
+
conductorAgentId: conductorHandle.conductor.agentId,
|
|
774
|
+
conductorBackend,
|
|
775
|
+
lazyWorkers: activeLazyWorkers,
|
|
776
|
+
...(harnessSocketPath ? { harnessSocketPath } : {}),
|
|
777
|
+
issueUrl: options.issueUrl,
|
|
778
|
+
repoRoot: options.repoRoot,
|
|
779
|
+
profile: structuredClone(activeProfile),
|
|
780
|
+
...(options.profilePath ? { profilePath: options.profilePath } : {}),
|
|
781
|
+
openQuestions: snapshot.openQuestions,
|
|
782
|
+
sequence: snapshot.sequence,
|
|
783
|
+
workers,
|
|
784
|
+
mcpConfigDigest,
|
|
785
|
+
...((terminalLifecycle ?? lifecycle)
|
|
786
|
+
? { lifecycle: terminalLifecycle ?? lifecycle }
|
|
787
|
+
: {}),
|
|
788
|
+
...(githubMonitorCursor ? { githubMonitor: githubMonitorCursor } : {}),
|
|
789
|
+
updatedAt: Date.now(),
|
|
790
|
+
};
|
|
697
791
|
};
|
|
792
|
+
const persistOpenQuestionsSnapshot = async (snapshot) => {
|
|
793
|
+
await saveOpenQuestionsStore(openQuestionsStorePath({
|
|
794
|
+
repoRoot: options.repoRoot,
|
|
795
|
+
conductorAgentId: conductorHandle.conductor.agentId,
|
|
796
|
+
}), snapshot);
|
|
797
|
+
};
|
|
798
|
+
const persistSessionSidecar = async (snapshot, lifecycle) => {
|
|
799
|
+
await saveSessionSidecar(sessionSidecarPath({
|
|
800
|
+
repoRoot: options.repoRoot,
|
|
801
|
+
conductorAgentId: conductorHandle.conductor.agentId,
|
|
802
|
+
}), createSessionSidecar(snapshot, lifecycle));
|
|
803
|
+
};
|
|
804
|
+
flushSidecar = (lifecycle) => enqueueSidecarWrite(async () => {
|
|
805
|
+
const snapshot = openQuestions.snapshot();
|
|
806
|
+
await persistOpenQuestionsSnapshot(snapshot);
|
|
807
|
+
await persistSessionSidecar(snapshot, lifecycle);
|
|
808
|
+
});
|
|
698
809
|
const reportSidecarFlushFailure = (error) => {
|
|
699
810
|
const detail = error instanceof Error ? error.message : String(error);
|
|
700
811
|
sessionLogger.emit({
|
|
@@ -702,6 +813,13 @@ export async function runConductorSession(options) {
|
|
|
702
813
|
message: `Open Question YAML / session sidecar を保存できませんでした。最新状態から再開できず、直前に保存された状態からの再開になる場合があります。 error=${detail}`,
|
|
703
814
|
});
|
|
704
815
|
};
|
|
816
|
+
const reportSessionSidecarWriteFailure = (error) => {
|
|
817
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
818
|
+
sessionLogger.emit({
|
|
819
|
+
type: 'harness.warning',
|
|
820
|
+
message: `Session sidecar の更新に失敗しました。Open Question YAML の保存は完了しており、sidecar は後続 flush で同期します。 error=${detail}`,
|
|
821
|
+
});
|
|
822
|
+
};
|
|
705
823
|
scheduleSidecarFlush = () => {
|
|
706
824
|
void flushSidecar().catch(reportSidecarFlushFailure);
|
|
707
825
|
};
|
|
@@ -871,6 +989,57 @@ export async function runConductorSession(options) {
|
|
|
871
989
|
let operatorRequestedExit = false;
|
|
872
990
|
let operatorRequestedClose = false;
|
|
873
991
|
let disposeOperatorInput;
|
|
992
|
+
const onOperatorMessageQueued = (delivery) => {
|
|
993
|
+
scheduleSidecarFlush();
|
|
994
|
+
if (delivery !== 'interrupt')
|
|
995
|
+
return;
|
|
996
|
+
const cancel = conductorHandle.conductor.cancel;
|
|
997
|
+
if (!cancel) {
|
|
998
|
+
sessionLogger.emit({
|
|
999
|
+
type: 'harness.warning',
|
|
1000
|
+
message: 'Operator interrupt cancellation is unavailable; the message will use normal next dispatch.',
|
|
1001
|
+
});
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
1004
|
+
void cancel.call(conductorHandle.conductor).catch((error) => {
|
|
1005
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
1006
|
+
sessionLogger.emit({
|
|
1007
|
+
type: 'harness.warning',
|
|
1008
|
+
message: `Operator interrupt cancellation failed; the message will use normal next dispatch: ${detail}`,
|
|
1009
|
+
});
|
|
1010
|
+
});
|
|
1011
|
+
};
|
|
1012
|
+
const submitOperatorMessage = (input) => {
|
|
1013
|
+
const delivery = input.delivery ?? 'next';
|
|
1014
|
+
const received = submitOperatorInput({
|
|
1015
|
+
...input,
|
|
1016
|
+
delivery,
|
|
1017
|
+
conductorTurn: sendCount + 1,
|
|
1018
|
+
openQuestions,
|
|
1019
|
+
escalations,
|
|
1020
|
+
eventQueue,
|
|
1021
|
+
sessionLogger,
|
|
1022
|
+
onEscalated: input.onEscalated ?? ((record) => options.onEscalated?.(record)),
|
|
1023
|
+
});
|
|
1024
|
+
if (!received)
|
|
1025
|
+
return false;
|
|
1026
|
+
onOperatorMessageQueued(delivery);
|
|
1027
|
+
return true;
|
|
1028
|
+
};
|
|
1029
|
+
const sendOperatorMessage = (input) => {
|
|
1030
|
+
const delivery = input.delivery ?? 'next';
|
|
1031
|
+
const received = enqueueOperatorMessageEvent({
|
|
1032
|
+
...input,
|
|
1033
|
+
delivery,
|
|
1034
|
+
conductorTurn: sendCount + 1,
|
|
1035
|
+
eventQueue,
|
|
1036
|
+
sessionLogger,
|
|
1037
|
+
});
|
|
1038
|
+
if (!received)
|
|
1039
|
+
return false;
|
|
1040
|
+
onOperatorMessageQueued(delivery);
|
|
1041
|
+
return true;
|
|
1042
|
+
};
|
|
874
1043
|
if (options.bindOperatorInput) {
|
|
875
1044
|
const bindingDispose = options.bindOperatorInput({
|
|
876
1045
|
submit: (message, submitOptions) => {
|
|
@@ -878,34 +1047,32 @@ export async function runConductorSession(options) {
|
|
|
878
1047
|
sessionLogger.emit({ type: 'session.operator_exit' });
|
|
879
1048
|
workerSession.runtime.cancelAllActivePrompts();
|
|
880
1049
|
operatorRequestedExit = true;
|
|
881
|
-
|
|
1050
|
+
shutdownIntentController.abort();
|
|
1051
|
+
abortAfterOpenQuestionDrain(operatorTerminationController);
|
|
882
1052
|
return false;
|
|
883
1053
|
}
|
|
884
1054
|
if (isOperatorCloseCommand(message)) {
|
|
885
1055
|
sessionLogger.emit({ type: 'session.operator_close' });
|
|
886
1056
|
workerSession.runtime.cancelAllActivePrompts();
|
|
887
1057
|
operatorRequestedClose = true;
|
|
888
|
-
|
|
1058
|
+
shutdownIntentController.abort();
|
|
1059
|
+
abortAfterOpenQuestionDrain(operatorTerminationController);
|
|
889
1060
|
return false;
|
|
890
1061
|
}
|
|
891
1062
|
if (isOperatorReconnectCommand(message)) {
|
|
892
1063
|
eventQueue.enqueue({ type: 'operator.reconnect' });
|
|
893
1064
|
return true;
|
|
894
1065
|
}
|
|
895
|
-
const
|
|
896
|
-
message,
|
|
1066
|
+
const parsed = submitOptions?.targetOpenQuestionId !== undefined
|
|
1067
|
+
? { message, delivery: 'next' }
|
|
1068
|
+
: submitOptions?.delivery === undefined
|
|
1069
|
+
? parseOperatorMessageDelivery(message)
|
|
1070
|
+
: { message, delivery: submitOptions.delivery };
|
|
1071
|
+
return submitOperatorMessage({
|
|
1072
|
+
message: parsed.message,
|
|
1073
|
+
delivery: parsed.delivery,
|
|
897
1074
|
targetOpenQuestionId: submitOptions?.targetOpenQuestionId,
|
|
898
|
-
conductorTurn: sendCount + 1,
|
|
899
|
-
openQuestions,
|
|
900
|
-
escalations,
|
|
901
|
-
eventQueue,
|
|
902
|
-
sessionLogger,
|
|
903
|
-
onEscalated: (record) => options.onEscalated?.(record),
|
|
904
1075
|
});
|
|
905
|
-
if (received) {
|
|
906
|
-
scheduleSidecarFlush();
|
|
907
|
-
}
|
|
908
|
-
return received;
|
|
909
1076
|
},
|
|
910
1077
|
getContext: () => ({
|
|
911
1078
|
conductorTurn: sendCount + 1,
|
|
@@ -919,6 +1086,128 @@ export async function runConductorSession(options) {
|
|
|
919
1086
|
}
|
|
920
1087
|
}
|
|
921
1088
|
try {
|
|
1089
|
+
openQuestionControlServer = await startOpenQuestionControlServer({
|
|
1090
|
+
listOpenQuestions: () => openQuestions.listOpen(),
|
|
1091
|
+
getLastConductorMessage: () => lastConductorMessage,
|
|
1092
|
+
observeSession: async () => {
|
|
1093
|
+
await sessionStateWriteQueue;
|
|
1094
|
+
const sessionState = await loadSessionStateStore(sessionStateStorePath({
|
|
1095
|
+
repoRoot: options.repoRoot,
|
|
1096
|
+
conductorAgentId: conductorAgent.agentId,
|
|
1097
|
+
}));
|
|
1098
|
+
return {
|
|
1099
|
+
...openQuestions.snapshot(),
|
|
1100
|
+
...(sessionState ? { sessionState } : {}),
|
|
1101
|
+
};
|
|
1102
|
+
},
|
|
1103
|
+
sendOperatorMessage: ({ text, delivery }) => {
|
|
1104
|
+
if (teardownStarted) {
|
|
1105
|
+
throw new OpenQuestionControlOperationError(-32000, 'The session is shutting down.');
|
|
1106
|
+
}
|
|
1107
|
+
return sendOperatorMessage({ message: text, delivery });
|
|
1108
|
+
},
|
|
1109
|
+
answerOpenQuestion: async ({ questionId, answer }) => {
|
|
1110
|
+
if (teardownStarted) {
|
|
1111
|
+
throw new OpenQuestionControlOperationError(-32000, 'The session is shutting down.');
|
|
1112
|
+
}
|
|
1113
|
+
const current = openQuestions.get(questionId);
|
|
1114
|
+
if (!current || current.status !== 'open') {
|
|
1115
|
+
throw new OpenQuestionControlOperationError(-32004, 'Open Question is not available.');
|
|
1116
|
+
}
|
|
1117
|
+
let accepted = false;
|
|
1118
|
+
let sidecarWriteError;
|
|
1119
|
+
let dispatchReceipt;
|
|
1120
|
+
let answered;
|
|
1121
|
+
const escalatedRecords = [];
|
|
1122
|
+
await enqueueSidecarWrite(async () => {
|
|
1123
|
+
const stagedOpenQuestions = new OpenQuestionRegistry();
|
|
1124
|
+
stagedOpenQuestions.restore(openQuestions.snapshot());
|
|
1125
|
+
const stagedAccepted = submitOperatorInput({
|
|
1126
|
+
message: answer,
|
|
1127
|
+
targetOpenQuestionId: questionId,
|
|
1128
|
+
conductorTurn: sendCount + 1,
|
|
1129
|
+
openQuestions: stagedOpenQuestions,
|
|
1130
|
+
escalations: [],
|
|
1131
|
+
eventQueue: new SessionEventQueue(),
|
|
1132
|
+
sessionLogger: { emit: () => { } },
|
|
1133
|
+
});
|
|
1134
|
+
const stagedAnswer = stagedOpenQuestions.get(questionId);
|
|
1135
|
+
if (!stagedAccepted ||
|
|
1136
|
+
!stagedAnswer ||
|
|
1137
|
+
stagedAnswer.status !== 'answered') {
|
|
1138
|
+
return;
|
|
1139
|
+
}
|
|
1140
|
+
// The Open Questions YAML is the durable commit point. Do not expose
|
|
1141
|
+
// the answer in the live Registry or event queue until this succeeds.
|
|
1142
|
+
await persistOpenQuestionsSnapshot(stagedOpenQuestions.snapshot());
|
|
1143
|
+
// Another answer path may have won while the atomic YAML write was
|
|
1144
|
+
// in flight. Restore the current Registry snapshot before rejecting.
|
|
1145
|
+
if (openQuestions.get(questionId)?.status !== 'open') {
|
|
1146
|
+
await persistOpenQuestionsSnapshot(openQuestions.snapshot());
|
|
1147
|
+
return;
|
|
1148
|
+
}
|
|
1149
|
+
accepted = submitOperatorMessage({
|
|
1150
|
+
message: answer,
|
|
1151
|
+
delivery: 'next',
|
|
1152
|
+
targetOpenQuestionId: questionId,
|
|
1153
|
+
answeredAt: stagedAnswer.answeredAt,
|
|
1154
|
+
onEscalated: (record) => escalatedRecords.push(record),
|
|
1155
|
+
onOperatorMessageQueued: (event) => {
|
|
1156
|
+
let receipt;
|
|
1157
|
+
dispatchReceipt = new Promise((resolve, reject) => {
|
|
1158
|
+
receipt = {
|
|
1159
|
+
resolve: () => resolve(),
|
|
1160
|
+
reject: (error) => reject(error),
|
|
1161
|
+
};
|
|
1162
|
+
openQuestionDispatchReceipts.set(event, receipt);
|
|
1163
|
+
pendingOpenQuestionDispatchReceipts.add(receipt);
|
|
1164
|
+
});
|
|
1165
|
+
void dispatchReceipt.catch(() => { });
|
|
1166
|
+
},
|
|
1167
|
+
});
|
|
1168
|
+
answered = openQuestions.get(questionId);
|
|
1169
|
+
if (!accepted || !answered || answered.status !== 'answered') {
|
|
1170
|
+
await persistOpenQuestionsSnapshot(openQuestions.snapshot());
|
|
1171
|
+
accepted = false;
|
|
1172
|
+
return;
|
|
1173
|
+
}
|
|
1174
|
+
try {
|
|
1175
|
+
await persistSessionSidecar(openQuestions.snapshot());
|
|
1176
|
+
}
|
|
1177
|
+
catch (error) {
|
|
1178
|
+
// YAML is authoritative and is already durable. Keep the accepted
|
|
1179
|
+
// answer/notification, report the mirror failure, then repair it.
|
|
1180
|
+
sidecarWriteError = error;
|
|
1181
|
+
}
|
|
1182
|
+
});
|
|
1183
|
+
if (!accepted || !answered || answered.status !== 'answered') {
|
|
1184
|
+
throw new OpenQuestionControlOperationError(-32004, 'Open Question could not be answered.');
|
|
1185
|
+
}
|
|
1186
|
+
for (const record of escalatedRecords) {
|
|
1187
|
+
try {
|
|
1188
|
+
options.onEscalated?.(record);
|
|
1189
|
+
}
|
|
1190
|
+
catch (error) {
|
|
1191
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
1192
|
+
sessionLogger.emit({
|
|
1193
|
+
type: 'harness.warning',
|
|
1194
|
+
message: `Open Question observer callback failed: ${detail}`,
|
|
1195
|
+
});
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
if (sidecarWriteError !== undefined) {
|
|
1199
|
+
reportSessionSidecarWriteFailure(sidecarWriteError);
|
|
1200
|
+
scheduleSidecarFlush();
|
|
1201
|
+
}
|
|
1202
|
+
if (!dispatchReceipt) {
|
|
1203
|
+
throw new OpenQuestionControlOperationError(-32603, 'The answer was recorded, but its conductor notification was not queued. The outcome is unknown; do not resubmit the answer and inspect the session state.', RECORDED_ANSWER_UNKNOWN_DISPATCH);
|
|
1204
|
+
}
|
|
1205
|
+
await dispatchReceipt;
|
|
1206
|
+
return answered;
|
|
1207
|
+
},
|
|
1208
|
+
});
|
|
1209
|
+
harnessSocketPath = openQuestionControlServer.socketPath;
|
|
1210
|
+
await flushSidecar().catch(reportSidecarFlushFailure);
|
|
922
1211
|
const driverResult = await runConductorSessionDriver({
|
|
923
1212
|
issueUrl: options.issueUrl,
|
|
924
1213
|
initialPrompt: conductorBackend === 'pi'
|
|
@@ -979,6 +1268,23 @@ export async function runConductorSession(options) {
|
|
|
979
1268
|
});
|
|
980
1269
|
},
|
|
981
1270
|
onSendComplete: recordSendComplete,
|
|
1271
|
+
onDispatchBatchSettled: ({ events, result, error }) => {
|
|
1272
|
+
for (const event of events) {
|
|
1273
|
+
if (event.type !== 'operator.message')
|
|
1274
|
+
continue;
|
|
1275
|
+
const receipt = openQuestionDispatchReceipts.get(event);
|
|
1276
|
+
if (!receipt)
|
|
1277
|
+
continue;
|
|
1278
|
+
openQuestionDispatchReceipts.delete(event);
|
|
1279
|
+
pendingOpenQuestionDispatchReceipts.delete(receipt);
|
|
1280
|
+
if (error || result?.status !== 'finished') {
|
|
1281
|
+
receipt.reject(new OpenQuestionControlOperationError(-32005, 'The answer was recorded and persisted, but conductor notification could not be confirmed. The outcome is unknown; do not resubmit the answer and inspect the session state.', RECORDED_ANSWER_UNKNOWN_DISPATCH));
|
|
1282
|
+
}
|
|
1283
|
+
else {
|
|
1284
|
+
receipt.resolve();
|
|
1285
|
+
}
|
|
1286
|
+
}
|
|
1287
|
+
},
|
|
982
1288
|
onOpenQuestionEnqueued: (question) => {
|
|
983
1289
|
sessionLogger.emit({ type: 'open.question.enqueued', question });
|
|
984
1290
|
options.onOpenQuestionEnqueued?.(question);
|
|
@@ -995,6 +1301,14 @@ export async function runConductorSession(options) {
|
|
|
995
1301
|
function recordSendComplete(info) {
|
|
996
1302
|
sendCount = info.sendCount;
|
|
997
1303
|
autonomousTurns = info.autonomousTurns;
|
|
1304
|
+
const responseText = info.result?.trim();
|
|
1305
|
+
if (info.status === 'finished' && responseText) {
|
|
1306
|
+
lastConductorMessage = {
|
|
1307
|
+
text: responseText,
|
|
1308
|
+
at: Date.now(),
|
|
1309
|
+
sendCount: info.sendCount,
|
|
1310
|
+
};
|
|
1311
|
+
}
|
|
998
1312
|
const githubUpdateIds = info.githubUpdateIds ?? [];
|
|
999
1313
|
if (info.status === 'finished' &&
|
|
1000
1314
|
!operatorRequestedClose &&
|
|
@@ -1069,7 +1383,19 @@ export async function runConductorSession(options) {
|
|
|
1069
1383
|
}
|
|
1070
1384
|
}
|
|
1071
1385
|
finally {
|
|
1386
|
+
const lifecycle = operatorRequestedClose
|
|
1387
|
+
? 'closed'
|
|
1388
|
+
: operatorRequestedExit
|
|
1389
|
+
? 'exited'
|
|
1390
|
+
: shutdownSignal.aborted
|
|
1391
|
+
? 'interrupted'
|
|
1392
|
+
: 'unknown';
|
|
1393
|
+
terminalLifecycle = lifecycle;
|
|
1072
1394
|
teardownStarted = true;
|
|
1395
|
+
for (const receipt of pendingOpenQuestionDispatchReceipts) {
|
|
1396
|
+
receipt.reject(new OpenQuestionControlOperationError(-32005, 'The answer was recorded and persisted, but the conductor session stopped before notification completed. The outcome is unknown; do not resubmit the answer and inspect the session state.', RECORDED_ANSWER_UNKNOWN_DISPATCH));
|
|
1397
|
+
}
|
|
1398
|
+
pendingOpenQuestionDispatchReceipts.clear();
|
|
1073
1399
|
const forceShutdown = operatorRequestedExit ||
|
|
1074
1400
|
operatorRequestedClose ||
|
|
1075
1401
|
shutdownSignal.aborted;
|
|
@@ -1095,6 +1421,24 @@ export async function runConductorSession(options) {
|
|
|
1095
1421
|
sessionLogger.emit({ type: 'harness.teardown.phase', phase });
|
|
1096
1422
|
};
|
|
1097
1423
|
try {
|
|
1424
|
+
const activeControlServer = openQuestionControlServer;
|
|
1425
|
+
openQuestionControlServer = undefined;
|
|
1426
|
+
harnessSocketPath = undefined;
|
|
1427
|
+
if (activeControlServer) {
|
|
1428
|
+
emitTeardownPhase('openQuestionControl');
|
|
1429
|
+
const phaseStart = Date.now();
|
|
1430
|
+
try {
|
|
1431
|
+
await activeControlServer.close();
|
|
1432
|
+
}
|
|
1433
|
+
catch (error) {
|
|
1434
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
1435
|
+
sessionLogger.emit({
|
|
1436
|
+
type: 'harness.warning',
|
|
1437
|
+
message: `Open Question control socket cleanup failed: ${detail}`,
|
|
1438
|
+
});
|
|
1439
|
+
}
|
|
1440
|
+
teardownPhases.openQuestionControl = Date.now() - phaseStart;
|
|
1441
|
+
}
|
|
1098
1442
|
disposeOperatorInput?.();
|
|
1099
1443
|
if (permissionDeadlockMonitor) {
|
|
1100
1444
|
emitTeardownPhase('permissionDeadlockMonitor');
|
|
@@ -1212,6 +1556,14 @@ export async function runConductorSession(options) {
|
|
|
1212
1556
|
}
|
|
1213
1557
|
}
|
|
1214
1558
|
finally {
|
|
1559
|
+
try {
|
|
1560
|
+
emitTeardownPhase('flushSidecar');
|
|
1561
|
+
await sessionStateWriteQueue;
|
|
1562
|
+
await flushSidecar(lifecycle);
|
|
1563
|
+
}
|
|
1564
|
+
catch (error) {
|
|
1565
|
+
reportSidecarFlushFailure(error);
|
|
1566
|
+
}
|
|
1215
1567
|
process.off('SIGINT', onTeardownSignal);
|
|
1216
1568
|
process.off('SIGTERM', onTeardownSignal);
|
|
1217
1569
|
}
|