@agents-ensemble/core 0.14.0 → 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.
Files changed (151) hide show
  1. package/dist/acp/acp-client.d.ts.map +1 -1
  2. package/dist/acp/acp-client.js +19 -1
  3. package/dist/acp/acp-client.js.map +1 -1
  4. package/dist/acp/resolve-acp-spawn.d.ts +1 -1
  5. package/dist/acp/resolve-acp-spawn.d.ts.map +1 -1
  6. package/dist/acp/resolve-acp-spawn.js +2 -2
  7. package/dist/acp/resolve-acp-spawn.js.map +1 -1
  8. package/dist/acp/testing/fake-acp-server.d.ts +3 -2
  9. package/dist/acp/testing/fake-acp-server.d.ts.map +1 -1
  10. package/dist/acp/testing/fake-acp-server.js +1 -0
  11. package/dist/acp/testing/fake-acp-server.js.map +1 -1
  12. package/dist/conductor/conductor-agent.d.ts +2 -0
  13. package/dist/conductor/conductor-agent.d.ts.map +1 -1
  14. package/dist/conductor/conductor-session-driver.d.ts +5 -2
  15. package/dist/conductor/conductor-session-driver.d.ts.map +1 -1
  16. package/dist/conductor/conductor-session-driver.js +65 -5
  17. package/dist/conductor/conductor-session-driver.js.map +1 -1
  18. package/dist/conductor/conductor-session.d.ts +2 -0
  19. package/dist/conductor/conductor-session.d.ts.map +1 -1
  20. package/dist/conductor/conductor-session.js +276 -85
  21. package/dist/conductor/conductor-session.js.map +1 -1
  22. package/dist/conductor/cursor-sdk-conductor-agent.d.ts +3 -0
  23. package/dist/conductor/cursor-sdk-conductor-agent.d.ts.map +1 -1
  24. package/dist/conductor/cursor-sdk-conductor-agent.js +48 -15
  25. package/dist/conductor/cursor-sdk-conductor-agent.js.map +1 -1
  26. package/dist/conductor/index.d.ts +5 -1
  27. package/dist/conductor/index.d.ts.map +1 -1
  28. package/dist/conductor/index.js +2 -0
  29. package/dist/conductor/index.js.map +1 -1
  30. package/dist/conductor/operator-command.d.ts +22 -0
  31. package/dist/conductor/operator-command.d.ts.map +1 -1
  32. package/dist/conductor/operator-command.js +29 -0
  33. package/dist/conductor/operator-command.js.map +1 -1
  34. package/dist/conductor/operator-input-binding.d.ts +3 -0
  35. package/dist/conductor/operator-input-binding.d.ts.map +1 -1
  36. package/dist/conductor/pi-conductor-agent.d.ts +1 -0
  37. package/dist/conductor/pi-conductor-agent.d.ts.map +1 -1
  38. package/dist/conductor/pi-conductor-agent.js +23 -1
  39. package/dist/conductor/pi-conductor-agent.js.map +1 -1
  40. package/dist/conductor/session/dispatch-hold-reminder-monitor.d.ts +28 -0
  41. package/dist/conductor/session/dispatch-hold-reminder-monitor.d.ts.map +1 -0
  42. package/dist/conductor/session/dispatch-hold-reminder-monitor.js +88 -0
  43. package/dist/conductor/session/dispatch-hold-reminder-monitor.js.map +1 -0
  44. package/dist/conductor/session/dispatch-hold.d.ts +1 -1
  45. package/dist/conductor/session/dispatch-hold.d.ts.map +1 -1
  46. package/dist/conductor/session/events/index.d.ts +1 -1
  47. package/dist/conductor/session/events/index.d.ts.map +1 -1
  48. package/dist/conductor/session/events/index.js.map +1 -1
  49. package/dist/conductor/session/events/session-event.d.ts +2 -0
  50. package/dist/conductor/session/events/session-event.d.ts.map +1 -1
  51. package/dist/conductor/session/events/session-log-event.d.ts +3 -1
  52. package/dist/conductor/session/events/session-log-event.d.ts.map +1 -1
  53. package/dist/conductor/session/select-dispatch-batch.d.ts +2 -0
  54. package/dist/conductor/session/select-dispatch-batch.d.ts.map +1 -1
  55. package/dist/conductor/session/select-dispatch-batch.js +8 -5
  56. package/dist/conductor/session/select-dispatch-batch.js.map +1 -1
  57. package/dist/conductor/session/session-event.d.ts +1 -1
  58. package/dist/conductor/session/session-event.d.ts.map +1 -1
  59. package/dist/conductor/submit-operator-input.d.ts +12 -1
  60. package/dist/conductor/submit-operator-input.d.ts.map +1 -1
  61. package/dist/conductor/submit-operator-input.js +28 -7
  62. package/dist/conductor/submit-operator-input.js.map +1 -1
  63. package/dist/config/defaults.d.ts.map +1 -1
  64. package/dist/config/defaults.js +3 -0
  65. package/dist/config/defaults.js.map +1 -1
  66. package/dist/config/index.d.ts +1 -1
  67. package/dist/config/index.d.ts.map +1 -1
  68. package/dist/config/load-ensemble-config.d.ts +2 -0
  69. package/dist/config/load-ensemble-config.d.ts.map +1 -1
  70. package/dist/config/load-ensemble-config.js +18 -5
  71. package/dist/config/load-ensemble-config.js.map +1 -1
  72. package/dist/config/parse-config.d.ts.map +1 -1
  73. package/dist/config/parse-config.js +12 -0
  74. package/dist/config/parse-config.js.map +1 -1
  75. package/dist/config/types.d.ts +5 -0
  76. package/dist/config/types.d.ts.map +1 -1
  77. package/dist/dispatch/attach-worker.d.ts +2 -1
  78. package/dist/dispatch/attach-worker.d.ts.map +1 -1
  79. package/dist/dispatch/attach-worker.js +1 -0
  80. package/dist/dispatch/attach-worker.js.map +1 -1
  81. package/dist/dispatch/worker-acp-session.d.ts +2 -2
  82. package/dist/dispatch/worker-acp-session.d.ts.map +1 -1
  83. package/dist/dispatch/worker-acp-session.js +1 -1
  84. package/dist/dispatch/worker-acp-session.js.map +1 -1
  85. package/dist/github/github-auth.d.ts +4 -0
  86. package/dist/github/github-auth.d.ts.map +1 -1
  87. package/dist/github/github-auth.js +37 -0
  88. package/dist/github/github-auth.js.map +1 -1
  89. package/dist/github/github-monitor-cursor.d.ts +10 -10
  90. package/dist/github/github-monitor-cursor.d.ts.map +1 -1
  91. package/dist/github/github-monitor-cursor.js +1 -1
  92. package/dist/github/github-monitor-cursor.js.map +1 -1
  93. package/dist/github/github-monitor-error.d.ts +1 -1
  94. package/dist/github/github-monitor-error.d.ts.map +1 -1
  95. package/dist/github/github-monitor-error.js +13 -1
  96. package/dist/github/github-monitor-error.js.map +1 -1
  97. package/dist/github/github-monitor.d.ts +3 -3
  98. package/dist/github/github-monitor.d.ts.map +1 -1
  99. package/dist/github/github-monitor.js +3 -3
  100. package/dist/github/github-monitor.js.map +1 -1
  101. package/dist/github/index.d.ts +1 -1
  102. package/dist/github/index.d.ts.map +1 -1
  103. package/dist/github/index.js +1 -1
  104. package/dist/github/index.js.map +1 -1
  105. package/dist/mcp/load-mcp-config.d.ts +1 -1
  106. package/dist/mcp/load-mcp-config.js +1 -1
  107. package/dist/operator-runtime.d.ts +11 -4
  108. package/dist/operator-runtime.d.ts.map +1 -1
  109. package/dist/operator-runtime.js +6 -2
  110. package/dist/operator-runtime.js.map +1 -1
  111. package/dist/prompt/build-worker-attach-prompt.d.ts +2 -1
  112. package/dist/prompt/build-worker-attach-prompt.d.ts.map +1 -1
  113. package/dist/prompt/build-worker-attach-prompt.js +4 -1
  114. package/dist/prompt/build-worker-attach-prompt.js.map +1 -1
  115. package/dist/prompt/build-worker-prompt.d.ts +2 -1
  116. package/dist/prompt/build-worker-prompt.d.ts.map +1 -1
  117. package/dist/prompt/build-worker-prompt.js +4 -1
  118. package/dist/prompt/build-worker-prompt.js.map +1 -1
  119. package/dist/prompt/load-user-instructions.d.ts +1 -1
  120. package/dist/prompt/load-user-instructions.js +1 -1
  121. package/dist/prompt/modules/ensemble/worker-base-module.d.ts.map +1 -1
  122. package/dist/prompt/modules/ensemble/worker-base-module.js +8 -0
  123. package/dist/prompt/modules/ensemble/worker-base-module.js.map +1 -1
  124. package/dist/runtime/worker-runtime.d.ts +2 -0
  125. package/dist/runtime/worker-runtime.d.ts.map +1 -1
  126. package/dist/runtime/worker-runtime.js +1 -0
  127. package/dist/runtime/worker-runtime.js.map +1 -1
  128. package/dist/runtime/worker-session.d.ts +2 -1
  129. package/dist/runtime/worker-session.d.ts.map +1 -1
  130. package/dist/runtime/worker-session.js +3 -0
  131. package/dist/runtime/worker-session.js.map +1 -1
  132. package/dist/session/index.d.ts +8 -4
  133. package/dist/session/index.d.ts.map +1 -1
  134. package/dist/session/index.js +4 -2
  135. package/dist/session/index.js.map +1 -1
  136. package/dist/session/open-question-control.d.ts +46 -2
  137. package/dist/session/open-question-control.d.ts.map +1 -1
  138. package/dist/session/open-question-control.js +127 -11
  139. package/dist/session/open-question-control.js.map +1 -1
  140. package/dist/session/session-resume-store.d.ts +119 -0
  141. package/dist/session/session-resume-store.d.ts.map +1 -0
  142. package/dist/session/session-resume-store.js +354 -0
  143. package/dist/session/session-resume-store.js.map +1 -0
  144. package/dist/session/tool-call-snapshot-history.d.ts +1 -1
  145. package/dist/session/tool-call-snapshot-history.js +4 -4
  146. package/dist/session/tool-call-snapshot-history.js.map +1 -1
  147. package/package.json +2 -2
  148. package/dist/session/session-sidecar.d.ts +0 -76
  149. package/dist/session/session-sidecar.d.ts.map +0 -1
  150. package/dist/session/session-sidecar.js +0 -261
  151. package/dist/session/session-sidecar.js.map +0 -1
