@agents-ensemble/core 0.11.0 → 0.13.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 (189) hide show
  1. package/dist/acp/acp-client.d.ts +9 -1
  2. package/dist/acp/acp-client.d.ts.map +1 -1
  3. package/dist/acp/acp-client.js +14 -11
  4. package/dist/acp/acp-client.js.map +1 -1
  5. package/dist/acp/acp-process.d.ts.map +1 -1
  6. package/dist/acp/acp-process.js +3 -1
  7. package/dist/acp/acp-process.js.map +1 -1
  8. package/dist/acp/index.d.ts +1 -1
  9. package/dist/acp/index.d.ts.map +1 -1
  10. package/dist/acp/index.js.map +1 -1
  11. package/dist/acp/testing/fake-acp-server.d.ts +4 -0
  12. package/dist/acp/testing/fake-acp-server.d.ts.map +1 -1
  13. package/dist/acp/testing/fake-acp-server.js +12 -7
  14. package/dist/acp/testing/fake-acp-server.js.map +1 -1
  15. package/dist/conductor/conductor-agent-factory.d.ts +5 -0
  16. package/dist/conductor/conductor-agent-factory.d.ts.map +1 -0
  17. package/dist/conductor/conductor-agent-factory.js +10 -0
  18. package/dist/conductor/conductor-agent-factory.js.map +1 -0
  19. package/dist/conductor/conductor-agent.d.ts +9 -0
  20. package/dist/conductor/conductor-agent.d.ts.map +1 -1
  21. package/dist/conductor/conductor-session-driver.d.ts +2 -0
  22. package/dist/conductor/conductor-session-driver.d.ts.map +1 -1
  23. package/dist/conductor/conductor-session-driver.js +7 -1
  24. package/dist/conductor/conductor-session-driver.js.map +1 -1
  25. package/dist/conductor/conductor-session.d.ts +2 -3
  26. package/dist/conductor/conductor-session.d.ts.map +1 -1
  27. package/dist/conductor/conductor-session.js +289 -26
  28. package/dist/conductor/conductor-session.js.map +1 -1
  29. package/dist/conductor/index.d.ts +6 -2
  30. package/dist/conductor/index.d.ts.map +1 -1
  31. package/dist/conductor/index.js +4 -1
  32. package/dist/conductor/index.js.map +1 -1
  33. package/dist/conductor/operator-close.d.ts +3 -0
  34. package/dist/conductor/operator-close.d.ts.map +1 -0
  35. package/dist/conductor/operator-close.js +6 -0
  36. package/dist/conductor/operator-close.js.map +1 -0
  37. package/dist/conductor/operator-command.d.ts +20 -0
  38. package/dist/conductor/operator-command.d.ts.map +1 -0
  39. package/dist/conductor/operator-command.js +29 -0
  40. package/dist/conductor/operator-command.js.map +1 -0
  41. package/dist/conductor/operator-exit.d.ts.map +1 -1
  42. package/dist/conductor/operator-exit.js +2 -2
  43. package/dist/conductor/operator-exit.js.map +1 -1
  44. package/dist/conductor/operator-post-loop-gate.d.ts +2 -2
  45. package/dist/conductor/operator-post-loop-gate.d.ts.map +1 -1
  46. package/dist/conductor/operator-reconnect.d.ts.map +1 -1
  47. package/dist/conductor/operator-reconnect.js +2 -2
  48. package/dist/conductor/operator-reconnect.js.map +1 -1
  49. package/dist/conductor/pi-conductor-agent.d.ts +7 -5
  50. package/dist/conductor/pi-conductor-agent.d.ts.map +1 -1
  51. package/dist/conductor/pi-conductor-agent.js +104 -23
  52. package/dist/conductor/pi-conductor-agent.js.map +1 -1
  53. package/dist/conductor/pi-resource-loader.d.ts +2 -0
  54. package/dist/conductor/pi-resource-loader.d.ts.map +1 -1
  55. package/dist/conductor/pi-resource-loader.js +16 -8
  56. package/dist/conductor/pi-resource-loader.js.map +1 -1
  57. package/dist/conductor/session/dispatch-hold.d.ts +1 -1
  58. package/dist/conductor/session/dispatch-hold.d.ts.map +1 -1
  59. package/dist/conductor/session/dispatch-hold.js +4 -3
  60. package/dist/conductor/session/dispatch-hold.js.map +1 -1
  61. package/dist/conductor/session/dispatch-mode.d.ts +1 -1
  62. package/dist/conductor/session/events/index.d.ts +3 -2
  63. package/dist/conductor/session/events/index.d.ts.map +1 -1
  64. package/dist/conductor/session/events/index.js.map +1 -1
  65. package/dist/conductor/session/events/session-event.d.ts +7 -1
  66. package/dist/conductor/session/events/session-event.d.ts.map +1 -1
  67. package/dist/conductor/session/events/session-log-event-groups.d.ts +4 -4
  68. package/dist/conductor/session/events/session-log-event-groups.d.ts.map +1 -1
  69. package/dist/conductor/session/events/session-log-event-groups.js +3 -0
  70. package/dist/conductor/session/events/session-log-event-groups.js.map +1 -1
  71. package/dist/conductor/session/events/session-log-event.d.ts +9 -2
  72. package/dist/conductor/session/events/session-log-event.d.ts.map +1 -1
  73. package/dist/conductor/session/events/shared/permission-pending.d.ts +7 -0
  74. package/dist/conductor/session/events/shared/permission-pending.d.ts.map +1 -1
  75. package/dist/conductor/session/format-session-event.d.ts.map +1 -1
  76. package/dist/conductor/session/format-session-event.js +37 -2
  77. package/dist/conductor/session/format-session-event.js.map +1 -1
  78. package/dist/conductor/session/select-dispatch-batch.d.ts.map +1 -1
  79. package/dist/conductor/session/select-dispatch-batch.js +21 -4
  80. package/dist/conductor/session/select-dispatch-batch.js.map +1 -1
  81. package/dist/conductor/session/session-event.d.ts +1 -1
  82. package/dist/conductor/session/session-event.d.ts.map +1 -1
  83. package/dist/conductor/session/session-logger.d.ts.map +1 -1
  84. package/dist/conductor/session/session-logger.js +2 -0
  85. package/dist/conductor/session/session-logger.js.map +1 -1
  86. package/dist/dispatch/get-tool-call-tool.d.ts +8 -0
  87. package/dist/dispatch/get-tool-call-tool.d.ts.map +1 -0
  88. package/dist/dispatch/get-tool-call-tool.js +46 -0
  89. package/dist/dispatch/get-tool-call-tool.js.map +1 -0
  90. package/dist/dispatch/index.d.ts +3 -0
  91. package/dist/dispatch/index.d.ts.map +1 -1
  92. package/dist/dispatch/index.js +2 -0
  93. package/dist/dispatch/index.js.map +1 -1
  94. package/dist/dispatch/report-session-state-tool.d.ts +4 -0
  95. package/dist/dispatch/report-session-state-tool.d.ts.map +1 -0
  96. package/dist/dispatch/report-session-state-tool.js +38 -0
  97. package/dist/dispatch/report-session-state-tool.js.map +1 -0
  98. package/dist/dispatch/session-usage-tool.js +1 -1
  99. package/dist/dispatch/session-usage-tool.js.map +1 -1
  100. package/dist/escalation/answer-open-question-tool.d.ts.map +1 -1
  101. package/dist/escalation/answer-open-question-tool.js +1 -6
  102. package/dist/escalation/answer-open-question-tool.js.map +1 -1
  103. package/dist/escalation/ask-human-tool.d.ts.map +1 -1
  104. package/dist/escalation/ask-human-tool.js +7 -5
  105. package/dist/escalation/ask-human-tool.js.map +1 -1
  106. package/dist/escalation/open-question.d.ts.map +1 -1
  107. package/dist/escalation/open-question.js +5 -8
  108. package/dist/escalation/open-question.js.map +1 -1
  109. package/dist/escalation/record-open-question-answer.js +4 -2
  110. package/dist/escalation/record-open-question-answer.js.map +1 -1
  111. package/dist/escalation/yes-no-assent.d.ts.map +1 -1
  112. package/dist/escalation/yes-no-assent.js +5 -3
  113. package/dist/escalation/yes-no-assent.js.map +1 -1
  114. package/dist/github/github-monitor-cursor.d.ts +3 -1
  115. package/dist/github/github-monitor-cursor.d.ts.map +1 -1
  116. package/dist/github/github-monitor-cursor.js +11 -1
  117. package/dist/github/github-monitor-cursor.js.map +1 -1
  118. package/dist/github/github-monitor-error.d.ts +1 -1
  119. package/dist/github/github-monitor-error.d.ts.map +1 -1
  120. package/dist/github/github-monitor-error.js +35 -0
  121. package/dist/github/github-monitor-error.js.map +1 -1
  122. package/dist/github/github-monitor.d.ts +4 -0
  123. package/dist/github/github-monitor.d.ts.map +1 -1
  124. package/dist/github/github-monitor.js +60 -2
  125. package/dist/github/github-monitor.js.map +1 -1
  126. package/dist/operator-runtime.d.ts +20 -0
  127. package/dist/operator-runtime.d.ts.map +1 -0
  128. package/dist/operator-runtime.js +14 -0
  129. package/dist/operator-runtime.js.map +1 -0
  130. package/dist/permission/format-permission-summary-for-operator.d.ts.map +1 -1
  131. package/dist/permission/format-permission-summary-for-operator.js +44 -12
  132. package/dist/permission/format-permission-summary-for-operator.js.map +1 -1
  133. package/dist/permission/index.d.ts +2 -0
  134. package/dist/permission/index.d.ts.map +1 -1
  135. package/dist/permission/index.js +1 -0
  136. package/dist/permission/index.js.map +1 -1
  137. package/dist/permission/permission-brief.d.ts +22 -0
  138. package/dist/permission/permission-brief.d.ts.map +1 -0
  139. package/dist/permission/permission-brief.js +245 -0
  140. package/dist/permission/permission-brief.js.map +1 -0
  141. package/dist/permission/permission-request.js +7 -2
  142. package/dist/permission/permission-request.js.map +1 -1
  143. package/dist/prompt/index.d.ts +1 -0
  144. package/dist/prompt/index.d.ts.map +1 -1
  145. package/dist/prompt/index.js +1 -0
  146. package/dist/prompt/index.js.map +1 -1
  147. package/dist/prompt/modules/ensemble/conductor-base-module.d.ts.map +1 -1
  148. package/dist/prompt/modules/ensemble/conductor-base-module.js +12 -0
  149. package/dist/prompt/modules/ensemble/conductor-base-module.js.map +1 -1
  150. package/dist/runtime/index.d.ts +3 -0
  151. package/dist/runtime/index.d.ts.map +1 -1
  152. package/dist/runtime/index.js +2 -0
  153. package/dist/runtime/index.js.map +1 -1
  154. package/dist/runtime/tool-call-snapshot-registry.d.ts +69 -0
  155. package/dist/runtime/tool-call-snapshot-registry.d.ts.map +1 -0
  156. package/dist/runtime/tool-call-snapshot-registry.js +275 -0
  157. package/dist/runtime/tool-call-snapshot-registry.js.map +1 -0
  158. package/dist/runtime/types.d.ts +2 -0
  159. package/dist/runtime/types.d.ts.map +1 -1
  160. package/dist/runtime/worker-runtime.d.ts.map +1 -1
  161. package/dist/runtime/worker-runtime.js +6 -2
  162. package/dist/runtime/worker-runtime.js.map +1 -1
  163. package/dist/session/index.d.ts +2 -0
  164. package/dist/session/index.d.ts.map +1 -1
  165. package/dist/session/index.js +1 -0
  166. package/dist/session/index.js.map +1 -1
  167. package/dist/session/open-questions-store.d.ts +12 -0
  168. package/dist/session/open-questions-store.d.ts.map +1 -0
  169. package/dist/session/open-questions-store.js +161 -0
  170. package/dist/session/open-questions-store.js.map +1 -0
  171. package/dist/session/session-sidecar.d.ts +1 -1
  172. package/dist/session/session-sidecar.d.ts.map +1 -1
  173. package/dist/session/tool-call-snapshot-history.d.ts +32 -0
  174. package/dist/session/tool-call-snapshot-history.d.ts.map +1 -0
  175. package/dist/session/tool-call-snapshot-history.js +203 -0
  176. package/dist/session/tool-call-snapshot-history.js.map +1 -0
  177. package/dist/usage/enrich-session-usage-cost.d.ts +4 -1
  178. package/dist/usage/enrich-session-usage-cost.d.ts.map +1 -1
  179. package/dist/usage/enrich-session-usage-cost.js +8 -2
  180. package/dist/usage/enrich-session-usage-cost.js.map +1 -1
  181. package/dist/usage/index.d.ts +1 -1
  182. package/dist/usage/index.d.ts.map +1 -1
  183. package/dist/usage/session-usage-tracker.d.ts +7 -1
  184. package/dist/usage/session-usage-tracker.d.ts.map +1 -1
  185. package/dist/usage/session-usage-tracker.js +51 -0
  186. package/dist/usage/session-usage-tracker.js.map +1 -1
  187. package/dist/usage/types.d.ts +30 -2
  188. package/dist/usage/types.d.ts.map +1 -1
  189. package/package.json +5 -1
