@agents-ensemble/core 0.14.2 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/acp/resolve-acp-spawn.d.ts +1 -1
- package/dist/acp/resolve-acp-spawn.d.ts.map +1 -1
- package/dist/acp/resolve-acp-spawn.js +2 -2
- package/dist/acp/resolve-acp-spawn.js.map +1 -1
- package/dist/conductor/conductor-session-driver.d.ts +5 -2
- package/dist/conductor/conductor-session-driver.d.ts.map +1 -1
- package/dist/conductor/conductor-session-driver.js +6 -4
- 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 +186 -67
- package/dist/conductor/conductor-session.js.map +1 -1
- package/dist/conductor/index.d.ts +2 -0
- 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/pi-conductor-agent.d.ts.map +1 -1
- package/dist/conductor/pi-conductor-agent.js +18 -1
- package/dist/conductor/pi-conductor-agent.js.map +1 -1
- package/dist/conductor/session/dispatch-hold-reminder-monitor.d.ts +28 -0
- package/dist/conductor/session/dispatch-hold-reminder-monitor.d.ts.map +1 -0
- package/dist/conductor/session/dispatch-hold-reminder-monitor.js +88 -0
- package/dist/conductor/session/dispatch-hold-reminder-monitor.js.map +1 -0
- package/dist/conductor/session/dispatch-hold.d.ts +1 -1
- package/dist/conductor/session/dispatch-hold.d.ts.map +1 -1
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +3 -0
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/load-ensemble-config.d.ts +2 -0
- package/dist/config/load-ensemble-config.d.ts.map +1 -1
- package/dist/config/load-ensemble-config.js +18 -5
- 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 +12 -0
- package/dist/config/parse-config.js.map +1 -1
- package/dist/config/types.d.ts +5 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/dispatch/worker-acp-session.d.ts +2 -2
- package/dist/dispatch/worker-acp-session.d.ts.map +1 -1
- package/dist/dispatch/worker-acp-session.js +1 -1
- package/dist/dispatch/worker-acp-session.js.map +1 -1
- package/dist/github/github-monitor-cursor.d.ts +10 -10
- package/dist/github/github-monitor-cursor.d.ts.map +1 -1
- package/dist/github/github-monitor-cursor.js +1 -1
- package/dist/github/github-monitor-cursor.js.map +1 -1
- package/dist/mcp/load-mcp-config.d.ts +1 -1
- package/dist/mcp/load-mcp-config.js +1 -1
- package/dist/operator-runtime.d.ts +8 -4
- package/dist/operator-runtime.d.ts.map +1 -1
- package/dist/operator-runtime.js +4 -2
- package/dist/operator-runtime.js.map +1 -1
- package/dist/prompt/modules/ensemble/worker-base-module.d.ts.map +1 -1
- package/dist/prompt/modules/ensemble/worker-base-module.js +1 -0
- package/dist/prompt/modules/ensemble/worker-base-module.js.map +1 -1
- package/dist/session/index.d.ts +8 -4
- package/dist/session/index.d.ts.map +1 -1
- package/dist/session/index.js +4 -2
- package/dist/session/index.js.map +1 -1
- package/dist/session/open-question-control.d.ts +26 -2
- package/dist/session/open-question-control.d.ts.map +1 -1
- package/dist/session/open-question-control.js +96 -10
- package/dist/session/open-question-control.js.map +1 -1
- package/dist/session/session-resume-store.d.ts +119 -0
- package/dist/session/session-resume-store.d.ts.map +1 -0
- package/dist/session/session-resume-store.js +354 -0
- package/dist/session/session-resume-store.js.map +1 -0
- package/dist/session/tool-call-snapshot-history.d.ts +1 -1
- package/dist/session/tool-call-snapshot-history.js +4 -4
- package/dist/session/tool-call-snapshot-history.js.map +1 -1
- package/package.json +1 -1
- package/dist/session/session-sidecar.d.ts +0 -76
- package/dist/session/session-sidecar.d.ts.map +0 -1
- package/dist/session/session-sidecar.js +0 -261
- package/dist/session/session-sidecar.js.map +0 -1
|
@@ -31,17 +31,18 @@ import { resolvePiConductorProvider } from './conductor-pi-auth.js';
|
|
|
31
31
|
import { SessionLogger } from './session/session-logger.js';
|
|
32
32
|
import { SessionEventQueue } from './session/session-event-queue.js';
|
|
33
33
|
import { bufferDispatchHoldEvents, createDispatchHoldState, pruneStalePermissionEvents, } from './session/dispatch-hold.js';
|
|
34
|
+
import { createDispatchHoldReminderMonitor, formatDispatchHoldReminderMessage, } from './session/dispatch-hold-reminder-monitor.js';
|
|
34
35
|
import { operatorInputMaxTurns, resolveMaxTurns, } from './session-policy.js';
|
|
35
36
|
import { runConductorSessionDriver } from './conductor-session-driver.js';
|
|
36
37
|
import { isOperatorExitCommand } from './operator-exit.js';
|
|
37
38
|
import { isOperatorCloseCommand } from './operator-close.js';
|
|
38
39
|
import { isOperatorReconnectCommand } from './operator-reconnect.js';
|
|
39
40
|
import { parseOperatorMessageDelivery } from './operator-command.js';
|
|
40
|
-
import {
|
|
41
|
+
import { assertSessionResumeStoreMatches, requireSessionResumeStoreForResume, saveSessionResumeStore, SESSION_RESUME_STORE_VERSION, sessionResumeStorePath, } from '../session/session-resume-store.js';
|
|
41
42
|
import { saveSessionStatusLine, sessionStateStorePath, loadSessionStateStore, } from '../session/session-state-store.js';
|
|
42
43
|
import { loadToolCallSnapshotHistory, pruneExpiredToolCallSnapshotHistoryFiles, saveToolCallSnapshotHistory, toolCallSnapshotHistoryPath, } from '../session/tool-call-snapshot-history.js';
|
|
43
44
|
import { loadOpenQuestionsStore, openQuestionsStorePath, saveOpenQuestionsStore, } from '../session/open-questions-store.js';
|
|
44
|
-
import { OpenQuestionControlOperationError, startOpenQuestionControlServer, } from '../session/open-question-control.js';
|
|
45
|
+
import { CONDUCTOR_MESSAGE_HISTORY_MAX_ENTRIES, OpenQuestionControlOperationError, startOpenQuestionControlServer, } from '../session/open-question-control.js';
|
|
45
46
|
import { formatPermissionSummaryForOperator } from '../permission/format-permission-summary-for-operator.js';
|
|
46
47
|
import { enrichSessionUsageWithCost } from '../usage/enrich-session-usage-cost.js';
|
|
47
48
|
import { SessionUsageTracker } from '../usage/session-usage-tracker.js';
|
|
@@ -89,12 +90,17 @@ export async function runConductorSession(options) {
|
|
|
89
90
|
const escalations = [];
|
|
90
91
|
const openQuestions = new OpenQuestionRegistry();
|
|
91
92
|
let harnessSocketPath;
|
|
93
|
+
let harnessPhase;
|
|
94
|
+
let harnessModifiedAt;
|
|
95
|
+
let reportHarnessActivity = () => { };
|
|
92
96
|
let openQuestionControlServer;
|
|
93
97
|
let lastConductorMessage = null;
|
|
98
|
+
const conductorMessages = [];
|
|
94
99
|
const eventQueue = new SessionEventQueue();
|
|
95
100
|
const openQuestionDispatchReceipts = new WeakMap();
|
|
96
101
|
const pendingOpenQuestionDispatchReceipts = new Set();
|
|
97
102
|
const dispatchHoldState = createDispatchHoldState();
|
|
103
|
+
let dispatchHoldReminderMonitor;
|
|
98
104
|
const onDispatchHoldChanged = (change) => {
|
|
99
105
|
sessionLogger.emit({
|
|
100
106
|
type: 'conductor.dispatch_hold',
|
|
@@ -105,6 +111,7 @@ export async function runConductorSession(options) {
|
|
|
105
111
|
? { flushedEventCount: change.flushedEventCount }
|
|
106
112
|
: {}),
|
|
107
113
|
});
|
|
114
|
+
dispatchHoldReminderMonitor?.onHoldChanged(change.hold);
|
|
108
115
|
};
|
|
109
116
|
let activeProfile = options.profile;
|
|
110
117
|
let activeLazyWorkers = ensembleConfig.session.lazyWorkers;
|
|
@@ -119,11 +126,11 @@ export async function runConductorSession(options) {
|
|
|
119
126
|
let resumeToolCallSnapshots = [];
|
|
120
127
|
let toolCallHistoryNeedsRewrite = false;
|
|
121
128
|
if (options.resumeAgentId) {
|
|
122
|
-
const
|
|
129
|
+
const loadedResumeStore = await requireSessionResumeStoreForResume({
|
|
123
130
|
repoRoot: options.repoRoot,
|
|
124
131
|
conductorAgentId: options.resumeAgentId,
|
|
125
132
|
});
|
|
126
|
-
|
|
133
|
+
assertSessionResumeStoreMatches(loadedResumeStore, {
|
|
127
134
|
conductorAgentId: options.resumeAgentId,
|
|
128
135
|
issueUrl: options.issueUrl,
|
|
129
136
|
repoRoot: options.repoRoot,
|
|
@@ -133,18 +140,22 @@ export async function runConductorSession(options) {
|
|
|
133
140
|
});
|
|
134
141
|
// A previously completed lifecycle no longer describes this instance once
|
|
135
142
|
// resume begins. Persist unknown before restoring or attaching anything so
|
|
136
|
-
// a crash before the first regular
|
|
143
|
+
// a crash before the first regular SessionResumeStore flush cannot leave stale
|
|
137
144
|
// `closed` / `exited` data behind.
|
|
138
|
-
const
|
|
139
|
-
...
|
|
145
|
+
const resumeStore = {
|
|
146
|
+
...loadedResumeStore,
|
|
140
147
|
lifecycle: 'unknown',
|
|
148
|
+
harnessPhase: 'running',
|
|
149
|
+
modifiedAt: Date.now(),
|
|
141
150
|
harnessSocketPath: undefined,
|
|
142
151
|
updatedAt: Date.now(),
|
|
143
152
|
};
|
|
144
|
-
|
|
153
|
+
harnessPhase = 'running';
|
|
154
|
+
harnessModifiedAt = resumeStore.modifiedAt;
|
|
155
|
+
await saveSessionResumeStore(sessionResumeStorePath({
|
|
145
156
|
repoRoot: options.repoRoot,
|
|
146
157
|
conductorAgentId: options.resumeAgentId,
|
|
147
|
-
}),
|
|
158
|
+
}), resumeStore);
|
|
148
159
|
const history = await loadToolCallSnapshotHistory({
|
|
149
160
|
repoRoot: options.repoRoot,
|
|
150
161
|
conductorAgentId: options.resumeAgentId,
|
|
@@ -167,8 +178,8 @@ export async function runConductorSession(options) {
|
|
|
167
178
|
}
|
|
168
179
|
else {
|
|
169
180
|
const legacySnapshot = {
|
|
170
|
-
sequence:
|
|
171
|
-
openQuestions:
|
|
181
|
+
sequence: resumeStore.sequence,
|
|
182
|
+
openQuestions: resumeStore.openQuestions,
|
|
172
183
|
};
|
|
173
184
|
openQuestions.restore(legacySnapshot);
|
|
174
185
|
try {
|
|
@@ -178,15 +189,15 @@ export async function runConductorSession(options) {
|
|
|
178
189
|
const detail = error instanceof Error ? error.message : String(error);
|
|
179
190
|
sessionLogger.emit({
|
|
180
191
|
type: 'harness.warning',
|
|
181
|
-
message:
|
|
192
|
+
message: `旧形式の SessionResumeStore から復元した open question を YAML にバックフィルできませんでした。今回のセッションは継続します。 error=${detail}`,
|
|
182
193
|
});
|
|
183
194
|
}
|
|
184
195
|
}
|
|
185
|
-
activeProfile =
|
|
186
|
-
//
|
|
187
|
-
activeLazyWorkers =
|
|
188
|
-
githubMonitorCursor =
|
|
189
|
-
for (const [name, worker] of Object.entries(
|
|
196
|
+
activeProfile = resumeStore.profile;
|
|
197
|
+
// ResumeStores written before this setting always started every worker eagerly.
|
|
198
|
+
activeLazyWorkers = resumeStore.lazyWorkers ?? false;
|
|
199
|
+
githubMonitorCursor = resumeStore.githubMonitor;
|
|
200
|
+
for (const [name, worker] of Object.entries(resumeStore.workers)) {
|
|
190
201
|
workerSessions.set(name, {
|
|
191
202
|
acpSessionId: worker.acpSessionId,
|
|
192
203
|
...(worker.acpCwd ? { acpCwd: worker.acpCwd } : {}),
|
|
@@ -292,8 +303,8 @@ export async function runConductorSession(options) {
|
|
|
292
303
|
}
|
|
293
304
|
const permissionPipeline = options.permissionPipeline ??
|
|
294
305
|
new PermissionPipeline({ policy: options.permissionPolicy });
|
|
295
|
-
let
|
|
296
|
-
let
|
|
306
|
+
let scheduleSessionResumeStoreFlush = () => { };
|
|
307
|
+
let flushSessionResumeStore = async () => { };
|
|
297
308
|
let scheduleToolCallHistoryFlush = () => { };
|
|
298
309
|
let flushToolCallHistory = async () => { };
|
|
299
310
|
let toolCallHistoryDirty = toolCallHistoryNeedsRewrite;
|
|
@@ -436,7 +447,7 @@ export async function runConductorSession(options) {
|
|
|
436
447
|
: {}),
|
|
437
448
|
});
|
|
438
449
|
eventQueue.enqueue({ type: 'worker.completed', result });
|
|
439
|
-
|
|
450
|
+
scheduleSessionResumeStoreFlush();
|
|
440
451
|
},
|
|
441
452
|
onWorkerFailed: (failure) => {
|
|
442
453
|
sessionLogger.emit({ type: 'worker.failed', failure });
|
|
@@ -445,6 +456,7 @@ export async function runConductorSession(options) {
|
|
|
445
456
|
onPromptTelemetry: (event) => {
|
|
446
457
|
switch (event.phase) {
|
|
447
458
|
case 'started':
|
|
459
|
+
reportHarnessActivity('worker', true);
|
|
448
460
|
sessionLogger.emit({
|
|
449
461
|
type: 'harness.worker.prompt.started',
|
|
450
462
|
name: event.name,
|
|
@@ -454,6 +466,7 @@ export async function runConductorSession(options) {
|
|
|
454
466
|
});
|
|
455
467
|
break;
|
|
456
468
|
case 'completed':
|
|
469
|
+
reportHarnessActivity('worker', false);
|
|
457
470
|
sessionLogger.emit({
|
|
458
471
|
type: 'harness.worker.prompt.completed',
|
|
459
472
|
name: event.name,
|
|
@@ -464,6 +477,7 @@ export async function runConductorSession(options) {
|
|
|
464
477
|
});
|
|
465
478
|
break;
|
|
466
479
|
case 'failed':
|
|
480
|
+
reportHarnessActivity('worker', false);
|
|
467
481
|
sessionLogger.emit({
|
|
468
482
|
type: 'harness.worker.prompt.failed',
|
|
469
483
|
name: event.name,
|
|
@@ -567,14 +581,14 @@ export async function runConductorSession(options) {
|
|
|
567
581
|
sessionLogger.emit({ type: 'escalation.recorded', record });
|
|
568
582
|
options.onEscalated?.(record);
|
|
569
583
|
}
|
|
570
|
-
|
|
584
|
+
scheduleSessionResumeStoreFlush();
|
|
571
585
|
};
|
|
572
586
|
const askHumanTools = createAskHumanTool({
|
|
573
587
|
registry: openQuestions,
|
|
574
588
|
onEnqueued: (question) => {
|
|
575
589
|
sessionLogger.emit({ type: 'open.question.enqueued', question });
|
|
576
590
|
options.onOpenQuestionEnqueued?.(question);
|
|
577
|
-
|
|
591
|
+
scheduleSessionResumeStoreFlush();
|
|
578
592
|
},
|
|
579
593
|
});
|
|
580
594
|
const answerOpenQuestionTools = createAnswerOpenQuestionTool({
|
|
@@ -621,7 +635,7 @@ export async function runConductorSession(options) {
|
|
|
621
635
|
// Do not report success to the conductor until the explicit watch is
|
|
622
636
|
// durable. This closes the crash window between the tool result and a
|
|
623
637
|
// later best-effort flush.
|
|
624
|
-
await
|
|
638
|
+
await flushSessionResumeStore();
|
|
625
639
|
},
|
|
626
640
|
});
|
|
627
641
|
const unregisterGitHubWatchTools = createUnregisterGitHubWatchTool({
|
|
@@ -632,7 +646,7 @@ export async function runConductorSession(options) {
|
|
|
632
646
|
},
|
|
633
647
|
onUnregistered: async (prNumber) => {
|
|
634
648
|
githubMonitor?.unregisterPullRequest?.(prNumber);
|
|
635
|
-
await
|
|
649
|
+
await flushSessionResumeStore();
|
|
636
650
|
},
|
|
637
651
|
});
|
|
638
652
|
let conductorAgent;
|
|
@@ -752,14 +766,15 @@ export async function runConductorSession(options) {
|
|
|
752
766
|
});
|
|
753
767
|
},
|
|
754
768
|
};
|
|
755
|
-
let
|
|
769
|
+
let sessionResumeStoreWriteQueue = Promise.resolve();
|
|
756
770
|
let terminalLifecycle;
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
771
|
+
let terminalResumeStoreFinalFlushQueued = false;
|
|
772
|
+
const enqueueSessionResumeStoreWrite = (write) => {
|
|
773
|
+
const nextWrite = sessionResumeStoreWriteQueue.then(write);
|
|
774
|
+
sessionResumeStoreWriteQueue = nextWrite.catch(() => { });
|
|
760
775
|
return nextWrite;
|
|
761
776
|
};
|
|
762
|
-
const
|
|
777
|
+
const createSessionResumeStore = (snapshot, lifecycle) => {
|
|
763
778
|
const workers = {};
|
|
764
779
|
for (const [name, worker] of workerSessions) {
|
|
765
780
|
workers[name] = {
|
|
@@ -768,8 +783,13 @@ export async function runConductorSession(options) {
|
|
|
768
783
|
...(worker.acpSpawn ? { acpSpawn: worker.acpSpawn } : {}),
|
|
769
784
|
};
|
|
770
785
|
}
|
|
786
|
+
const persistedLifecycle = terminalLifecycle ??
|
|
787
|
+
lifecycle ??
|
|
788
|
+
(harnessPhase === 'running' || harnessPhase === 'idle'
|
|
789
|
+
? 'unknown'
|
|
790
|
+
: undefined);
|
|
771
791
|
return {
|
|
772
|
-
version:
|
|
792
|
+
version: SESSION_RESUME_STORE_VERSION,
|
|
773
793
|
conductorAgentId: conductorHandle.conductor.agentId,
|
|
774
794
|
conductorBackend,
|
|
775
795
|
lazyWorkers: activeLazyWorkers,
|
|
@@ -778,13 +798,15 @@ export async function runConductorSession(options) {
|
|
|
778
798
|
repoRoot: options.repoRoot,
|
|
779
799
|
profile: structuredClone(activeProfile),
|
|
780
800
|
...(options.profilePath ? { profilePath: options.profilePath } : {}),
|
|
801
|
+
...(harnessPhase ? { harnessPhase } : {}),
|
|
802
|
+
...(harnessModifiedAt !== undefined
|
|
803
|
+
? { modifiedAt: harnessModifiedAt }
|
|
804
|
+
: {}),
|
|
781
805
|
openQuestions: snapshot.openQuestions,
|
|
782
806
|
sequence: snapshot.sequence,
|
|
783
807
|
workers,
|
|
784
808
|
mcpConfigDigest,
|
|
785
|
-
...(
|
|
786
|
-
? { lifecycle: terminalLifecycle ?? lifecycle }
|
|
787
|
-
: {}),
|
|
809
|
+
...(persistedLifecycle ? { lifecycle: persistedLifecycle } : {}),
|
|
788
810
|
...(githubMonitorCursor ? { githubMonitor: githubMonitorCursor } : {}),
|
|
789
811
|
updatedAt: Date.now(),
|
|
790
812
|
};
|
|
@@ -795,33 +817,90 @@ export async function runConductorSession(options) {
|
|
|
795
817
|
conductorAgentId: conductorHandle.conductor.agentId,
|
|
796
818
|
}), snapshot);
|
|
797
819
|
};
|
|
798
|
-
const
|
|
799
|
-
await
|
|
820
|
+
const persistSessionResumeStore = async (snapshot, lifecycle) => {
|
|
821
|
+
await saveSessionResumeStore(sessionResumeStorePath({
|
|
800
822
|
repoRoot: options.repoRoot,
|
|
801
823
|
conductorAgentId: conductorHandle.conductor.agentId,
|
|
802
|
-
}),
|
|
824
|
+
}), createSessionResumeStore(snapshot, lifecycle));
|
|
803
825
|
};
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
826
|
+
flushSessionResumeStore = (lifecycle) => {
|
|
827
|
+
if (terminalResumeStoreFinalFlushQueued && lifecycle === undefined) {
|
|
828
|
+
return sessionResumeStoreWriteQueue;
|
|
829
|
+
}
|
|
830
|
+
if (lifecycle !== undefined && lifecycle !== 'unknown') {
|
|
831
|
+
terminalResumeStoreFinalFlushQueued = true;
|
|
832
|
+
}
|
|
833
|
+
return enqueueSessionResumeStoreWrite(async () => {
|
|
834
|
+
if (lifecycle !== undefined) {
|
|
835
|
+
terminalLifecycle = lifecycle;
|
|
836
|
+
harnessPhase =
|
|
837
|
+
lifecycle === 'closed'
|
|
838
|
+
? 'close'
|
|
839
|
+
: lifecycle === 'exited'
|
|
840
|
+
? 'exit'
|
|
841
|
+
: lifecycle === 'interrupted'
|
|
842
|
+
? 'abort'
|
|
843
|
+
: undefined;
|
|
844
|
+
harnessModifiedAt = harnessPhase ? Date.now() : undefined;
|
|
845
|
+
}
|
|
846
|
+
const snapshot = openQuestions.snapshot();
|
|
847
|
+
await persistOpenQuestionsSnapshot(snapshot);
|
|
848
|
+
await persistSessionResumeStore(snapshot, lifecycle);
|
|
849
|
+
});
|
|
850
|
+
};
|
|
851
|
+
const reportResumeStoreFlushFailure = (error) => {
|
|
810
852
|
const detail = error instanceof Error ? error.message : String(error);
|
|
811
853
|
sessionLogger.emit({
|
|
812
854
|
type: 'harness.warning',
|
|
813
|
-
message: `Open Question YAML /
|
|
855
|
+
message: `Open Question YAML / SessionResumeStore を保存できませんでした。最新状態から再開できず、直前に保存された状態からの再開になる場合があります。 error=${detail}`,
|
|
814
856
|
});
|
|
815
857
|
};
|
|
816
|
-
const
|
|
858
|
+
const reportSessionResumeStoreWriteFailure = (error) => {
|
|
817
859
|
const detail = error instanceof Error ? error.message : String(error);
|
|
818
860
|
sessionLogger.emit({
|
|
819
861
|
type: 'harness.warning',
|
|
820
|
-
message: `
|
|
862
|
+
message: `SessionResumeStore の更新に失敗しました。Open Question YAML の保存は完了しており、保存状態は後続 flush で同期します。 error=${detail}`,
|
|
821
863
|
});
|
|
822
864
|
};
|
|
823
|
-
|
|
824
|
-
void
|
|
865
|
+
scheduleSessionResumeStoreFlush = () => {
|
|
866
|
+
void flushSessionResumeStore().catch(reportResumeStoreFlushFailure);
|
|
867
|
+
};
|
|
868
|
+
let conductorSendCount = 0;
|
|
869
|
+
let workerPromptCount = 0;
|
|
870
|
+
let idlePhaseTimer;
|
|
871
|
+
const persistHarnessPhase = (phase) => {
|
|
872
|
+
if (teardownStarted || harnessPhase === phase)
|
|
873
|
+
return;
|
|
874
|
+
if (idlePhaseTimer) {
|
|
875
|
+
clearTimeout(idlePhaseTimer);
|
|
876
|
+
idlePhaseTimer = undefined;
|
|
877
|
+
}
|
|
878
|
+
harnessPhase = phase;
|
|
879
|
+
harnessModifiedAt = Date.now();
|
|
880
|
+
void flushSessionResumeStore().catch(reportResumeStoreFlushFailure);
|
|
881
|
+
};
|
|
882
|
+
reportHarnessActivity = (kind, active) => {
|
|
883
|
+
if (teardownStarted)
|
|
884
|
+
return;
|
|
885
|
+
if (kind === 'conductor') {
|
|
886
|
+
conductorSendCount = Math.max(0, conductorSendCount + (active ? 1 : -1));
|
|
887
|
+
}
|
|
888
|
+
else {
|
|
889
|
+
workerPromptCount = Math.max(0, workerPromptCount + (active ? 1 : -1));
|
|
890
|
+
}
|
|
891
|
+
if (conductorSendCount + workerPromptCount > 0) {
|
|
892
|
+
persistHarnessPhase('running');
|
|
893
|
+
return;
|
|
894
|
+
}
|
|
895
|
+
if (idlePhaseTimer || harnessPhase === 'idle')
|
|
896
|
+
return;
|
|
897
|
+
idlePhaseTimer = setTimeout(() => {
|
|
898
|
+
idlePhaseTimer = undefined;
|
|
899
|
+
if (conductorSendCount + workerPromptCount === 0) {
|
|
900
|
+
persistHarnessPhase('idle');
|
|
901
|
+
}
|
|
902
|
+
}, 250);
|
|
903
|
+
idlePhaseTimer.unref?.();
|
|
825
904
|
};
|
|
826
905
|
const toolCallHistoryPath = toolCallSnapshotHistoryPath({
|
|
827
906
|
repoRoot: options.repoRoot,
|
|
@@ -894,7 +973,7 @@ export async function runConductorSession(options) {
|
|
|
894
973
|
shutdownSignal,
|
|
895
974
|
onCursorChange: (cursor) => {
|
|
896
975
|
githubMonitorCursor = cursor;
|
|
897
|
-
|
|
976
|
+
scheduleSessionResumeStoreFlush();
|
|
898
977
|
},
|
|
899
978
|
onUpdate: (payload) => {
|
|
900
979
|
sessionLogger.emit({
|
|
@@ -953,6 +1032,21 @@ export async function runConductorSession(options) {
|
|
|
953
1032
|
});
|
|
954
1033
|
githubMonitor.start();
|
|
955
1034
|
}
|
|
1035
|
+
dispatchHoldReminderMonitor = createDispatchHoldReminderMonitor({
|
|
1036
|
+
getSnapshot: () => ({
|
|
1037
|
+
hold: dispatchHoldState.dispatchHold,
|
|
1038
|
+
heldEventCount: dispatchHoldState.heldEvents.length,
|
|
1039
|
+
}),
|
|
1040
|
+
onReminder: ({ heldEventCount }) => {
|
|
1041
|
+
sessionLogger.emit({
|
|
1042
|
+
type: 'harness.warning',
|
|
1043
|
+
message: formatDispatchHoldReminderMessage(heldEventCount, options.dispatchHoldReminderMs),
|
|
1044
|
+
});
|
|
1045
|
+
},
|
|
1046
|
+
reminderIntervalMs: options.dispatchHoldReminderMs,
|
|
1047
|
+
shutdownSignal,
|
|
1048
|
+
});
|
|
1049
|
+
dispatchHoldReminderMonitor.start();
|
|
956
1050
|
let permissionDeadlockMonitor;
|
|
957
1051
|
if (!options.disablePermissionDeadlockMonitor) {
|
|
958
1052
|
permissionDeadlockMonitor = createPermissionDeadlockMonitor({
|
|
@@ -990,7 +1084,7 @@ export async function runConductorSession(options) {
|
|
|
990
1084
|
let operatorRequestedClose = false;
|
|
991
1085
|
let disposeOperatorInput;
|
|
992
1086
|
const onOperatorMessageQueued = (delivery) => {
|
|
993
|
-
|
|
1087
|
+
scheduleSessionResumeStoreFlush();
|
|
994
1088
|
if (delivery !== 'interrupt')
|
|
995
1089
|
return;
|
|
996
1090
|
const cancel = conductorHandle.conductor.cancel;
|
|
@@ -1089,6 +1183,7 @@ export async function runConductorSession(options) {
|
|
|
1089
1183
|
openQuestionControlServer = await startOpenQuestionControlServer({
|
|
1090
1184
|
listOpenQuestions: () => openQuestions.listOpen(),
|
|
1091
1185
|
getLastConductorMessage: () => lastConductorMessage,
|
|
1186
|
+
listConductorMessages: () => conductorMessages,
|
|
1092
1187
|
observeSession: async () => {
|
|
1093
1188
|
await sessionStateWriteQueue;
|
|
1094
1189
|
const sessionState = await loadSessionStateStore(sessionStateStorePath({
|
|
@@ -1115,11 +1210,11 @@ export async function runConductorSession(options) {
|
|
|
1115
1210
|
throw new OpenQuestionControlOperationError(-32004, 'Open Question is not available.');
|
|
1116
1211
|
}
|
|
1117
1212
|
let accepted = false;
|
|
1118
|
-
let
|
|
1213
|
+
let resumeStoreWriteError;
|
|
1119
1214
|
let dispatchReceipt;
|
|
1120
1215
|
let answered;
|
|
1121
1216
|
const escalatedRecords = [];
|
|
1122
|
-
await
|
|
1217
|
+
await enqueueSessionResumeStoreWrite(async () => {
|
|
1123
1218
|
const stagedOpenQuestions = new OpenQuestionRegistry();
|
|
1124
1219
|
stagedOpenQuestions.restore(openQuestions.snapshot());
|
|
1125
1220
|
const stagedAccepted = submitOperatorInput({
|
|
@@ -1172,12 +1267,12 @@ export async function runConductorSession(options) {
|
|
|
1172
1267
|
return;
|
|
1173
1268
|
}
|
|
1174
1269
|
try {
|
|
1175
|
-
await
|
|
1270
|
+
await persistSessionResumeStore(openQuestions.snapshot());
|
|
1176
1271
|
}
|
|
1177
1272
|
catch (error) {
|
|
1178
1273
|
// YAML is authoritative and is already durable. Keep the accepted
|
|
1179
1274
|
// answer/notification, report the mirror failure, then repair it.
|
|
1180
|
-
|
|
1275
|
+
resumeStoreWriteError = error;
|
|
1181
1276
|
}
|
|
1182
1277
|
});
|
|
1183
1278
|
if (!accepted || !answered || answered.status !== 'answered') {
|
|
@@ -1195,9 +1290,9 @@ export async function runConductorSession(options) {
|
|
|
1195
1290
|
});
|
|
1196
1291
|
}
|
|
1197
1292
|
}
|
|
1198
|
-
if (
|
|
1199
|
-
|
|
1200
|
-
|
|
1293
|
+
if (resumeStoreWriteError !== undefined) {
|
|
1294
|
+
reportSessionResumeStoreWriteFailure(resumeStoreWriteError);
|
|
1295
|
+
scheduleSessionResumeStoreFlush();
|
|
1201
1296
|
}
|
|
1202
1297
|
if (!dispatchReceipt) {
|
|
1203
1298
|
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);
|
|
@@ -1207,7 +1302,7 @@ export async function runConductorSession(options) {
|
|
|
1207
1302
|
},
|
|
1208
1303
|
});
|
|
1209
1304
|
harnessSocketPath = openQuestionControlServer.socketPath;
|
|
1210
|
-
await
|
|
1305
|
+
await flushSessionResumeStore().catch(reportResumeStoreFlushFailure);
|
|
1211
1306
|
const driverResult = await runConductorSessionDriver({
|
|
1212
1307
|
issueUrl: options.issueUrl,
|
|
1213
1308
|
initialPrompt: conductorBackend === 'pi'
|
|
@@ -1253,6 +1348,7 @@ export async function runConductorSession(options) {
|
|
|
1253
1348
|
// session briefing.
|
|
1254
1349
|
skipInitialSend: Boolean(options.resumeAgentId),
|
|
1255
1350
|
onSendStarted: (info) => {
|
|
1351
|
+
reportHarnessActivity('conductor', true);
|
|
1256
1352
|
sessionLogger.emit({
|
|
1257
1353
|
type: 'conductor.send.started',
|
|
1258
1354
|
sendCount: info.sendCount,
|
|
@@ -1303,11 +1399,26 @@ export async function runConductorSession(options) {
|
|
|
1303
1399
|
autonomousTurns = info.autonomousTurns;
|
|
1304
1400
|
const responseText = info.result?.trim();
|
|
1305
1401
|
if (info.status === 'finished' && responseText) {
|
|
1402
|
+
const at = Date.now();
|
|
1306
1403
|
lastConductorMessage = {
|
|
1307
1404
|
text: responseText,
|
|
1308
|
-
at
|
|
1405
|
+
at,
|
|
1309
1406
|
sendCount: info.sendCount,
|
|
1310
1407
|
};
|
|
1408
|
+
if (info.dispatchSource !== 'initial') {
|
|
1409
|
+
conductorMessages.push({
|
|
1410
|
+
text: responseText,
|
|
1411
|
+
inbound: info.inbound,
|
|
1412
|
+
at,
|
|
1413
|
+
sendCount: info.sendCount,
|
|
1414
|
+
...(info.dispatchSource
|
|
1415
|
+
? { dispatchSource: info.dispatchSource }
|
|
1416
|
+
: {}),
|
|
1417
|
+
});
|
|
1418
|
+
if (conductorMessages.length > CONDUCTOR_MESSAGE_HISTORY_MAX_ENTRIES) {
|
|
1419
|
+
conductorMessages.shift();
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1311
1422
|
}
|
|
1312
1423
|
const githubUpdateIds = info.githubUpdateIds ?? [];
|
|
1313
1424
|
if (info.status === 'finished' &&
|
|
@@ -1338,6 +1449,7 @@ export async function runConductorSession(options) {
|
|
|
1338
1449
|
workerDispatches: info.workerDispatches,
|
|
1339
1450
|
workerFailures: info.workerFailures,
|
|
1340
1451
|
});
|
|
1452
|
+
reportHarnessActivity('conductor', false);
|
|
1341
1453
|
outboundWorkersThisSend.clear();
|
|
1342
1454
|
if (isConductorSendAuthError({
|
|
1343
1455
|
runId: info.runId,
|
|
@@ -1357,7 +1469,7 @@ export async function runConductorSession(options) {
|
|
|
1357
1469
|
: { backend: 'cursor' }),
|
|
1358
1470
|
});
|
|
1359
1471
|
}
|
|
1360
|
-
|
|
1472
|
+
scheduleSessionResumeStoreFlush();
|
|
1361
1473
|
}
|
|
1362
1474
|
async function buildResult() {
|
|
1363
1475
|
sessionLogger.finish(stopReason);
|
|
@@ -1390,8 +1502,11 @@ export async function runConductorSession(options) {
|
|
|
1390
1502
|
: shutdownSignal.aborted
|
|
1391
1503
|
? 'interrupted'
|
|
1392
1504
|
: 'unknown';
|
|
1393
|
-
terminalLifecycle = lifecycle;
|
|
1394
1505
|
teardownStarted = true;
|
|
1506
|
+
if (idlePhaseTimer) {
|
|
1507
|
+
clearTimeout(idlePhaseTimer);
|
|
1508
|
+
idlePhaseTimer = undefined;
|
|
1509
|
+
}
|
|
1395
1510
|
for (const receipt of pendingOpenQuestionDispatchReceipts) {
|
|
1396
1511
|
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
1512
|
}
|
|
@@ -1440,6 +1555,10 @@ export async function runConductorSession(options) {
|
|
|
1440
1555
|
teardownPhases.openQuestionControl = Date.now() - phaseStart;
|
|
1441
1556
|
}
|
|
1442
1557
|
disposeOperatorInput?.();
|
|
1558
|
+
if (dispatchHoldReminderMonitor) {
|
|
1559
|
+
emitTeardownPhase('dispatchHoldReminderMonitor');
|
|
1560
|
+
dispatchHoldReminderMonitor.stop();
|
|
1561
|
+
}
|
|
1443
1562
|
if (permissionDeadlockMonitor) {
|
|
1444
1563
|
emitTeardownPhase('permissionDeadlockMonitor');
|
|
1445
1564
|
permissionDeadlockMonitor.stop();
|
|
@@ -1486,13 +1605,13 @@ export async function runConductorSession(options) {
|
|
|
1486
1605
|
githubMonitorStopped = true;
|
|
1487
1606
|
}
|
|
1488
1607
|
try {
|
|
1489
|
-
emitTeardownPhase('
|
|
1608
|
+
emitTeardownPhase('flushSessionResumeStore');
|
|
1490
1609
|
const phaseStart = Date.now();
|
|
1491
|
-
await
|
|
1492
|
-
teardownPhases.
|
|
1610
|
+
await flushSessionResumeStore();
|
|
1611
|
+
teardownPhases.flushSessionResumeStore = Date.now() - phaseStart;
|
|
1493
1612
|
}
|
|
1494
1613
|
catch (error) {
|
|
1495
|
-
|
|
1614
|
+
reportResumeStoreFlushFailure(error);
|
|
1496
1615
|
}
|
|
1497
1616
|
await flushToolCallHistoryBestEffort();
|
|
1498
1617
|
const runGithubMonitorStop = async () => {
|
|
@@ -1557,12 +1676,12 @@ export async function runConductorSession(options) {
|
|
|
1557
1676
|
}
|
|
1558
1677
|
finally {
|
|
1559
1678
|
try {
|
|
1560
|
-
emitTeardownPhase('
|
|
1679
|
+
emitTeardownPhase('flushSessionResumeStore');
|
|
1561
1680
|
await sessionStateWriteQueue;
|
|
1562
|
-
await
|
|
1681
|
+
await flushSessionResumeStore(lifecycle);
|
|
1563
1682
|
}
|
|
1564
1683
|
catch (error) {
|
|
1565
|
-
|
|
1684
|
+
reportResumeStoreFlushFailure(error);
|
|
1566
1685
|
}
|
|
1567
1686
|
process.off('SIGINT', onTeardownSignal);
|
|
1568
1687
|
process.off('SIGTERM', onTeardownSignal);
|