@@ -31,20 +31,22 @@ 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
- import { assertSessionSidecarMatches, requireSessionSidecarForResume, saveSessionSidecar, SESSION_SIDECAR_VERSION, sessionSidecarPath, } from '../session/session-sidecar.js';
40
- import { saveSessionStatusLine, sessionStateStorePath, } from '../session/session-state-store.js';
40
+ import { parseOperatorMessageDelivery } from './operator-command.js';
41
+ import { assertSessionResumeStoreMatches, requireSessionResumeStoreForResume, saveSessionResumeStore, SESSION_RESUME_STORE_VERSION, sessionResumeStorePath, } from '../session/session-resume-store.js';
42
+ import { saveSessionStatusLine, sessionStateStorePath, loadSessionStateStore, } from '../session/session-state-store.js';
41
43
  import { loadToolCallSnapshotHistory, pruneExpiredToolCallSnapshotHistoryFiles, saveToolCallSnapshotHistory, toolCallSnapshotHistoryPath, } from '../session/tool-call-snapshot-history.js';
42
44
  import { loadOpenQuestionsStore, openQuestionsStorePath, saveOpenQuestionsStore, } from '../session/open-questions-store.js';
43
- import { OpenQuestionControlOperationError, startOpenQuestionControlServer, } from '../session/open-question-control.js';
45
+ import { CONDUCTOR_MESSAGE_HISTORY_MAX_ENTRIES, OpenQuestionControlOperationError, startOpenQuestionControlServer, } from '../session/open-question-control.js';
44
46
  import { formatPermissionSummaryForOperator } from '../permission/format-permission-summary-for-operator.js';