@@ -9,20 +9,23 @@ import { deny } from '../permission/permission-broker.js';
9
9
  import { PermissionPipeline } from '../permission/permission-pipeline.js';
10
10
  import { createPermissionDeadlockMonitor, } from '../permission/permission-deadlock-monitor.js';
11
11
  import { createResolvePermissionTool } from '../permission/resolve-permission-tool.js';
12
+ import { buildPermissionBrief, getPermissionToolCallId, } from '../permission/permission-brief.js';
12
13
  import { acpSpawnFingerprint, resolveWorkerAcpSpawn, resolvedAcpSpawnToOptions, } from '../acp/resolve-acp-spawn.js';
13
14
  import { finalizeResolvedAcpSpawn, validateWorkerAcpPrerequisites, } from '../acp/validate-acp-preset-prerequisites.js';
14
15
  import { profileWorkersToSessionSpecs, sessionStateFromProfile, resolveAgentPromptModule, } from '../profile/types.js';
15
16
  import { WorkerSession } from '../runtime/worker-session.js';
17
+ import { ToolCallSnapshotRegistry, } from '../runtime/tool-call-snapshot-registry.js';
16
18
  import { createPromptWorkerTool } from '../dispatch/prompt-worker-tool.js';
17
19
  import { createWorkerStatusTools } from '../dispatch/worker-status-tool.js';
