@agents-ensemble/core 0.18.0 → 0.19.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/acp-bridge.d.ts +5 -1
- package/dist/acp/acp-bridge.d.ts.map +1 -1
- package/dist/acp/acp-bridge.js +4 -2
- package/dist/acp/acp-bridge.js.map +1 -1
- package/dist/acp/acp-client.d.ts +12 -0
- package/dist/acp/acp-client.d.ts.map +1 -1
- package/dist/acp/acp-client.js +88 -2
- package/dist/acp/acp-client.js.map +1 -1
- package/dist/acp/resolve-acp-spawn.d.ts +20 -2
- package/dist/acp/resolve-acp-spawn.d.ts.map +1 -1
- package/dist/acp/resolve-acp-spawn.js +92 -31
- package/dist/acp/resolve-acp-spawn.js.map +1 -1
- package/dist/acp/testing/fake-acp-server.d.ts +11 -0
- package/dist/acp/testing/fake-acp-server.d.ts.map +1 -1
- package/dist/acp/testing/fake-acp-server.js +31 -2
- package/dist/acp/testing/fake-acp-server.js.map +1 -1
- package/dist/conductor/conductor-agent-factory-cleanup.d.ts +7 -0
- package/dist/conductor/conductor-agent-factory-cleanup.d.ts.map +1 -0
- package/dist/conductor/conductor-agent-factory-cleanup.js +14 -0
- package/dist/conductor/conductor-agent-factory-cleanup.js.map +1 -0
- package/dist/conductor/conductor-agent.d.ts +6 -0
- package/dist/conductor/conductor-agent.d.ts.map +1 -1
- package/dist/conductor/conductor-session-driver.js +5 -7
- package/dist/conductor/conductor-session-driver.js.map +1 -1
- package/dist/conductor/conductor-session.d.ts +8 -0
- package/dist/conductor/conductor-session.d.ts.map +1 -1
- package/dist/conductor/conductor-session.js +228 -54
- package/dist/conductor/conductor-session.js.map +1 -1
- package/dist/conductor/cursor-sdk-conductor-agent.d.ts.map +1 -1
- package/dist/conductor/cursor-sdk-conductor-agent.js +77 -5
- package/dist/conductor/cursor-sdk-conductor-agent.js.map +1 -1
- package/dist/conductor/index.d.ts +1 -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 +1 -1
- package/dist/conductor/pi-conductor-agent.d.ts.map +1 -1
- package/dist/conductor/pi-conductor-agent.js +36 -1
- package/dist/conductor/pi-conductor-agent.js.map +1 -1
- package/dist/conductor/resolve-conductor-reasoning.d.ts +13 -0
- package/dist/conductor/resolve-conductor-reasoning.d.ts.map +1 -0
- package/dist/conductor/resolve-conductor-reasoning.js +16 -0
- package/dist/conductor/resolve-conductor-reasoning.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/conductor/session/dispatch-hold.js +2 -1
- package/dist/conductor/session/dispatch-hold.js.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 +1 -0
- package/dist/conductor/session/session-logger.js.map +1 -1
- package/dist/config/ensemble-config-digest.d.ts +47 -1
- package/dist/config/ensemble-config-digest.d.ts.map +1 -1
- package/dist/config/ensemble-config-digest.js +68 -2
- package/dist/config/ensemble-config-digest.js.map +1 -1
- package/dist/config/index.d.ts +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +1 -1
- package/dist/config/index.js.map +1 -1
- package/dist/config/load-ensemble-config.js +1 -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 +12 -1
- package/dist/config/parse-config.js.map +1 -1
- package/dist/config/resolve-settings.d.ts +8 -0
- package/dist/config/resolve-settings.d.ts.map +1 -1
- package/dist/config/resolve-settings.js +10 -0
- package/dist/config/resolve-settings.js.map +1 -1
- package/dist/config/types.d.ts +4 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/dispatch/set-dispatch-hold-tool.d.ts.map +1 -1
- package/dist/dispatch/set-dispatch-hold-tool.js +5 -4
- package/dist/dispatch/set-dispatch-hold-tool.js.map +1 -1
- package/dist/dispatch/worker-acp-session.d.ts +1 -1
- package/dist/dispatch/worker-acp-session.d.ts.map +1 -1
- package/dist/dispatch/worker-acp-session.js +23 -7
- package/dist/dispatch/worker-acp-session.js.map +1 -1
- package/dist/operator-runtime.d.ts +1 -0
- package/dist/operator-runtime.d.ts.map +1 -1
- package/dist/operator-runtime.js +1 -0
- package/dist/operator-runtime.js.map +1 -1
- package/dist/permission/permission-deadlock-monitor.d.ts +7 -2
- package/dist/permission/permission-deadlock-monitor.d.ts.map +1 -1
- package/dist/permission/permission-deadlock-monitor.js +16 -9
- package/dist/permission/permission-deadlock-monitor.js.map +1 -1
- package/dist/profiles/implementer-and-reviewer-v2/team-conductor.md +2 -0
- package/dist/prompt/modules/ensemble/conductor-base-module.js +3 -3
- package/dist/prompt/modules/ensemble/conductor-base-module.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/build-session-models-summary.d.ts +39 -0
- package/dist/session/build-session-models-summary.d.ts.map +1 -0
- package/dist/session/build-session-models-summary.js +40 -0
- package/dist/session/build-session-models-summary.js.map +1 -0
- package/dist/session/index.d.ts +3 -1
- package/dist/session/index.d.ts.map +1 -1
- package/dist/session/index.js +1 -0
- package/dist/session/index.js.map +1 -1
- package/dist/session/session-resume-store.d.ts +7 -2
- package/dist/session/session-resume-store.d.ts.map +1 -1
- package/dist/session/session-resume-store.js +48 -0
- package/dist/session/session-resume-store.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { computeEnsembleConfigDigest } from '../config/ensemble-config-digest.js';
|
|
1
|
+
import { computeAcpModelConfigDigest, computeEnsembleConfigDigest, computeResumeFeasibilityConfigDigest, } from '../config/ensemble-config-digest.js';
|
|
2
2
|
import { loadEnsembleConfig } from '../config/load-ensemble-config.js';
|
|
3
3
|
import { computeMcpConfigDigest, resolveMcpServers, } from '../mcp/load-mcp-config.js';
|
|
4
4
|
import { createAnswerOpenQuestionTool } from '../escalation/answer-open-question-tool.js';
|
|
@@ -25,8 +25,10 @@ import { createSetDispatchHoldTool } from '../dispatch/set-dispatch-hold-tool.js
|
|
|
25
25
|
import { parseIssueUrl } from '../issue/issue-ref.js';
|
|
26
26
|
import { resolveWorkerWorkspace, removeWorkerWorktree, } from '../worktree/worktree.js';
|
|
27
27
|
import { WorkerOutboundQueue } from '../runtime/worker-outbound-queue.js';
|
|
28
|
+
import { isConductorAgentFactoryCleanupIncomplete } from './conductor-agent-factory-cleanup.js';
|
|
28
29
|
import { ConductorToolRegistry } from './conductor-tool.js';
|
|
29
30
|
import { createConductorAgentFactory } from './conductor-agent-factory.js';
|
|
31
|
+
import { resolveConductorReasoning } from './resolve-conductor-reasoning.js';
|
|
30
32
|
import { formatConductorAuthRecoveryHint, isConductorSendAuthError, } from './conductor-auth.js';
|
|
31
33
|
import { resolvePiConductorProvider } from './conductor-pi-auth.js';
|
|
32
34
|
import { SessionLogger } from './session/session-logger.js';
|
|
@@ -40,6 +42,7 @@ import { isOperatorCloseCommand } from './operator-close.js';
|
|
|
40
42
|
import { isOperatorReconnectCommand } from './operator-reconnect.js';
|
|
41
43
|
import { parseOperatorMessageDelivery } from './operator-command.js';
|
|
42
44
|
import { assertSessionResumeStoreMatches, requireLocatedSessionResumeStoreForResume, saveSessionResumeStore, SESSION_RESUME_STORE_VERSION, sessionResumeStorePath, } from '../session/session-resume-store.js';
|
|
45
|
+
import { buildSessionModelsSummary, } from '../session/build-session-models-summary.js';
|
|
43
46
|
import { saveSessionStatusLine, sessionStateStorePath, loadSessionStateStore, legacySessionStateStorePath, } from '../session/session-state-store.js';
|
|
44
47
|
import { loadToolCallSnapshotHistory, pruneExpiredToolCallSnapshotHistoryFiles, saveToolCallSnapshotHistory, toolCallSnapshotHistoryPath, } from '../session/tool-call-snapshot-history.js';
|
|
45
48
|
import { loadOpenQuestionsStore, legacyOpenQuestionsStorePath, openQuestionsStorePath, saveOpenQuestionsStore, } from '../session/open-questions-store.js';
|
|
@@ -50,6 +53,7 @@ import { SessionUsageTracker } from '../usage/session-usage-tracker.js';
|
|
|
50
53
|
import { enqueueOperatorMessage as enqueueOperatorMessageEvent, submitOperatorInput, } from './submit-operator-input.js';
|
|
51
54
|
import { createGitHubMonitor, } from '../github/github-monitor.js';
|
|
52
55
|
import { resolveConductorBackendSetting, resolveConductorPiResourcePaths, resolveGitHubMonitorEnabled, } from '../config/resolve-settings.js';
|
|
56
|
+
import { CONDUCTOR_MODEL_ID_ENV, DEFAULT_CONDUCTOR_MODEL_ID, resolveConductorModelId, } from './resolve-conductor-model-id.js';
|
|
53
57
|
import { GitHubMonitorError } from '../github/github-monitor-error.js';
|
|
54
58
|
import { formatGitHubErrorMessage, GITHUB_AUTH_HINT, } from '../github/github-auth.js';
|
|
55
59
|
import { fetchIssueContext } from '../github/issue-context.js';
|
|
@@ -66,12 +70,86 @@ const RECORDED_ANSWER_UNKNOWN_DISPATCH = {
|
|
|
66
70
|
answerStatus: 'recorded',
|
|
67
71
|
dispatchStatus: 'unknown',
|
|
68
72
|
};
|
|
73
|
+
/** Acquired resources are released here until the session teardown takes ownership. */
|
|
74
|
+
class StartupCleanupGuard {
|
|
75
|
+
actions = [];
|
|
76
|
+
handedOff = false;
|
|
77
|
+
cleanupFailed = false;
|
|
78
|
+
reportCleanupError = () => { };
|
|
79
|
+
get hasFailures() {
|
|
80
|
+
return this.cleanupFailed;
|
|
81
|
+
}
|
|
82
|
+
add(action) {
|
|
83
|
+
if (this.handedOff) {
|
|
84
|
+
throw new Error('Cannot register startup cleanup after session handoff');
|
|
85
|
+
}
|
|
86
|
+
this.actions.push(action);
|
|
87
|
+
}
|
|
88
|
+
setErrorReporter(report) {
|
|
89
|
+
this.reportCleanupError = report;
|
|
90
|
+
}
|
|
91
|
+
recordCleanupFailure(error) {
|
|
92
|
+
this.cleanupFailed = true;
|
|
93
|
+
try {
|
|
94
|
+
this.reportCleanupError(error);
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
// Keep the startup error as the rejection reason if reporting fails.
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
handoff() {
|
|
101
|
+
this.handedOff = true;
|
|
102
|
+
this.actions.length = 0;
|
|
103
|
+
}
|
|
104
|
+
async cleanup() {
|
|
105
|
+
if (this.handedOff)
|
|
106
|
+
return;
|
|
107
|
+
this.handedOff = true;
|
|
108
|
+
for (const action of this.actions.reverse()) {
|
|
109
|
+
try {
|
|
110
|
+
await action();
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
this.recordCleanupFailure(error);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
69
118
|
export async function runConductorSession(options) {
|
|
119
|
+
const startupCleanup = new StartupCleanupGuard();
|
|
120
|
+
try {
|
|
121
|
+
return await runConductorSessionWithStartupGuard(options, startupCleanup);
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
await startupCleanup.cleanup();
|
|
125
|
+
throw error;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
async function runConductorSessionWithStartupGuard(options, startupCleanup) {
|
|
70
129
|
const userInstructions = await loadUserInstructions({
|
|
71
130
|
userEnsembleRoot: options.mcpConfigOptions?.userEnsembleRoot,
|
|
72
131
|
});
|
|
73
132
|
const ensembleConfig = options.ensembleConfig ?? (await loadEnsembleConfig(options.repoRoot));
|
|
74
|
-
|
|
133
|
+
let ensembleConfigDigestScheme = 'resume-feasibility';
|
|
134
|
+
let ensembleConfigDigest = computeResumeFeasibilityConfigDigest(ensembleConfig);
|
|
135
|
+
let acpModelConfigDigest = computeAcpModelConfigDigest(ensembleConfig);
|
|
136
|
+
const resolvedConductorModelId = resolveConductorModelId(options.modelId, {
|
|
137
|
+
config: ensembleConfig,
|
|
138
|
+
});
|
|
139
|
+
const hasInvocationModelOverride = Boolean(options.modelId?.trim() ||
|
|
140
|
+
process.env[CONDUCTOR_MODEL_ID_ENV]?.trim());
|
|
141
|
+
// Built-in factories understand `default` as their backend default. For a
|
|
142
|
+
// custom factory, preserve the optional contract when the default comes
|
|
143
|
+
// from config/code defaults. Keep an invocation-level `default` sentinel so
|
|
144
|
+
// the factory can honor it over its own lower-priority defaults.
|
|
145
|
+
const conductorFactoryModelId = options.conductorAgentFactory &&
|
|
146
|
+
resolvedConductorModelId === DEFAULT_CONDUCTOR_MODEL_ID &&
|
|
147
|
+
!hasInvocationModelOverride
|
|
148
|
+
? undefined
|
|
149
|
+
: resolvedConductorModelId;
|
|
150
|
+
const conductorReasoning = resolveConductorReasoning(options.reasoning, {
|
|
151
|
+
config: ensembleConfig,
|
|
152
|
+
});
|
|
75
153
|
const conductorBackend = resolveConductorBackendSetting({
|
|
76
154
|
profile: options.profile,
|
|
77
155
|
config: ensembleConfig,
|
|
@@ -82,6 +160,13 @@ export async function runConductorSession(options) {
|
|
|
82
160
|
issueUrl: options.issueUrl,
|
|
83
161
|
repoRoot: options.repoRoot,
|
|
84
162
|
});
|
|
163
|
+
startupCleanup.setErrorReporter((error) => {
|
|
164
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
165
|
+
sessionLogger.emit({
|
|
166
|
+
type: 'harness.warning',
|
|
167
|
+
message: `起動失敗後の後始末に失敗しました: ${detail}`,
|
|
168
|
+
});
|
|
169
|
+
});
|
|
85
170
|
attachLegacySessionCallbacks(sessionLogger, options);
|
|
86
171
|
const expiredHistory = await pruneExpiredToolCallSnapshotHistoryFiles(options.repoRoot);
|
|
87
172
|
if (expiredHistory.failedCount > 0) {
|
|
@@ -123,6 +208,7 @@ export async function runConductorSession(options) {
|
|
|
123
208
|
let githubMonitor;
|
|
124
209
|
let activeResumeStorePath;
|
|
125
210
|
let activeStoreLocation = 'user-path-hash';
|
|
211
|
+
let drainStartupResumeStoreWrites = async () => { };
|
|
126
212
|
const pendingGitHubRecoveries = [];
|
|
127
213
|
const conductorCwd = options.conductorCwd ?? process.cwd();
|
|
128
214
|
let cursorBootstrapRunId;
|
|
@@ -139,6 +225,11 @@ export async function runConductorSession(options) {
|
|
|
139
225
|
const loadedResumeStore = locatedResumeStore.resumeStore;
|
|
140
226
|
activeResumeStorePath = locatedResumeStore.path;
|
|
141
227
|
activeStoreLocation = locatedResumeStore.location;
|
|
228
|
+
const savedConfigDigestScheme = loadedResumeStore.ensembleConfigDigestScheme;
|
|
229
|
+
const effectiveConfigDigestScheme = savedConfigDigestScheme ?? 'full';
|
|
230
|
+
const currentEnsembleConfigDigest = effectiveConfigDigestScheme === 'resume-feasibility'
|
|
231
|
+
? computeResumeFeasibilityConfigDigest(ensembleConfig)
|
|
232
|
+
: computeEnsembleConfigDigest(ensembleConfig);
|
|
142
233
|
assertSessionResumeStoreMatches(loadedResumeStore, {
|
|
143
234
|
conductorAgentId: options.resumeAgentId,
|
|
144
235
|
issueUrl: options.issueUrl,
|
|
@@ -146,7 +237,7 @@ export async function runConductorSession(options) {
|
|
|
146
237
|
conductorBackend,
|
|
147
238
|
mcpConfigDigest,
|
|
148
239
|
mcpConfigConfigured,
|
|
149
|
-
ensembleConfigDigest,
|
|
240
|
+
ensembleConfigDigest: currentEnsembleConfigDigest,
|
|
150
241
|
});
|
|
151
242
|
if (loadedResumeStore.ensembleConfigDigest === undefined) {
|
|
152
243
|
sessionLogger.emit({
|
|
@@ -154,10 +245,31 @@ export async function runConductorSession(options) {
|
|
|
154
245
|
message: 'SessionResumeStore has no ensemble config digest; config consistency cannot be verified for this legacy session.',
|
|
155
246
|
});
|
|
156
247
|
}
|
|
248
|
+
const hasComparableLegacyFullDigest = effectiveConfigDigestScheme === 'full' &&
|
|
249
|
+
loadedResumeStore.ensembleConfigDigest !== undefined;
|
|
250
|
+
if (loadedResumeStore.acpModelConfigDigest === undefined &&
|
|
251
|
+
!hasComparableLegacyFullDigest) {
|
|
252
|
+
sessionLogger.emit({
|
|
253
|
+
type: 'harness.warning',
|
|
254
|
+
message: 'SessionResumeStore has no ACP model config digest; this diagnostic metadata is unavailable and will not be backfilled. Resume is not blocked by its absence.',
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
// Keep the persisted scheme and digest paired across resume and flush.
|
|
258
|
+
// Legacy stores without a digest remain unverifiable until a new session
|
|
259
|
+
// is started; a resume must not silently relabel their existing state.
|
|
260
|
+
ensembleConfigDigestScheme = savedConfigDigestScheme;
|
|
261
|
+
ensembleConfigDigest = loadedResumeStore.ensembleConfigDigest;
|
|
262
|
+
// This digest describes the config used when the session was first
|
|
263
|
+
// recorded. Keep it as diagnostic metadata; model changes do not gate
|
|
264
|
+
// resume, and a missing legacy value must not be backfilled.
|
|
265
|
+
acpModelConfigDigest = loadedResumeStore.acpModelConfigDigest;
|
|
157
266
|
// A previously completed lifecycle no longer describes this instance once
|
|
158
267
|
// resume begins. Persist unknown before restoring or attaching anything so
|
|
159
|
-
// a crash
|
|
160
|
-
//
|
|
268
|
+
// a crash during backend resume cannot leave stale `closed` / `exited` data
|
|
269
|
+
// behind. Startup cleanup restores the previous record only if every
|
|
270
|
+
// acquired resource is released successfully; otherwise keep this record
|
|
271
|
+
// marked unknown/running so it does not claim a possibly active session is
|
|
272
|
+
// closed.
|
|
161
273
|
const resumeStore = {
|
|
162
274
|
...loadedResumeStore,
|
|
163
275
|
lifecycle: 'unknown',
|
|
@@ -169,7 +281,22 @@ export async function runConductorSession(options) {
|
|
|
169
281
|
cursorBootstrapRunId = loadedResumeStore.cursorBootstrapRunId;
|
|
170
282
|
harnessPhase = 'running';
|
|
171
283
|
harnessModifiedAt = resumeStore.modifiedAt;
|
|
284
|
+
let resumeStoreActivationCommitted = false;
|
|
285
|
+
startupCleanup.add(async () => {
|
|
286
|
+
if (!resumeStoreActivationCommitted)
|
|
287
|
+
return;
|
|
288
|
+
await drainStartupResumeStoreWrites();
|
|
289
|
+
if (startupCleanup.hasFailures) {
|
|
290
|
+
sessionLogger.emit({
|
|
291
|
+
type: 'harness.warning',
|
|
292
|
+
message: '起動失敗後の後始末が完了しなかったため、再開記録を unknown/running のまま保ちます。',
|
|
293
|
+
});
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
await saveSessionResumeStore(locatedResumeStore.path, loadedResumeStore);
|
|
297
|
+
});
|
|
172
298
|
await saveSessionResumeStore(activeResumeStorePath, resumeStore);
|
|
299
|
+
resumeStoreActivationCommitted = true;
|
|
173
300
|
const history = await loadToolCallSnapshotHistory({
|
|
174
301
|
repoRoot: options.repoRoot,
|
|
175
302
|
conductorAgentId: options.resumeAgentId,
|
|
@@ -334,22 +461,22 @@ export async function runConductorSession(options) {
|
|
|
334
461
|
shutdownIntentController.abort();
|
|
335
462
|
abortAfterOpenQuestionDrain(drainedShutdownController);
|
|
336
463
|
};
|
|
464
|
+
let unregisterProcessSignalHandlers = () => { };
|
|
465
|
+
const removeShutdownDrainListener = () => shutdownSignal.removeEventListener('abort', onShutdownSignal);
|
|
466
|
+
const onSignal = () => shutdownController.abort();
|
|
467
|
+
unregisterProcessSignalHandlers = () => {
|
|
468
|
+
process.off('SIGINT', onSignal);
|
|
469
|
+
process.off('SIGTERM', onSignal);
|
|
470
|
+
removeShutdownDrainListener();
|
|
471
|
+
};
|
|
472
|
+
startupCleanup.add(() => unregisterProcessSignalHandlers());
|
|
337
473
|
shutdownSignal.addEventListener('abort', onShutdownSignal, { once: true });
|
|
338
474
|
if (shutdownSignal.aborted)
|
|
339
475
|
onShutdownSignal();
|
|
340
|
-
let unregisterProcessSignalHandlers = () => { };
|
|
341
|
-
const removeShutdownDrainListener = () => shutdownSignal.removeEventListener('abort', onShutdownSignal);
|
|
342
|
-
unregisterProcessSignalHandlers = removeShutdownDrainListener;
|
|
343
476
|
if (ownsShutdownController &&
|
|
344
477
|
options.registerProcessSignalHandlers !== false) {
|
|
345
|
-
const onSignal = () => shutdownController.abort();
|
|
346
478
|
process.once('SIGINT', onSignal);
|
|
347
479
|
process.once('SIGTERM', onSignal);
|
|
348
|
-
unregisterProcessSignalHandlers = () => {
|
|
349
|
-
process.off('SIGINT', onSignal);
|
|
350
|
-
process.off('SIGTERM', onSignal);
|
|
351
|
-
removeShutdownDrainListener();
|
|
352
|
-
};
|
|
353
480
|
}
|
|
354
481
|
const permissionPipeline = options.permissionPipeline ??
|
|
355
482
|
new PermissionPipeline({ policy: options.permissionPolicy });
|
|
@@ -598,33 +725,7 @@ export async function runConductorSession(options) {
|
|
|
598
725
|
});
|
|
599
726
|
},
|
|
600
727
|
});
|
|
601
|
-
|
|
602
|
-
sessionLogger.emit({
|
|
603
|
-
type: 'harness.worker.bootstrap',
|
|
604
|
-
workers: workers.map((worker) => ({
|
|
605
|
-
name: worker.name,
|
|
606
|
-
kind: worker.kind,
|
|
607
|
-
})),
|
|
608
|
-
mode: 'init',
|
|
609
|
-
});
|
|
610
|
-
}
|
|
611
|
-
workerSession.startWorkers();
|
|
612
|
-
if (resumeToolCallSnapshots.length > 0) {
|
|
613
|
-
const workerIdsByName = new Map();
|
|
614
|
-
workers.forEach((worker, index) => {
|
|
615
|
-
const workerId = workerSession.startedWorkerIds[index];
|
|
616
|
-
if (workerId)
|
|
617
|
-
workerIdsByName.set(worker.name, workerId);
|
|
618
|
-
});
|
|
619
|
-
const restoreResult = toolCallSnapshots.restore(resumeToolCallSnapshots, workerIdsByName);
|
|
620
|
-
if (restoreResult.skippedCount > 0) {
|
|
621
|
-
toolCallHistoryDirty = true;
|
|
622
|
-
sessionLogger.emit({
|
|
623
|
-
type: 'harness.warning',
|
|
624
|
-
message: `${restoreResult.skippedCount} tool-call snapshot entr${restoreResult.skippedCount === 1 ? 'y was' : 'ies were'} skipped because its worker is no longer available or its data is invalid.`,
|
|
625
|
-
});
|
|
626
|
-
}
|
|
627
|
-
}
|
|
728
|
+
startupCleanup.add(() => workerSession.stop({ force: true }));
|
|
628
729
|
const recordAnsweredQuestion = (answered) => {
|
|
629
730
|
const record = openQuestionToEscalationRecord(answered);
|
|
630
731
|
if (record) {
|
|
@@ -778,7 +879,10 @@ export async function runConductorSession(options) {
|
|
|
778
879
|
? { piSkills: activeProfile.conductor?.piSkills !== false }
|
|
779
880
|
: {}),
|
|
780
881
|
apiKey: options.apiKey,
|
|
781
|
-
modelId:
|
|
882
|
+
modelId: conductorFactoryModelId,
|
|
883
|
+
...(conductorReasoning !== undefined
|
|
884
|
+
? { reasoning: conductorReasoning }
|
|
885
|
+
: {}),
|
|
782
886
|
...(conductorBackend === 'pi'
|
|
783
887
|
? {
|
|
784
888
|
pi: resolveConductorPiResourcePaths({
|
|
@@ -808,9 +912,29 @@ export async function runConductorSession(options) {
|
|
|
808
912
|
pi: conductorOptions.pi,
|
|
809
913
|
})) || undefined
|
|
810
914
|
: undefined;
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
915
|
+
shutdownSignal.throwIfAborted();
|
|
916
|
+
try {
|
|
917
|
+
conductorAgent = options.resumeAgentId
|
|
918
|
+
? await conductorAgentFactory.resume(options.resumeAgentId, conductorOptions)
|
|
919
|
+
: await conductorAgentFactory.create(conductorOptions);
|
|
920
|
+
}
|
|
921
|
+
catch (error) {
|
|
922
|
+
if (isConductorAgentFactoryCleanupIncomplete(error)) {
|
|
923
|
+
startupCleanup.recordCleanupFailure(error);
|
|
924
|
+
}
|
|
925
|
+
throw error;
|
|
926
|
+
}
|
|
927
|
+
startupCleanup.add(async () => {
|
|
928
|
+
if (options.resumeAgentId && conductorAgent.closeForResume) {
|
|
929
|
+
await conductorAgent.closeForResume();
|
|
930
|
+
}
|
|
931
|
+
else {
|
|
932
|
+
await conductorAgent.close();
|
|
933
|
+
}
|
|
934
|
+
});
|
|
935
|
+
// Factories may not be abortable while pending. Register the returned agent
|
|
936
|
+
// before checking shutdown so a late success is still closed by the guard.
|
|
937
|
+
shutdownSignal.throwIfAborted();
|
|
814
938
|
const conductorHandle = { conductor: conductorAgent };
|
|
815
939
|
activeResumeStorePath ??= sessionResumeStorePath({
|
|
816
940
|
repoRoot: options.repoRoot,
|
|
@@ -844,6 +968,9 @@ export async function runConductorSession(options) {
|
|
|
844
968
|
},
|
|
845
969
|
};
|
|
846
970
|
let sessionResumeStoreWriteQueue = Promise.resolve();
|
|
971
|
+
drainStartupResumeStoreWrites = async () => {
|
|
972
|
+
await sessionResumeStoreWriteQueue;
|
|
973
|
+
};
|
|
847
974
|
let terminalLifecycle;
|
|
848
975
|
let terminalResumeStoreFinalFlushQueued = false;
|
|
849
976
|
const enqueueSessionResumeStoreWrite = (write) => {
|
|
@@ -887,7 +1014,15 @@ export async function runConductorSession(options) {
|
|
|
887
1014
|
sequence: snapshot.sequence,
|
|
888
1015
|
workers,
|
|
889
1016
|
mcpConfigDigest,
|
|
890
|
-
|
|
1017
|
+
...(ensembleConfigDigestScheme !== undefined
|
|
1018
|
+
? { ensembleConfigDigestScheme }
|
|
1019
|
+
: {}),
|
|
1020
|
+
...(ensembleConfigDigest !== undefined
|
|
1021
|
+
? { ensembleConfigDigest }
|
|
1022
|
+
: {}),
|
|
1023
|
+
...(acpModelConfigDigest !== undefined
|
|
1024
|
+
? { acpModelConfigDigest }
|
|
1025
|
+
: {}),
|
|
891
1026
|
...(persistedLifecycle ? { lifecycle: persistedLifecycle } : {}),
|
|
892
1027
|
...(githubMonitorCursor ? { githubMonitor: githubMonitorCursor } : {}),
|
|
893
1028
|
updatedAt: Date.now(),
|
|
@@ -1033,8 +1168,6 @@ export async function runConductorSession(options) {
|
|
|
1033
1168
|
emitToolCallHistoryFlushWarning();
|
|
1034
1169
|
}
|
|
1035
1170
|
};
|
|
1036
|
-
if (toolCallHistoryDirty)
|
|
1037
|
-
scheduleToolCallHistoryFlush();
|
|
1038
1171
|
let sendCount = 0;
|
|
1039
1172
|
let autonomousTurns = 0;
|
|
1040
1173
|
const monitorDefaults = ensembleConfig.github.monitor;
|
|
@@ -1111,6 +1244,9 @@ export async function runConductorSession(options) {
|
|
|
1111
1244
|
}
|
|
1112
1245
|
},
|
|
1113
1246
|
});
|
|
1247
|
+
startupCleanup.add(async () => {
|
|
1248
|
+
await githubMonitor?.stop();
|
|
1249
|
+
});
|
|
1114
1250
|
githubMonitor.start();
|
|
1115
1251
|
}
|
|
1116
1252
|
dispatchHoldReminderMonitor = createDispatchHoldReminderMonitor({
|
|
@@ -1127,17 +1263,12 @@ export async function runConductorSession(options) {
|
|
|
1127
1263
|
reminderIntervalMs: options.dispatchHoldReminderMs,
|
|
1128
1264
|
shutdownSignal,
|
|
1129
1265
|
});
|
|
1266
|
+
startupCleanup.add(() => dispatchHoldReminderMonitor.stop());
|
|
1130
1267
|
dispatchHoldReminderMonitor.start();
|
|
1131
1268
|
let permissionDeadlockMonitor;
|
|
1132
1269
|
if (!options.disablePermissionDeadlockMonitor) {
|
|
1133
1270
|
permissionDeadlockMonitor = createPermissionDeadlockMonitor({
|
|
1134
1271
|
pipeline: permissionPipeline,
|
|
1135
|
-
getActivitySnapshot: () => ({
|
|
1136
|
-
attachInFlight: workerSession.runtime.attachInFlightCount,
|
|
1137
|
-
hasProcessingWorker: workerSession.runtime
|
|
1138
|
-
.listWorkerStatuses()
|
|
1139
|
-
.some((worker) => worker.state === 'processing'),
|
|
1140
|
-
}),
|
|
1141
1272
|
onWarning: (message) => {
|
|
1142
1273
|
sessionLogger.emit({ type: 'harness.warning', message });
|
|
1143
1274
|
},
|
|
@@ -1156,6 +1287,7 @@ export async function runConductorSession(options) {
|
|
|
1156
1287
|
pollIntervalMs: options.permissionDeadlockPollMs,
|
|
1157
1288
|
shutdownSignal,
|
|
1158
1289
|
});
|
|
1290
|
+
startupCleanup.add(() => permissionDeadlockMonitor.stop());
|
|
1159
1291
|
permissionDeadlockMonitor.start();
|
|
1160
1292
|
}
|
|
1161
1293
|
let stopReason = 'completed';
|
|
@@ -1266,9 +1398,43 @@ export async function runConductorSession(options) {
|
|
|
1266
1398
|
});
|
|
1267
1399
|
if (typeof bindingDispose === 'function') {
|
|
1268
1400
|
disposeOperatorInput = bindingDispose;
|
|
1401
|
+
startupCleanup.add(() => disposeOperatorInput?.());
|
|
1269
1402
|
}
|
|
1270
1403
|
}
|
|
1271
1404
|
try {
|
|
1405
|
+
if (workers.length > 0 && !activeLazyWorkers) {
|
|
1406
|
+
sessionLogger.emit({
|
|
1407
|
+
type: 'harness.worker.bootstrap',
|
|
1408
|
+
workers: workers.map((worker) => ({
|
|
1409
|
+
name: worker.name,
|
|
1410
|
+
kind: worker.kind,
|
|
1411
|
+
})),
|
|
1412
|
+
mode: 'init',
|
|
1413
|
+
});
|
|
1414
|
+
}
|
|
1415
|
+
// Keep startup cleanup active through startup logging and the final
|
|
1416
|
+
// shutdown check. No await follows before workers start.
|
|
1417
|
+
shutdownSignal.throwIfAborted();
|
|
1418
|
+
startupCleanup.handoff();
|
|
1419
|
+
workerSession.startWorkers();
|
|
1420
|
+
if (resumeToolCallSnapshots.length > 0) {
|
|
1421
|
+
const workerIdsByName = new Map();
|
|
1422
|
+
workers.forEach((worker, index) => {
|
|
1423
|
+
const workerId = workerSession.startedWorkerIds[index];
|
|
1424
|
+
if (workerId)
|
|
1425
|
+
workerIdsByName.set(worker.name, workerId);
|
|
1426
|
+
});
|
|
1427
|
+
const restoreResult = toolCallSnapshots.restore(resumeToolCallSnapshots, workerIdsByName);
|
|
1428
|
+
if (restoreResult.skippedCount > 0) {
|
|
1429
|
+
toolCallHistoryDirty = true;
|
|
1430
|
+
sessionLogger.emit({
|
|
1431
|
+
type: 'harness.warning',
|
|
1432
|
+
message: `${restoreResult.skippedCount} tool-call snapshot entr${restoreResult.skippedCount === 1 ? 'y was' : 'ies were'} skipped because its worker is no longer available or its data is invalid.`,
|
|
1433
|
+
});
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
if (toolCallHistoryDirty)
|
|
1437
|
+
scheduleToolCallHistoryFlush();
|
|
1272
1438
|
openQuestionControlServer = await startOpenQuestionControlServer({
|
|
1273
1439
|
listOpenQuestions: () => openQuestions.listOpen(),
|
|
1274
1440
|
getLastConductorMessage: () => lastConductorMessage,
|
|
@@ -1615,6 +1781,14 @@ export async function runConductorSession(options) {
|
|
|
1615
1781
|
agentId: conductorHandle.conductor.agentId,
|
|
1616
1782
|
escalations,
|
|
1617
1783
|
openQuestions: openQuestions.list(),
|
|
1784
|
+
sessionModels: buildSessionModelsSummary({
|
|
1785
|
+
backend: conductorBackend,
|
|
1786
|
+
explicitConductorModelId: resolvedConductorModelId,
|
|
1787
|
+
explicitConductorReasoning: conductorReasoning,
|
|
1788
|
+
config: ensembleConfig,
|
|
1789
|
+
workers,
|
|
1790
|
+
usageTracker: sessionUsageTracker,
|
|
1791
|
+
}),
|
|
1618
1792
|
sessionUsage: sessionUsage.totals.rounds > 0 ||
|
|
1619
1793
|
sessionUsage.cost != null ||
|
|
1620
1794
|
sessionUsage.compactionCount != null
|