45
47
  import { enrichSessionUsageWithCost } from '../usage/enrich-session-usage-cost.js';
46
48
  import { SessionUsageTracker } from '../usage/session-usage-tracker.js';
47
- import { submitOperatorInput } from './submit-operator-input.js';
49
+ import { enqueueOperatorMessage as enqueueOperatorMessageEvent, submitOperatorInput, } from './submit-operator-input.js';
48
50
  import { createGitHubMonitor, } from '../github/github-monitor.js';
49
51
  import { resolveConductorBackendSetting, resolveConductorPiResourcePaths, resolveGitHubMonitorEnabled, } from '../config/resolve-settings.js';
50
52
  import { GitHubMonitorError } from '../github/github-monitor-error.js';
@@ -88,11 +90,17 @@ export async function runConductorSession(options) {
88
90
  const escalations = [];
89
91
  const openQuestions = new OpenQuestionRegistry();
90
92
  let harnessSocketPath;
93
+ let harnessPhase;
94
+ let harnessModifiedAt;
95
+ let reportHarnessActivity = () => { };
91
96
  let openQuestionControlServer;
97
+ let lastConductorMessage = null;
98
+ const conductorMessages = [];
92
99
  const eventQueue = new SessionEventQueue();
93
100
  const openQuestionDispatchReceipts = new WeakMap();
94
101
  const pendingOpenQuestionDispatchReceipts = new Set();
95
102
  const dispatchHoldState = createDispatchHoldState();
103
+ let dispatchHoldReminderMonitor;
96
104
  const onDispatchHoldChanged = (change) => {
97
105
  sessionLogger.emit({
98
106
  type: 'conductor.dispatch_hold',
@@ -103,6 +111,7 @@ export async function runConductorSession(options) {
103
111
  ? { flushedEventCount: change.flushedEventCount }
104
112
  : {}),
105
113
  });
114
+ dispatchHoldReminderMonitor?.onHoldChanged(change.hold);
106
115
  };
107
116
  let activeProfile = options.profile;
108
117
  let activeLazyWorkers = ensembleConfig.session.lazyWorkers;
@@ -117,11 +126,11 @@ export async function runConductorSession(options) {
117
126
  let resumeToolCallSnapshots = [];
118
127
  let toolCallHistoryNeedsRewrite = false;
119
128
  if (options.resumeAgentId) {
120
- const loadedSidecar = await requireSessionSidecarForResume({
129
+ const loadedResumeStore = await requireSessionResumeStoreForResume({
121
130
  repoRoot: options.repoRoot,
122
131
  conductorAgentId: options.resumeAgentId,
123
132
  });
124
- assertSessionSidecarMatches(loadedSidecar, {
133
+ assertSessionResumeStoreMatches(loadedResumeStore, {
125
134
  conductorAgentId: options.resumeAgentId,
126
135
  issueUrl: options.issueUrl,
127
136
  repoRoot: options.repoRoot,
@@ -131,18 +140,22 @@ export async function runConductorSession(options) {
131
140
  });
132
141
  // A previously completed lifecycle no longer describes this instance once
133
142
  // resume begins. Persist unknown before restoring or attaching anything so
134
- // a crash before the first regular sidecar flush cannot leave stale
143
+ // a crash before the first regular SessionResumeStore flush cannot leave stale
135
144
  // `closed` / `exited` data behind.
136
- const sidecar = {
137
- ...loadedSidecar,
145
+ const resumeStore = {
146
+ ...loadedResumeStore,
138
147
  lifecycle: 'unknown',
148
+ harnessPhase: 'running',
149
+ modifiedAt: Date.now(),
139
150
  harnessSocketPath: undefined,
140
151
  updatedAt: Date.now(),
141
152
  };
142
- await saveSessionSidecar(sessionSidecarPath({
153
+ harnessPhase = 'running';
154
+ harnessModifiedAt = resumeStore.modifiedAt;
155
+ await saveSessionResumeStore(sessionResumeStorePath({
143
156
  repoRoot: options.repoRoot,
144
157
  conductorAgentId: options.resumeAgentId,
145
- }), sidecar);
158
+ }), resumeStore);
146
159
  const history = await loadToolCallSnapshotHistory({
147
160
  repoRoot: options.repoRoot,
148
161
  conductorAgentId: options.resumeAgentId,
@@ -165,8 +178,8 @@ export async function runConductorSession(options) {
165
178
  }
166
179
  else {
167
180
  const legacySnapshot = {
168
- sequence: sidecar.sequence,
169
- openQuestions: sidecar.openQuestions,
181
+ sequence: resumeStore.sequence,
182
+ openQuestions: resumeStore.openQuestions,
170
183
  };
171
184
  openQuestions.restore(legacySnapshot);
172
185
  try {
@@ -176,15 +189,15 @@ export async function runConductorSession(options) {
176
189
  const detail = error instanceof Error ? error.message : String(error);
177
190
  sessionLogger.emit({
178
191
  type: 'harness.warning',
179
- message: `旧 sidecar から復元した open question を YAML にバックフィルできませんでした。今回のセッションは継続します。 error=${detail}`,
192
+ message: `旧形式の SessionResumeStore から復元した open question を YAML にバックフィルできませんでした。今回のセッションは継続します。 error=${detail}`,
180
193
  });
181
194
  }
182
195
  }
183
- activeProfile = sidecar.profile;
184
- // Sidecars written before this setting always started every worker eagerly.
185
- activeLazyWorkers = sidecar.lazyWorkers ?? false;
186
- githubMonitorCursor = sidecar.githubMonitor;
187
- for (const [name, worker] of Object.entries(sidecar.workers)) {
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)) {
188
201
  workerSessions.set(name, {
189
202
  acpSessionId: worker.acpSessionId,
190
203
  ...(worker.acpCwd ? { acpCwd: worker.acpCwd } : {}),
@@ -290,8 +303,8 @@ export async function runConductorSession(options) {
290
303
  }
291
304
  const permissionPipeline = options.permissionPipeline ??
292
305
  new PermissionPipeline({ policy: options.permissionPolicy });
293
- let scheduleSidecarFlush = () => { };
294
- let flushSidecar = async () => { };
306
+ let scheduleSessionResumeStoreFlush = () => { };
307
+ let flushSessionResumeStore = async () => { };
295
308
  let scheduleToolCallHistoryFlush = () => { };
296
309
  let flushToolCallHistory = async () => { };
297
310
  let toolCallHistoryDirty = toolCallHistoryNeedsRewrite;
@@ -357,6 +370,7 @@ export async function runConductorSession(options) {
357
370
  workers,
358
371
  lazyWorkers: activeLazyWorkers,
359
372
  sessionState,
373
+ ...(userInstructions ? { userInstructions } : {}),
360
374
  restoredWorkerSessions: Object.fromEntries(workerSessions),
361
375
  permissionPipeline,
362
376
  ...(options.connectAcp ? { connectAcp: options.connectAcp } : {}),
@@ -433,7 +447,7 @@ export async function runConductorSession(options) {
433
447
  : {}),
434
448
  });
435
449
  eventQueue.enqueue({ type: 'worker.completed', result });
436
- scheduleSidecarFlush();
450
+ scheduleSessionResumeStoreFlush();
437
451
  },
438
452
  onWorkerFailed: (failure) => {
439
453
  sessionLogger.emit({ type: 'worker.failed', failure });
@@ -442,6 +456,7 @@ export async function runConductorSession(options) {
442
456
  onPromptTelemetry: (event) => {
443
457
  switch (event.phase) {
444
458
  case 'started':
459
+ reportHarnessActivity('worker', true);
445
460
  sessionLogger.emit({
446
461
  type: 'harness.worker.prompt.started',
447
462
  name: event.name,
@@ -451,6 +466,7 @@ export async function runConductorSession(options) {
451
466
  });
452
467
  break;
453
468
  case 'completed':
469
+ reportHarnessActivity('worker', false);
454
470
  sessionLogger.emit({
455
471
  type: 'harness.worker.prompt.completed',
456
472
  name: event.name,
@@ -461,6 +477,7 @@ export async function runConductorSession(options) {
461
477
  });
462
478
  break;
463
479
  case 'failed':
480
+ reportHarnessActivity('worker', false);
464
481
  sessionLogger.emit({
465
482
  type: 'harness.worker.prompt.failed',
466
483
  name: event.name,
@@ -564,14 +581,14 @@ export async function runConductorSession(options) {
564
581
  sessionLogger.emit({ type: 'escalation.recorded', record });
565
582
  options.onEscalated?.(record);
566
583
  }
567
- scheduleSidecarFlush();
584
+ scheduleSessionResumeStoreFlush();
568
585
  };
569
586
  const askHumanTools = createAskHumanTool({
570
587
  registry: openQuestions,
571
588
  onEnqueued: (question) => {
572
589
  sessionLogger.emit({ type: 'open.question.enqueued', question });
573
590
  options.onOpenQuestionEnqueued?.(question);
574
- scheduleSidecarFlush();
591
+ scheduleSessionResumeStoreFlush();
575
592
  },
576
593
  });
577
594
  const answerOpenQuestionTools = createAnswerOpenQuestionTool({
@@ -618,7 +635,7 @@ export async function runConductorSession(options) {
618
635
  // Do not report success to the conductor until the explicit watch is
619
636
  // durable. This closes the crash window between the tool result and a
620
637
  // later best-effort flush.
621
- await flushSidecar();
638
+ await flushSessionResumeStore();
622
639
  },
623
640
  });
624
641
  const unregisterGitHubWatchTools = createUnregisterGitHubWatchTool({
@@ -629,7 +646,7 @@ export async function runConductorSession(options) {
629
646
  },
630
647
  onUnregistered: async (prNumber) => {
631
648
  githubMonitor?.unregisterPullRequest?.(prNumber);
632
- await flushSidecar();
649
+ await flushSessionResumeStore();
633
650
  },
634
651
  });
635
652
  let conductorAgent;
@@ -749,14 +766,15 @@ export async function runConductorSession(options) {
749
766
  });
750
767
  },
751
768
  };
752
- let sidecarFlushQueue = Promise.resolve();
769
+ let sessionResumeStoreWriteQueue = Promise.resolve();
753
770
  let terminalLifecycle;
754
- const enqueueSidecarWrite = (write) => {
755
- const nextWrite = sidecarFlushQueue.then(write);
756
- sidecarFlushQueue = nextWrite.catch(() => { });
771
+ let terminalResumeStoreFinalFlushQueued = false;
772
+ const enqueueSessionResumeStoreWrite = (write) => {
773
+ const nextWrite = sessionResumeStoreWriteQueue.then(write);
774
+ sessionResumeStoreWriteQueue = nextWrite.catch(() => { });
757
775
  return nextWrite;
758
776
  };
759
- const createSessionSidecar = (snapshot, lifecycle) => {
777
+ const createSessionResumeStore = (snapshot, lifecycle) => {
760
778
  const workers = {};
761
779
  for (const [name, worker] of workerSessions) {
762
780
  workers[name] = {
@@ -765,8 +783,13 @@ export async function runConductorSession(options) {
765
783
  ...(worker.acpSpawn ? { acpSpawn: worker.acpSpawn } : {}),
766
784
  };
767
785
  }
786
+ const persistedLifecycle = terminalLifecycle ??
787
+ lifecycle ??
788
+ (harnessPhase === 'running' || harnessPhase === 'idle'
789
+ ? 'unknown'
790
+ : undefined);
768
791
  return {
769
- version: SESSION_SIDECAR_VERSION,
792
+ version: SESSION_RESUME_STORE_VERSION,
770
793
  conductorAgentId: conductorHandle.conductor.agentId,
771
794
  conductorBackend,
772
795
  lazyWorkers: activeLazyWorkers,
@@ -775,13 +798,15 @@ export async function runConductorSession(options) {
775
798
  repoRoot: options.repoRoot,
776
799
  profile: structuredClone(activeProfile),
777
800
  ...(options.profilePath ? { profilePath: options.profilePath } : {}),
801
+ ...(harnessPhase ? { harnessPhase } : {}),
802
+ ...(harnessModifiedAt !== undefined
803
+ ? { modifiedAt: harnessModifiedAt }
804
+ : {}),
778
805
  openQuestions: snapshot.openQuestions,
779
806
  sequence: snapshot.sequence,
780
807
  workers,
781
808
  mcpConfigDigest,
782
- ...((terminalLifecycle ?? lifecycle)
783
- ? { lifecycle: terminalLifecycle ?? lifecycle }
784
- : {}),
809
+ ...(persistedLifecycle ? { lifecycle: persistedLifecycle } : {}),
785
810
  ...(githubMonitorCursor ? { githubMonitor: githubMonitorCursor } : {}),
786
811
  updatedAt: Date.now(),
787
812
  };
@@ -792,33 +817,90 @@ export async function runConductorSession(options) {
792
817
  conductorAgentId: conductorHandle.conductor.agentId,
793
818
  }), snapshot);
794
819
  };
795
- const persistSessionSidecar = async (snapshot, lifecycle) => {
796
- await saveSessionSidecar(sessionSidecarPath({
820
+ const persistSessionResumeStore = async (snapshot, lifecycle) => {
821
+ await saveSessionResumeStore(sessionResumeStorePath({
797
822
  repoRoot: options.repoRoot,
798
823
  conductorAgentId: conductorHandle.conductor.agentId,
799
- }), createSessionSidecar(snapshot, lifecycle));
824
+ }), createSessionResumeStore(snapshot, lifecycle));
800
825
  };
801
- flushSidecar = (lifecycle) => enqueueSidecarWrite(async () => {
802
- const snapshot = openQuestions.snapshot();
803
- await persistOpenQuestionsSnapshot(snapshot);
804
- await persistSessionSidecar(snapshot, lifecycle);
805
- });
806
- const reportSidecarFlushFailure = (error) => {
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) => {
807
852
  const detail = error instanceof Error ? error.message : String(error);
808
853
  sessionLogger.emit({
809
854
  type: 'harness.warning',
810
- message: `Open Question YAML / session sidecar を保存できませんでした。最新状態から再開できず、直前に保存された状態からの再開になる場合があります。 error=${detail}`,
855
+ message: `Open Question YAML / SessionResumeStore を保存できませんでした。最新状態から再開できず、直前に保存された状態からの再開になる場合があります。 error=${detail}`,
811
856
  });
812
857
  };
813
- const reportSessionSidecarWriteFailure = (error) => {
858
+ const reportSessionResumeStoreWriteFailure = (error) => {
814
859
  const detail = error instanceof Error ? error.message : String(error);
815
860
  sessionLogger.emit({
816
861
  type: 'harness.warning',
817
- message: `Session sidecar の更新に失敗しました。Open Question YAML の保存は完了しており、sidecar は後続 flush で同期します。 error=${detail}`,
862
+ message: `SessionResumeStore の更新に失敗しました。Open Question YAML の保存は完了しており、保存状態は後続 flush で同期します。 error=${detail}`,
818
863
  });
819
864
  };
820
- scheduleSidecarFlush = () => {
821
- void flushSidecar().catch(reportSidecarFlushFailure);
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?.();
822
904
  };
823
905
  const toolCallHistoryPath = toolCallSnapshotHistoryPath({
824
906
  repoRoot: options.repoRoot,
@@ -891,7 +973,7 @@ export async function runConductorSession(options) {
891
973
  shutdownSignal,
892
974
  onCursorChange: (cursor) => {
893
975
  githubMonitorCursor = cursor;
894
- scheduleSidecarFlush();
976
+ scheduleSessionResumeStoreFlush();
895
977
  },
896
978
  onUpdate: (payload) => {
897
979
  sessionLogger.emit({
@@ -950,6 +1032,21 @@ export async function runConductorSession(options) {
950
1032
  });
951
1033
  githubMonitor.start();
952
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();
953
1050
  let permissionDeadlockMonitor;
954
1051
  if (!options.disablePermissionDeadlockMonitor) {
955
1052
  permissionDeadlockMonitor = createPermissionDeadlockMonitor({
@@ -986,6 +1083,57 @@ export async function runConductorSession(options) {
986
1083
  let operatorRequestedExit = false;
987
1084
  let operatorRequestedClose = false;
988
1085
  let disposeOperatorInput;
1086
+ const onOperatorMessageQueued = (delivery) => {
1087
+ scheduleSessionResumeStoreFlush();
1088
+ if (delivery !== 'interrupt')
1089
+ return;
1090
+ const cancel = conductorHandle.conductor.cancel;
1091
+ if (!cancel) {
1092
+ sessionLogger.emit({
1093
+ type: 'harness.warning',
1094
+ message: 'Operator interrupt cancellation is unavailable; the message will use normal next dispatch.',
1095
+ });
1096
+ return;
1097
+ }
1098
+ void cancel.call(conductorHandle.conductor).catch((error) => {
1099
+ const detail = error instanceof Error ? error.message : String(error);
1100
+ sessionLogger.emit({
1101
+ type: 'harness.warning',
1102
+ message: `Operator interrupt cancellation failed; the message will use normal next dispatch: ${detail}`,
1103
+ });
1104
+ });
1105
+ };
1106
+ const submitOperatorMessage = (input) => {
1107
+ const delivery = input.delivery ?? 'next';
1108
+ const received = submitOperatorInput({
1109
+ ...input,
1110
+ delivery,
1111
+ conductorTurn: sendCount + 1,
1112
+ openQuestions,
1113
+ escalations,
1114
+ eventQueue,
1115
+ sessionLogger,
1116
+ onEscalated: input.onEscalated ?? ((record) => options.onEscalated?.(record)),
1117
+ });
1118
+ if (!received)
1119
+ return false;
1120
+ onOperatorMessageQueued(delivery);
1121
+ return true;
1122
+ };
1123
+ const sendOperatorMessage = (input) => {
1124
+ const delivery = input.delivery ?? 'next';
1125
+ const received = enqueueOperatorMessageEvent({
1126
+ ...input,
1127
+ delivery,
1128
+ conductorTurn: sendCount + 1,
1129
+ eventQueue,
1130
+ sessionLogger,
1131
+ });
1132
+ if (!received)
1133
+ return false;
1134
+ onOperatorMessageQueued(delivery);
1135
+ return true;
1136
+ };
989
1137
  if (options.bindOperatorInput) {
990
1138
  const bindingDispose = options.bindOperatorInput({
991
1139
  submit: (message, submitOptions) => {
@@ -1009,20 +1157,16 @@ export async function runConductorSession(options) {
1009
1157
  eventQueue.enqueue({ type: 'operator.reconnect' });
1010
1158
  return true;
1011
1159
  }
1012
- const received = submitOperatorInput({
1013
- message,
1160
+ const parsed = submitOptions?.targetOpenQuestionId !== undefined
1161
+ ? { message, delivery: 'next' }
1162
+ : submitOptions?.delivery === undefined
1163
+ ? parseOperatorMessageDelivery(message)
1164
+ : { message, delivery: submitOptions.delivery };
1165
+ return submitOperatorMessage({
1166
+ message: parsed.message,
1167
+ delivery: parsed.delivery,
1014
1168
  targetOpenQuestionId: submitOptions?.targetOpenQuestionId,
1015
- conductorTurn: sendCount + 1,
1016
- openQuestions,
1017
- escalations,
1018
- eventQueue,
1019
- sessionLogger,
1020
- onEscalated: (record) => options.onEscalated?.(record),
1021
1169
  });
1022
- if (received) {
1023
- scheduleSidecarFlush();
1024
- }
1025
- return received;
1026
1170
  },
1027
1171
  getContext: () => ({
1028
1172
  conductorTurn: sendCount + 1,
@@ -1038,6 +1182,25 @@ export async function runConductorSession(options) {
1038
1182
  try {
1039
1183
  openQuestionControlServer = await startOpenQuestionControlServer({
1040
1184
  listOpenQuestions: () => openQuestions.listOpen(),
1185
+ getLastConductorMessage: () => lastConductorMessage,
1186
+ listConductorMessages: () => conductorMessages,
1187
+ observeSession: async () => {
1188
+ await sessionStateWriteQueue;
1189
+ const sessionState = await loadSessionStateStore(sessionStateStorePath({
1190
+ repoRoot: options.repoRoot,
1191
+ conductorAgentId: conductorAgent.agentId,
1192
+ }));
1193
+ return {
1194
+ ...openQuestions.snapshot(),
1195
+ ...(sessionState ? { sessionState } : {}),
1196
+ };
1197
+ },
1198
+ sendOperatorMessage: ({ text, delivery }) => {
1199
+ if (teardownStarted) {
1200
+ throw new OpenQuestionControlOperationError(-32000, 'The session is shutting down.');
1201
+ }
1202
+ return sendOperatorMessage({ message: text, delivery });
1203
+ },
1041
1204
  answerOpenQuestion: async ({ questionId, answer }) => {
1042
1205
  if (teardownStarted) {
1043
1206
  throw new OpenQuestionControlOperationError(-32000, 'The session is shutting down.');
@@ -1047,11 +1210,11 @@ export async function runConductorSession(options) {
1047
1210
  throw new OpenQuestionControlOperationError(-32004, 'Open Question is not available.');
1048
1211
  }
1049
1212
  let accepted = false;
1050
- let sidecarWriteError;
1213
+ let resumeStoreWriteError;
1051
1214
  let dispatchReceipt;
1052
1215
  let answered;
1053
1216
  const escalatedRecords = [];
1054
- await enqueueSidecarWrite(async () => {
1217
+ await enqueueSessionResumeStoreWrite(async () => {
1055
1218
  const stagedOpenQuestions = new OpenQuestionRegistry();
1056
1219
  stagedOpenQuestions.restore(openQuestions.snapshot());
1057
1220
  const stagedAccepted = submitOperatorInput({
@@ -1078,15 +1241,11 @@ export async function runConductorSession(options) {
1078
1241
  await persistOpenQuestionsSnapshot(openQuestions.snapshot());
1079
1242
  return;
1080
1243
  }
1081
- accepted = submitOperatorInput({
1244
+ accepted = submitOperatorMessage({
1082
1245
  message: answer,
1246
+ delivery: 'next',
1083
1247
  targetOpenQuestionId: questionId,
1084
1248
  answeredAt: stagedAnswer.answeredAt,
1085
- conductorTurn: sendCount + 1,
1086
- openQuestions,
1087
- escalations,
1088
- eventQueue,
1089
- sessionLogger,
1090
1249
  onEscalated: (record) => escalatedRecords.push(record),
1091
1250
  onOperatorMessageQueued: (event) => {
1092
1251
  let receipt;
@@ -1108,12 +1267,12 @@ export async function runConductorSession(options) {
1108
1267
  return;
1109
1268
  }
1110
1269
  try {
1111
- await persistSessionSidecar(openQuestions.snapshot());
1270
+ await persistSessionResumeStore(openQuestions.snapshot());
1112
1271
  }
1113
1272
  catch (error) {
1114
1273
  // YAML is authoritative and is already durable. Keep the accepted
1115
1274
  // answer/notification, report the mirror failure, then repair it.
1116
- sidecarWriteError = error;
1275
+ resumeStoreWriteError = error;
1117
1276
  }
1118
1277
  });
1119
1278
  if (!accepted || !answered || answered.status !== 'answered') {
@@ -1131,9 +1290,9 @@ export async function runConductorSession(options) {
1131
1290
  });
1132
1291
  }
1133
1292
  }
1134
- if (sidecarWriteError !== undefined) {
1135
- reportSessionSidecarWriteFailure(sidecarWriteError);
1136
- scheduleSidecarFlush();
1293
+ if (resumeStoreWriteError !== undefined) {
1294
+ reportSessionResumeStoreWriteFailure(resumeStoreWriteError);
1295
+ scheduleSessionResumeStoreFlush();
1137
1296
  }
1138
1297
  if (!dispatchReceipt) {
1139
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);
@@ -1143,7 +1302,7 @@ export async function runConductorSession(options) {
1143
1302
  },
1144
1303
  });
1145
1304
  harnessSocketPath = openQuestionControlServer.socketPath;
1146
- await flushSidecar().catch(reportSidecarFlushFailure);
1305
+ await flushSessionResumeStore().catch(reportResumeStoreFlushFailure);
1147
1306
  const driverResult = await runConductorSessionDriver({
1148
1307
  issueUrl: options.issueUrl,
1149
1308
  initialPrompt: conductorBackend === 'pi'
@@ -1189,6 +1348,7 @@ export async function runConductorSession(options) {
1189
1348
  // session briefing.
1190
1349
  skipInitialSend: Boolean(options.resumeAgentId),
1191
1350
  onSendStarted: (info) => {
1351
+ reportHarnessActivity('conductor', true);
1192
1352
  sessionLogger.emit({
1193
1353
  type: 'conductor.send.started',
1194
1354
  sendCount: info.sendCount,
@@ -1237,6 +1397,29 @@ export async function runConductorSession(options) {
1237
1397
  function recordSendComplete(info) {
1238
1398
  sendCount = info.sendCount;
1239
1399
  autonomousTurns = info.autonomousTurns;
1400
+ const responseText = info.result?.trim();
1401
+ if (info.status === 'finished' && responseText) {
1402
+ const at = Date.now();
1403
+ lastConductorMessage = {
1404
+ text: responseText,
1405
+ at,
1406
+ sendCount: info.sendCount,
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
+ }
1422
+ }
1240
1423
  const githubUpdateIds = info.githubUpdateIds ?? [];
1241
1424
  if (info.status === 'finished' &&
1242
1425
  !operatorRequestedClose &&
@@ -1266,6 +1449,7 @@ export async function runConductorSession(options) {
1266
1449
  workerDispatches: info.workerDispatches,
1267
1450
  workerFailures: info.workerFailures,
1268
1451
  });
1452
+ reportHarnessActivity('conductor', false);
1269
1453
  outboundWorkersThisSend.clear();
1270
1454
  if (isConductorSendAuthError({
1271
1455
  runId: info.runId,
@@ -1285,7 +1469,7 @@ export async function runConductorSession(options) {
1285
1469
  : { backend: 'cursor' }),
1286
1470
  });
1287
1471
  }
1288
- scheduleSidecarFlush();
1472
+ scheduleSessionResumeStoreFlush();
1289
1473
  }
1290
1474
  async function buildResult() {
1291
1475
  sessionLogger.finish(stopReason);
@@ -1318,8 +1502,11 @@ export async function runConductorSession(options) {
1318
1502
  : shutdownSignal.aborted
1319
1503
  ? 'interrupted'
1320
1504
  : 'unknown';
1321
- terminalLifecycle = lifecycle;
1322
1505
  teardownStarted = true;
1506
+ if (idlePhaseTimer) {
1507
+ clearTimeout(idlePhaseTimer);
1508
+ idlePhaseTimer = undefined;
1509
+ }
1323
1510
  for (const receipt of pendingOpenQuestionDispatchReceipts) {
1324
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));
1325
1512
  }
@@ -1368,6 +1555,10 @@ export async function runConductorSession(options) {
1368
1555
  teardownPhases.openQuestionControl = Date.now() - phaseStart;
1369
1556
  }
1370
1557
  disposeOperatorInput?.();
1558
+ if (dispatchHoldReminderMonitor) {
1559
+ emitTeardownPhase('dispatchHoldReminderMonitor');
1560
+ dispatchHoldReminderMonitor.stop();
1561
+ }
1371
1562
  if (permissionDeadlockMonitor) {
1372
1563
  emitTeardownPhase('permissionDeadlockMonitor');
1373
1564
  permissionDeadlockMonitor.stop();
@@ -1414,13 +1605,13 @@ export async function runConductorSession(options) {
1414
1605
  githubMonitorStopped = true;
1415
1606
  }
1416
1607
  try {
1417
- emitTeardownPhase('flushSidecar');
1608
+ emitTeardownPhase('flushSessionResumeStore');
1418
1609
  const phaseStart = Date.now();
1419
- await flushSidecar();
1420
- teardownPhases.flushSidecar = Date.now() - phaseStart;
1610
+ await flushSessionResumeStore();
1611
+ teardownPhases.flushSessionResumeStore = Date.now() - phaseStart;
1421
1612
  }
1422
1613
  catch (error) {
1423
- reportSidecarFlushFailure(error);
1614
+ reportResumeStoreFlushFailure(error);
1424
1615
  }
1425
1616
  await flushToolCallHistoryBestEffort();
1426
1617
  const runGithubMonitorStop = async () => {
@@ -1485,12 +1676,12 @@ export async function runConductorSession(options) {
1485
1676
  }
1486
1677
  finally {
1487
1678
  try {
1488
- emitTeardownPhase('flushSidecar');
1679
+ emitTeardownPhase('flushSessionResumeStore');
1489
1680
  await sessionStateWriteQueue;
1490
- await flushSidecar(lifecycle);
1681
+ await flushSessionResumeStore(lifecycle);
1491
1682
  }
1492
1683
  catch (error) {
1493
- reportSidecarFlushFailure(error);
1684
+ reportResumeStoreFlushFailure(error);
1494
1685
  }
1495
1686
  process.off('SIGINT', onTeardownSignal);
1496
1687
  process.off('SIGTERM', onTeardownSignal);