20
+ import { createGetToolCallTool } from '../dispatch/get-tool-call-tool.js';
18
21
  import { createSessionUsageTools } from '../dispatch/session-usage-tool.js';
22
+ import { createReportSessionStateTool } from '../dispatch/report-session-state-tool.js';
19
23
  import { createSetDispatchHoldTool } from '../dispatch/set-dispatch-hold-tool.js';
20
24
  import { parseIssueUrl } from '../issue/issue-ref.js';
21
25
  import { resolveWorkerWorkspace, removeWorkerWorktree, } from '../worktree/worktree.js';
22
26
  import { WorkerOutboundQueue } from '../runtime/worker-outbound-queue.js';
23
27
  import { ConductorToolRegistry } from './conductor-tool.js';
24
- import { createCursorSdkConductorAgentFactory } from './cursor-sdk-conductor-agent.js';
25
- import { createPiConductorAgentFactory } from './pi-conductor-agent.js';
28
+ import { createConductorAgentFactory } from './conductor-agent-factory.js';
26
29
  import { formatConductorAuthRecoveryHint, isConductorSendAuthError, } from './conductor-auth.js';
27
30
  import { resolvePiConductorProvider } from './conductor-pi-auth.js';
28
31
  import { SessionLogger } from './session/session-logger.js';
@@ -31,8 +34,12 @@ import { bufferDispatchHoldEvents, createDispatchHoldState, pruneStalePermission
31
34
  import { operatorInputMaxTurns, resolveMaxTurns, } from './session-policy.js';
32
35
  import { runConductorSessionDriver } from './conductor-session-driver.js';
33
36
  import { isOperatorExitCommand } from './operator-exit.js';
37
+ import { isOperatorCloseCommand } from './operator-close.js';
34
38
  import { isOperatorReconnectCommand } from './operator-reconnect.js';
35
39
  import { assertSessionSidecarMatches, requireSessionSidecarForResume, saveSessionSidecar, SESSION_SIDECAR_VERSION, sessionSidecarPath, } from '../session/session-sidecar.js';
40
+ import { loadToolCallSnapshotHistory, pruneExpiredToolCallSnapshotHistoryFiles, saveToolCallSnapshotHistory, toolCallSnapshotHistoryPath, } from '../session/tool-call-snapshot-history.js';
41
+ import { loadOpenQuestionsStore, openQuestionsStorePath, saveOpenQuestionsStore, } from '../session/open-questions-store.js';
42
+ import { formatPermissionSummaryForOperator } from '../permission/format-permission-summary-for-operator.js';
36
43
  import { enrichSessionUsageWithCost } from '../usage/enrich-session-usage-cost.js';
37
44
  import { SessionUsageTracker } from '../usage/session-usage-tracker.js';
38
45
  import { submitOperatorInput } from './submit-operator-input.js';
@@ -63,6 +70,13 @@ export async function runConductorSession(options) {
63
70
  repoRoot: options.repoRoot,
64
71
  });
65
72
  attachLegacySessionCallbacks(sessionLogger, options);
73
+ const expiredHistory = await pruneExpiredToolCallSnapshotHistoryFiles(options.repoRoot);
74
+ if (expiredHistory.failedCount > 0) {
75
+ sessionLogger.emit({
76
+ type: 'harness.warning',
77
+ message: 'Some expired tool-call snapshot history files could not be removed.',
78
+ });
79
+ }
66
80
  const escalations = [];
67
81
  const openQuestions = new OpenQuestionRegistry();
68
82
  const eventQueue = new SessionEventQueue();
@@ -83,10 +97,13 @@ export async function runConductorSession(options) {
83
97
  const workerSessions = new Map();
84
98
  let githubMonitorCursor;
85
99
  let githubMonitor;
100
+ const pendingGitHubRecoveries = [];
86
101
  const conductorCwd = options.conductorCwd ?? process.cwd();
87
102
  const mcpServers = await resolveMcpServers(options.repoRoot, options.mcpConfigOptions);
88
103
  const mcpConfigDigest = computeMcpConfigDigest(mcpServers);
89
104
  const mcpConfigConfigured = Object.keys(mcpServers).length > 0;
105
+ let resumeToolCallSnapshots = [];
106
+ let toolCallHistoryNeedsRewrite = false;
90
107
  if (options.resumeAgentId) {
91
108
  const sidecar = await requireSessionSidecarForResume({
92
109
  repoRoot: options.repoRoot,
@@ -100,10 +117,43 @@ export async function runConductorSession(options) {
100
117
  mcpConfigDigest,
101
118
  mcpConfigConfigured,
102
119
  });
103
- openQuestions.restore({
104
- sequence: sidecar.sequence,
105
- openQuestions: sidecar.openQuestions,
120
+ const history = await loadToolCallSnapshotHistory({
121
+ repoRoot: options.repoRoot,
122
+ conductorAgentId: options.resumeAgentId,
106
123
  });
124
+ resumeToolCallSnapshots = history.entries;
125
+ toolCallHistoryNeedsRewrite = history.needsRewrite;
126
+ if (history.warning) {
127
+ sessionLogger.emit({
128
+ type: 'harness.warning',
129
+ message: `${history.warning}; the session will continue without affected history`,
130
+ });
131
+ }
132
+ const openQuestionsPath = openQuestionsStorePath({
133
+ repoRoot: options.repoRoot,
134
+ conductorAgentId: options.resumeAgentId,
135
+ });
136
+ const openQuestionsSnapshot = await loadOpenQuestionsStore(openQuestionsPath);
137
+ if (openQuestionsSnapshot) {
138
+ openQuestions.restore(openQuestionsSnapshot);
139
+ }
140
+ else {
141
+ const legacySnapshot = {
142
+ sequence: sidecar.sequence,
143
+ openQuestions: sidecar.openQuestions,
144
+ };
145
+ openQuestions.restore(legacySnapshot);
146
+ try {
147
+ await saveOpenQuestionsStore(openQuestionsPath, legacySnapshot);
148
+ }
149
+ catch (error) {
150
+ const detail = error instanceof Error ? error.message : String(error);
151
+ sessionLogger.emit({
152
+ type: 'harness.warning',
153
+ message: `旧 sidecar から復元した open question を YAML にバックフィルできませんでした。今回のセッションは継続します。 error=${detail}`,
154
+ });
155
+ }
156
+ }
107
157
  activeProfile = sidecar.profile;
108
158
  // Sidecars written before this setting always started every worker eagerly.
109
159
  activeLazyWorkers = sidecar.lazyWorkers ?? false;
@@ -145,10 +195,10 @@ export async function runConductorSession(options) {
145
195
  ? new AbortController()
146
196
  : undefined;
147
197
  const shutdownSignal = options.shutdownSignal ?? shutdownController.signal;
148
- const operatorExitController = new AbortController();
198
+ const operatorTerminationController = new AbortController();
149
199
  const driverShutdownSignal = AbortSignal.any([
150
200
  shutdownSignal,
151
- operatorExitController.signal,
201
+ operatorTerminationController.signal,
152
202
  ]);
153
203
  let unregisterProcessSignalHandlers = () => { };
154
204
  if (ownsShutdownController &&
@@ -164,6 +214,9 @@ export async function runConductorSession(options) {
164
214
  const permissionPipeline = options.permissionPipeline ??
165
215
  new PermissionPipeline({ policy: options.permissionPolicy });
166
216
  let scheduleSidecarFlush = () => { };
217
+ let scheduleToolCallHistoryFlush = () => { };
218
+ let flushToolCallHistory = async () => { };
219
+ let toolCallHistoryDirty = toolCallHistoryNeedsRewrite;
167
220
  const sessionState = sessionStateFromProfile(activeProfile);
168
221
  const issue = parseIssueUrl(options.issueUrl);
169
222
  const spawnBase = {
@@ -214,7 +267,11 @@ export async function runConductorSession(options) {
214
267
  }
215
268
  const sessionUsageTracker = new SessionUsageTracker({
216
269
  contextLimitTokens: options.contextLimitTokens,
270
+ trackCompactions: conductorBackend === 'pi' && options.conductorAgentFactory == null,
217
271
  });
272
+ const toolCallSnapshots = new ToolCallSnapshotRegistry();
273
+ let toolCallHistoryLimitWarningEmitted = false;
274
+ let toolCallHistoryOversizeWarningEmitted = false;
218
275
  const workerSession = new WorkerSession({
219
276
  issueUrl: options.issueUrl,
220
277
  repoRoot: options.repoRoot,
@@ -244,12 +301,28 @@ export async function runConductorSession(options) {
244
301
  const pending = permissionPipeline.pending.get(requestId);
245
302
  if (pending) {
246
303
  const workerLabel = workerSession.runtime.resolveWorkerLabel(workerId) ?? workerId;
304
+ const operatorSummary = formatPermissionSummaryForOperator(pending, {
305
+ workerLabel,
306
+ });
307
+ const toolCallId = getPermissionToolCallId(pending.request);
308
+ const toolCallSnapshot = toolCallId && pending.request.sessionId
309
+ ? toolCallSnapshots.get(toolCallId, {
310
+ workerId,
311
+ sessionId: pending.request.sessionId,
312
+ })
313
+ : undefined;
247
314
  sessionLogger.emit({
248
315
  type: 'permission.pending',
249
316
  permission: pending,
250
317
  workerLabel,
251
318
  });
252
- eventQueue.enqueue({ type: 'permission.pending', permission: pending });
319
+ eventQueue.enqueue({
320
+ type: 'permission.pending',
321
+ permission: pending,
322
+ worker: workerLabel,
323
+ brief: buildPermissionBrief(pending.request, toolCallSnapshot),
324
+ operatorSummary,
325
+ });
253
326
  }
254
327
  return null;
255
328
  },
@@ -322,6 +395,36 @@ export async function runConductorSession(options) {
322
395
  }
323
396
  },
324
397
  onAcpUpdate: (event) => {
398
+ if (event.toolCallUpdate) {
399
+ const result = toolCallSnapshots.applyWithResult({
400
+ workerId: event.workerId,
401
+ workerName: event.name,
402
+ sessionId: event.sessionId ?? '',
403
+ }, event.toolCallUpdate);
404
+ if (result.status === 'applied') {
405
+ toolCallHistoryDirty = true;
406
+ scheduleToolCallHistoryFlush();
407
+ if (result.evictedCount > 0 &&
408
+ !toolCallHistoryLimitWarningEmitted) {
409
+ toolCallHistoryLimitWarningEmitted = true;
410
+ sessionLogger.emit({
411
+ type: 'harness.warning',
412
+ message: 'Tool-call snapshot history reached its retention limit; oldest snapshots were removed.',
413
+ });
414
+ }
415
+ }
416
+ else if (result.reason === 'too-large') {
417
+ toolCallHistoryDirty = true;
418
+ scheduleToolCallHistoryFlush();
419
+ if (!toolCallHistoryOversizeWarningEmitted) {
420
+ toolCallHistoryOversizeWarningEmitted = true;
421
+ sessionLogger.emit({
422
+ type: 'harness.warning',
423
+ message: 'A tool-call snapshot exceeds the history size limit and was not retained.',
424
+ });
425
+ }
426
+ }
427
+ }
325
428
  sessionLogger.emit({
326
429
  type: 'harness.worker.acp.update',
327
430
  name: event.name,
@@ -360,6 +463,22 @@ export async function runConductorSession(options) {
360
463
  });
361
464
  }
362
465
  workerSession.startWorkers();
466
+ if (resumeToolCallSnapshots.length > 0) {
467
+ const workerIdsByName = new Map();
468
+ workers.forEach((worker, index) => {
469
+ const workerId = workerSession.startedWorkerIds[index];
470
+ if (workerId)
471
+ workerIdsByName.set(worker.name, workerId);
472
+ });
473
+ const restoreResult = toolCallSnapshots.restore(resumeToolCallSnapshots, workerIdsByName);
474
+ if (restoreResult.skippedCount > 0) {
475
+ toolCallHistoryDirty = true;
476
+ sessionLogger.emit({
477
+ type: 'harness.warning',
478
+ 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.`,
479
+ });
480
+ }
481
+ }
363
482
  const recordAnsweredQuestion = (answered) => {
364
483
  const record = openQuestionToEscalationRecord(answered);
365
484
  if (record) {
@@ -388,6 +507,9 @@ export async function runConductorSession(options) {
388
507
  pipeline: permissionPipeline,
389
508
  inbox: workerSession.inbox,
390
509
  });
510
+ const getToolCallTools = createGetToolCallTool({
511
+ snapshots: toolCallSnapshots,
512
+ });
391
513
  let outboundDispatchesThisSend = 0;
392
514
  const outboundWorkersThisSend = new Set();
393
515
  const workerOutboundQueue = new WorkerOutboundQueue((worker, instruction, options) => {
@@ -438,6 +560,7 @@ export async function runConductorSession(options) {
438
560
  return usage.cost;
439
561
  },
440
562
  });
563
+ const reportSessionStateTool = createReportSessionStateTool();
441
564
  const dispatchHoldTools = createSetDispatchHoldTool({
442
565
  state: dispatchHoldState,
443
566
  onChanged: onDispatchHoldChanged,
@@ -461,11 +584,13 @@ export async function runConductorSession(options) {
461
584
  .registerAll(answerOpenQuestionTools)
462
585
  .registerAll(openQuestionListTools)
463
586
  .registerAll(resolvePermissionTools)
587
+ .registerAll(getToolCallTools)
464
588
  .registerAll(promptWorkerTools)
465
589
  .registerAll(workerStatusTools)
466
590
  .registerAll(registerGitHubWatchTools)
467
591
  .registerAll(unregisterGitHubWatchTools)
468
592
  .registerAll(sessionUsageTools)
593
+ .registerAll(reportSessionStateTool)
469
594
  .registerAll(dispatchHoldTools);
470
595
  const conductorOptions = {
471
596
  cwd: conductorCwd,
@@ -486,6 +611,11 @@ export async function runConductorSession(options) {
486
611
  : {}),
487
612
  ...(Object.keys(mcpServers).length > 0 ? { mcpServers } : {}),
488
613
  customTools: conductorToolRegistry.toRecord(),
614
+ ...(conductorBackend === 'pi' && options.conductorAgentFactory == null
615
+ ? {
616
+ onContextCompaction: () => sessionUsageTracker.recordContextCompaction(),
617
+ }
618
+ : {}),
489
619
  };
490
620
  const conductorProvider = conductorBackend === 'pi'
491
621
  ? (await resolvePiConductorProvider({
@@ -553,6 +683,10 @@ export async function runConductorSession(options) {
553
683
  ...(githubMonitorCursor ? { githubMonitor: githubMonitorCursor } : {}),
554
684
  updatedAt: Date.now(),
555
685
  };
686
+ await saveOpenQuestionsStore(openQuestionsStorePath({
687
+ repoRoot: options.repoRoot,
688
+ conductorAgentId: conductorHandle.conductor.agentId,
689
+ }), snapshot);
556
690
  await saveSessionSidecar(sessionSidecarPath({
557
691
  repoRoot: options.repoRoot,
558
692
  conductorAgentId: conductorHandle.conductor.agentId,
@@ -561,11 +695,68 @@ export async function runConductorSession(options) {
561
695
  sidecarFlushQueue = nextFlush.catch(() => { });
562
696
  return nextFlush;
563
697
  };
698
+ const reportSidecarFlushFailure = (error) => {
699
+ const detail = error instanceof Error ? error.message : String(error);
700
+ sessionLogger.emit({
701
+ type: 'harness.warning',
702
+ message: `Open Question YAML / session sidecar を保存できませんでした。最新状態から再開できず、直前に保存された状態からの再開になる場合があります。 error=${detail}`,
703
+ });
704
+ };
564
705
  scheduleSidecarFlush = () => {
565
- void flushSidecar().catch(() => {
566
- // best-effort persistence
706
+ void flushSidecar().catch(reportSidecarFlushFailure);
707
+ };
708
+ const toolCallHistoryPath = toolCallSnapshotHistoryPath({
709
+ repoRoot: options.repoRoot,
710
+ conductorAgentId: conductorHandle.conductor.agentId,
711
+ });
712
+ let toolCallHistoryFlushTimer;
713
+ let toolCallHistoryFlushQueue = Promise.resolve();
714
+ let toolCallHistoryFlushWarningEmitted = false;
715
+ const emitToolCallHistoryFlushWarning = () => {
716
+ if (toolCallHistoryFlushWarningEmitted)
717
+ return;
718
+ toolCallHistoryFlushWarningEmitted = true;
719
+ sessionLogger.emit({
720
+ type: 'harness.warning',
721
+ message: 'Tool-call snapshot history could not be saved; the latest history may be unavailable after resume.',
567
722
  });
568
723
  };
724
+ flushToolCallHistory = () => {
725
+ if (toolCallHistoryFlushTimer) {
726
+ clearTimeout(toolCallHistoryFlushTimer);
727
+ toolCallHistoryFlushTimer = undefined;
728
+ }
729
+ if (!toolCallHistoryDirty)
730
+ return toolCallHistoryFlushQueue;
731
+ toolCallHistoryDirty = false;
732
+ const nextFlush = toolCallHistoryFlushQueue.then(() => saveToolCallSnapshotHistory(toolCallHistoryPath, toolCallSnapshots.snapshot()));
733
+ toolCallHistoryFlushQueue = nextFlush.catch(() => { });
734
+ return nextFlush;
735
+ };
736
+ scheduleToolCallHistoryFlush = () => {
737
+ toolCallHistoryDirty = true;
738
+ if (toolCallHistoryFlushTimer)
739
+ return;
740
+ toolCallHistoryFlushTimer = setTimeout(() => {
741
+ toolCallHistoryFlushTimer = undefined;
742
+ void flushToolCallHistory().catch(() => {
743
+ toolCallHistoryDirty = true;
744
+ emitToolCallHistoryFlushWarning();
745
+ });
746
+ }, 100);
747
+ toolCallHistoryFlushTimer.unref?.();
748
+ };
749
+ const flushToolCallHistoryBestEffort = async () => {
750
+ try {
751
+ await flushToolCallHistory();
752
+ }
753
+ catch {
754
+ toolCallHistoryDirty = true;
755
+ emitToolCallHistoryFlushWarning();
756
+ }
757
+ };
758
+ if (toolCallHistoryDirty)
759
+ scheduleToolCallHistoryFlush();
569
760
  let sendCount = 0;
570
761
  let autonomousTurns = 0;
571
762
  const monitorDefaults = ensembleConfig.github.monitor;
@@ -592,6 +783,14 @@ export async function runConductorSession(options) {
592
783
  type: 'harness.github.update',
593
784
  itemCount: payload.items.length,
594
785
  });
786
+ for (const suppressedErrorCount of pendingGitHubRecoveries.splice(0)) {
787
+ eventQueue.enqueue({
788
+ type: 'harness.connectivity.recovered',
789
+ source: 'github_monitor',
790
+ suppressedErrorCount,
791
+ dispatchMode: 'inform',
792
+ });
793
+ }
595
794
  eventQueue.enqueue({
596
795
  type: 'github.update',
597
796
  items: payload.items,
@@ -617,6 +816,22 @@ export async function runConductorSession(options) {
617
816
  message,
618
817
  });
619
818
  },
819
+ onPollRecovered: (suppressedErrorCount, hasUpdates) => {
820
+ sessionLogger.emit({
821
+ type: 'harness.github.monitor_recovered',
822
+ suppressedErrorCount,
823
+ });
824
+ if (hasUpdates) {
825
+ pendingGitHubRecoveries.push(suppressedErrorCount);
826
+ }
827
+ else {
828
+ eventQueue.enqueue({
829
+ type: 'harness.connectivity.recovered',
830
+ source: 'github_monitor',
831
+ suppressedErrorCount,
832
+ });
833
+ }
834
+ },
620
835
  });
621
836
  githubMonitor.start();
622
837
  }
@@ -654,6 +869,7 @@ export async function runConductorSession(options) {
654
869
  const continueOnConductorError = options.continueOnConductorError ?? false;
655
870
  const waitForOperatorExit = options.waitForOperatorExit ?? false;
656
871
  let operatorRequestedExit = false;
872
+ let operatorRequestedClose = false;
657
873
  let disposeOperatorInput;
658
874
  if (options.bindOperatorInput) {
659
875
  const bindingDispose = options.bindOperatorInput({
@@ -662,7 +878,14 @@ export async function runConductorSession(options) {
662
878
  sessionLogger.emit({ type: 'session.operator_exit' });
663
879
  workerSession.runtime.cancelAllActivePrompts();
664
880
  operatorRequestedExit = true;
665
- operatorExitController.abort();
881
+ operatorTerminationController.abort();
882
+ return false;
883
+ }
884
+ if (isOperatorCloseCommand(message)) {
885
+ sessionLogger.emit({ type: 'session.operator_close' });
886
+ workerSession.runtime.cancelAllActivePrompts();
887
+ operatorRequestedClose = true;
888
+ operatorTerminationController.abort();
666
889
  return false;
667
890
  }
668
891
  if (isOperatorReconnectCommand(message)) {
@@ -764,13 +987,20 @@ export async function runConductorSession(options) {
764
987
  stopReason = driverResult.stopReason;
765
988
  sendCount = driverResult.sendCount;
766
989
  autonomousTurns = driverResult.autonomousTurns;
767
- if (operatorRequestedExit && stopReason === 'interrupted') {
990
+ if ((operatorRequestedExit || operatorRequestedClose) &&
991
+ stopReason === 'interrupted') {
768
992
  stopReason = 'completed';
769
993
  }
770
994
  return await buildResult();
771
995
  function recordSendComplete(info) {
772
996
  sendCount = info.sendCount;
773
997
  autonomousTurns = info.autonomousTurns;
998
+ const githubUpdateIds = info.githubUpdateIds ?? [];
999
+ if (info.status === 'finished' &&
1000
+ !operatorRequestedClose &&
1001
+ githubUpdateIds.length > 0) {
1002
+ githubMonitor?.acknowledgeUpdates?.(githubUpdateIds);
1003
+ }
774
1004
  sessionUsageTracker.recordConductorRound({
775
1005
  runId: info.runId,
776
1006
  status: info.status,
@@ -817,7 +1047,9 @@ export async function runConductorSession(options) {
817
1047
  }
818
1048
  async function buildResult() {
819
1049
  sessionLogger.finish(stopReason);
820
- const skipUsageCostFetch = operatorRequestedExit || shutdownSignal.aborted;
1050
+ const skipUsageCostFetch = operatorRequestedExit ||
1051
+ operatorRequestedClose ||
1052
+ shutdownSignal.aborted;
821
1053
  const sessionUsage = await enrichSessionUsageWithCost(sessionUsageTracker.getSessionSummary(), skipUsageCostFetch
822
1054
  ? undefined
823
1055
  : async () => {
@@ -828,7 +1060,9 @@ export async function runConductorSession(options) {
828
1060
  agentId: conductorHandle.conductor.agentId,
829
1061
  escalations,
830
1062
  openQuestions: openQuestions.list(),
831
- sessionUsage: sessionUsage.totals.rounds > 0 || sessionUsage.cost != null
1063
+ sessionUsage: sessionUsage.totals.rounds > 0 ||
1064
+ sessionUsage.cost != null ||
1065
+ sessionUsage.compactionCount != null
832
1066
  ? sessionUsage
833
1067
  : undefined,
834
1068
  });
@@ -836,7 +1070,9 @@ export async function runConductorSession(options) {
836
1070
  }
837
1071
  finally {
838
1072
  teardownStarted = true;
839
- const forceShutdown = operatorRequestedExit || shutdownSignal.aborted;
1073
+ const forceShutdown = operatorRequestedExit ||
1074
+ operatorRequestedClose ||
1075
+ shutdownSignal.aborted;
840
1076
  const teardownStartedAt = Date.now();
841
1077
  const teardownPhases = {};
842
1078
  unregisterProcessSignalHandlers();
@@ -864,12 +1100,27 @@ export async function runConductorSession(options) {
864
1100
  emitTeardownPhase('permissionDeadlockMonitor');
865
1101
  permissionDeadlockMonitor.stop();
866
1102
  }
867
- if (githubMonitor) {
1103
+ if (githubMonitor && !operatorRequestedClose) {
868
1104
  githubMonitor.flush();
869
1105
  }
870
1106
  // Stop active prompts before closing ACP bridges. Requests that arrive
871
1107
  // after this point are rejected by the teardown guard above.
872
1108
  workerSession.runtime.cancelAllActivePrompts();
1109
+ if (operatorRequestedClose) {
1110
+ // A worker's ACP session exists before its first prompt completes, but
1111
+ // workerSessions is normally updated by onWorkerCompleted. Snapshot
1112
+ // attached sessions now so force shutdown cannot discard the only
1113
+ // completion event that would have persisted a resumable session ID.
1114
+ for (const attached of workerSession.runtime.listAttached()) {
1115
+ workerSessions.set(attached.name, {
1116
+ acpSessionId: attached.session.sessionId,
1117
+ acpCwd: attached.session.acpCwd,
1118
+ ...(workerAcpFingerprints.get(attached.name)
1119
+ ? { acpSpawn: workerAcpFingerprints.get(attached.name) }
1120
+ : {}),
1121
+ });
1122
+ }
1123
+ }
873
1124
  const rejectedPendingPermissions = rejectAllPendingPermissions(permissionPipeline, workerSession.inbox);
874
1125
  if (rejectedPendingPermissions.length > 0) {
875
1126
  sessionLogger.emit({
@@ -881,17 +1132,27 @@ export async function runConductorSession(options) {
881
1132
  })),
882
1133
  });
883
1134
  }
1135
+ let githubMonitorStopped = false;
1136
+ if (githubMonitor && operatorRequestedClose) {
1137
+ emitTeardownPhase('githubMonitor');
1138
+ const phaseStart = Date.now();
1139
+ await githubMonitor.stop();
1140
+ githubMonitorCursor = githubMonitor.getCursor();
1141
+ teardownPhases.githubMonitor = Date.now() - phaseStart;
1142
+ githubMonitorStopped = true;
1143
+ }
884
1144
  try {
885
1145
  emitTeardownPhase('flushSidecar');
886
1146
  const phaseStart = Date.now();
887
1147
  await flushSidecar();
888
1148
  teardownPhases.flushSidecar = Date.now() - phaseStart;
889
1149
  }
890
- catch {
891
- // best-effort persistence
1150
+ catch (error) {
1151
+ reportSidecarFlushFailure(error);
892
1152
  }
1153
+ await flushToolCallHistoryBestEffort();
893
1154
  const runGithubMonitorStop = async () => {
894
- if (!githubMonitor) {
1155
+ if (!githubMonitor || githubMonitorStopped) {
895
1156
  return;
896
1157
  }
897
1158
  emitTeardownPhase('githubMonitor');
@@ -909,7 +1170,12 @@ export async function runConductorSession(options) {
909
1170
  const runConductorClose = async () => {
910
1171
  emitTeardownPhase('conductor');
911
1172
  const phaseStart = Date.now();
912
- await conductorHandle.conductor.close();
1173
+ if (operatorRequestedClose && conductorHandle.conductor.closeForResume) {
1174
+ await conductorHandle.conductor.closeForResume();
1175
+ }
1176
+ else {
1177
+ await conductorHandle.conductor.close();
1178
+ }
913
1179
  teardownPhases.conductor = Date.now() - phaseStart;
914
1180
  };
915
1181
  if (forceShutdown) {
@@ -928,6 +1194,9 @@ export async function runConductorSession(options) {
928
1194
  await runWorkerStop();
929
1195
  await runConductorClose();
930
1196
  }
1197
+ // ACP updates can arrive while cancellation and bridge shutdown settle.
1198
+ // Flush once more after all workers stop to capture their final snapshot.
1199
+ await flushToolCallHistoryBestEffort();
931
1200
  sessionLogger.emit({
932
1201
  type: 'harness.teardown',
933
1202
  force: forceShutdown,
@@ -948,12 +1217,6 @@ export async function runConductorSession(options) {
948
1217
  }
949
1218
  }
950
1219
  }
951
- export function createConductorAgentFactory(backend) {
952
- if (backend === 'cursor') {
953
- return createCursorSdkConductorAgentFactory();
954
- }
955
- return createPiConductorAgentFactory();
956
- }
957
1220
  function createPiKickoffPrompt(issueUrl) {
958
1221
  return (`Start the conductor workflow for ${issueUrl}. ` +
959
1222
  'Review the Issue context in your system instructions and dispatch the first required worker with prompt_worker.');