@agents-ensemble/core 0.20.0 → 0.22.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 (270) hide show
  1. package/assets/pi/pi-cli-env-wrapper.cmd +28 -0
  2. package/assets/pi/pi-cli-env-wrapper.sh +19 -0
  3. package/assets/pi/settings.json +4 -0
  4. package/dist/acp/acp-preset-bins.d.ts +3 -3
  5. package/dist/acp/acp-preset-bins.d.ts.map +1 -1
  6. package/dist/acp/acp-preset-bins.js +3 -3
  7. package/dist/acp/acp-preset-bins.js.map +1 -1
  8. package/dist/acp/index.d.ts +3 -1
  9. package/dist/acp/index.d.ts.map +1 -1
  10. package/dist/acp/index.js +2 -1
  11. package/dist/acp/index.js.map +1 -1
  12. package/dist/acp/pi-acp-wrapper.d.ts +3 -0
  13. package/dist/acp/pi-acp-wrapper.d.ts.map +1 -0
  14. package/dist/acp/pi-acp-wrapper.js +41 -0
  15. package/dist/acp/pi-acp-wrapper.js.map +1 -0
  16. package/dist/acp/pi-builtin-config.d.ts +10 -0
  17. package/dist/acp/pi-builtin-config.d.ts.map +1 -0
  18. package/dist/acp/pi-builtin-config.js +36 -0
  19. package/dist/acp/pi-builtin-config.js.map +1 -0
  20. package/dist/acp/pi-runtime.d.ts +23 -0
  21. package/dist/acp/pi-runtime.d.ts.map +1 -0
  22. package/dist/acp/pi-runtime.js +160 -0
  23. package/dist/acp/pi-runtime.js.map +1 -0
  24. package/dist/acp/pi-session-map.d.ts +36 -0
  25. package/dist/acp/pi-session-map.d.ts.map +1 -0
  26. package/dist/acp/pi-session-map.js +266 -0
  27. package/dist/acp/pi-session-map.js.map +1 -0
  28. package/dist/acp/resolve-acp-spawn.d.ts +9 -3
  29. package/dist/acp/resolve-acp-spawn.d.ts.map +1 -1
  30. package/dist/acp/resolve-acp-spawn.js +41 -9
  31. package/dist/acp/resolve-acp-spawn.js.map +1 -1
  32. package/dist/acp/resolve-bundled-acp-bin.d.ts +2 -2
  33. package/dist/acp/resolve-bundled-acp-bin.d.ts.map +1 -1
  34. package/dist/acp/resolve-bundled-acp-bin.js +4 -4
  35. package/dist/acp/resolve-bundled-acp-bin.js.map +1 -1
  36. package/dist/acp/validate-acp-preset-prerequisites.d.ts +1 -1
  37. package/dist/acp/validate-acp-preset-prerequisites.d.ts.map +1 -1
  38. package/dist/acp/validate-acp-preset-prerequisites.js +30 -10
  39. package/dist/acp/validate-acp-preset-prerequisites.js.map +1 -1
  40. package/dist/conductor/conductor-agent.d.ts +15 -3
  41. package/dist/conductor/conductor-agent.d.ts.map +1 -1
  42. package/dist/conductor/conductor-auth.d.ts +4 -3
  43. package/dist/conductor/conductor-auth.d.ts.map +1 -1
  44. package/dist/conductor/conductor-auth.js +20 -4
  45. package/dist/conductor/conductor-auth.js.map +1 -1
  46. package/dist/conductor/conductor-dispatch-lifecycle.d.ts +85 -0
  47. package/dist/conductor/conductor-dispatch-lifecycle.d.ts.map +1 -0
  48. package/dist/conductor/conductor-dispatch-lifecycle.js +190 -0
  49. package/dist/conductor/conductor-dispatch-lifecycle.js.map +1 -0
  50. package/dist/conductor/conductor-history.d.ts +15 -0
  51. package/dist/conductor/conductor-history.d.ts.map +1 -1
  52. package/dist/conductor/conductor-history.js +38 -7
  53. package/dist/conductor/conductor-history.js.map +1 -1
  54. package/dist/conductor/conductor-path-safety.d.ts +23 -0
  55. package/dist/conductor/conductor-path-safety.d.ts.map +1 -0
  56. package/dist/conductor/conductor-path-safety.js +319 -0
  57. package/dist/conductor/conductor-path-safety.js.map +1 -0
  58. package/dist/conductor/conductor-pi-auth.d.ts +57 -2
  59. package/dist/conductor/conductor-pi-auth.d.ts.map +1 -1
  60. package/dist/conductor/conductor-pi-auth.js +718 -40
  61. package/dist/conductor/conductor-pi-auth.js.map +1 -1
  62. package/dist/conductor/conductor-recovery-inbox.d.ts +73 -0
  63. package/dist/conductor/conductor-recovery-inbox.d.ts.map +1 -0
  64. package/dist/conductor/conductor-recovery-inbox.js +181 -0
  65. package/dist/conductor/conductor-recovery-inbox.js.map +1 -0
  66. package/dist/conductor/conductor-send-reconnect.d.ts +33 -4
  67. package/dist/conductor/conductor-send-reconnect.d.ts.map +1 -1
  68. package/dist/conductor/conductor-send-reconnect.js +89 -24
  69. package/dist/conductor/conductor-send-reconnect.js.map +1 -1
  70. package/dist/conductor/conductor-session-driver.d.ts +34 -1
  71. package/dist/conductor/conductor-session-driver.d.ts.map +1 -1
  72. package/dist/conductor/conductor-session-driver.js +529 -75
  73. package/dist/conductor/conductor-session-driver.js.map +1 -1
  74. package/dist/conductor/conductor-session.d.ts +4 -0
  75. package/dist/conductor/conductor-session.d.ts.map +1 -1
  76. package/dist/conductor/conductor-session.js +416 -145
  77. package/dist/conductor/conductor-session.js.map +1 -1
  78. package/dist/conductor/conductor-setup.d.ts +30 -0
  79. package/dist/conductor/conductor-setup.d.ts.map +1 -0
  80. package/dist/conductor/conductor-setup.js +243 -0
  81. package/dist/conductor/conductor-setup.js.map +1 -0
  82. package/dist/conductor/cursor-bootstrap-state.d.ts +9 -0
  83. package/dist/conductor/cursor-bootstrap-state.d.ts.map +1 -0
  84. package/dist/conductor/cursor-bootstrap-state.js +11 -0
  85. package/dist/conductor/cursor-bootstrap-state.js.map +1 -0
  86. package/dist/conductor/cursor-run-snapshots.d.ts +30 -0
  87. package/dist/conductor/cursor-run-snapshots.d.ts.map +1 -0
  88. package/dist/conductor/cursor-run-snapshots.js +120 -0
  89. package/dist/conductor/cursor-run-snapshots.js.map +1 -0
  90. package/dist/conductor/index.d.ts +18 -7
  91. package/dist/conductor/index.d.ts.map +1 -1
  92. package/dist/conductor/index.js +9 -4
  93. package/dist/conductor/index.js.map +1 -1
  94. package/dist/conductor/operator-command.d.ts +5 -0
  95. package/dist/conductor/operator-command.d.ts.map +1 -1
  96. package/dist/conductor/operator-command.js +6 -0
  97. package/dist/conductor/operator-command.js.map +1 -1
  98. package/dist/conductor/operator-input-binding.d.ts +17 -0
  99. package/dist/conductor/operator-input-binding.d.ts.map +1 -1
  100. package/dist/conductor/pi-auth-recovery-snapshot-store.d.ts +45 -0
  101. package/dist/conductor/pi-auth-recovery-snapshot-store.d.ts.map +1 -0
  102. package/dist/conductor/pi-auth-recovery-snapshot-store.js +116 -0
  103. package/dist/conductor/pi-auth-recovery-snapshot-store.js.map +1 -0
  104. package/dist/conductor/pi-conductor-agent.d.ts +6 -3
  105. package/dist/conductor/pi-conductor-agent.d.ts.map +1 -1
  106. package/dist/conductor/pi-conductor-agent.js +276 -53
  107. package/dist/conductor/pi-conductor-agent.js.map +1 -1
  108. package/dist/conductor/pi-conductor-session-file.d.ts +13 -0
  109. package/dist/conductor/pi-conductor-session-file.d.ts.map +1 -0
  110. package/dist/conductor/pi-conductor-session-file.js +21 -0
  111. package/dist/conductor/pi-conductor-session-file.js.map +1 -0
  112. package/dist/conductor/pi-resource-loader.d.ts +6 -0
  113. package/dist/conductor/pi-resource-loader.d.ts.map +1 -1
  114. package/dist/conductor/pi-resource-loader.js +26 -12
  115. package/dist/conductor/pi-resource-loader.js.map +1 -1
  116. package/dist/conductor/pi-user-root-lock.d.ts +8 -0
  117. package/dist/conductor/pi-user-root-lock.d.ts.map +1 -0
  118. package/dist/conductor/pi-user-root-lock.js +53 -0
  119. package/dist/conductor/pi-user-root-lock.js.map +1 -0
  120. package/dist/conductor/recovery-inbox-command.d.ts +29 -0
  121. package/dist/conductor/recovery-inbox-command.d.ts.map +1 -0
  122. package/dist/conductor/recovery-inbox-command.js +248 -0
  123. package/dist/conductor/recovery-inbox-command.js.map +1 -0
  124. package/dist/conductor/session/events/index.d.ts +1 -1
  125. package/dist/conductor/session/events/index.d.ts.map +1 -1
  126. package/dist/conductor/session/events/index.js.map +1 -1
  127. package/dist/conductor/session/events/session-event.d.ts +43 -1
  128. package/dist/conductor/session/events/session-event.d.ts.map +1 -1
  129. package/dist/conductor/session/events/session-log-event-groups.d.ts +1 -1
  130. package/dist/conductor/session/events/session-log-event-groups.d.ts.map +1 -1
  131. package/dist/conductor/session/events/session-log-event-groups.js +2 -0
  132. package/dist/conductor/session/events/session-log-event-groups.js.map +1 -1
  133. package/dist/conductor/session/events/session-log-event.d.ts +4 -0
  134. package/dist/conductor/session/events/session-log-event.d.ts.map +1 -1
  135. package/dist/conductor/session/format-session-event.js +57 -5
  136. package/dist/conductor/session/format-session-event.js.map +1 -1
  137. package/dist/conductor/session/select-dispatch-batch.d.ts +2 -0
  138. package/dist/conductor/session/select-dispatch-batch.d.ts.map +1 -1
  139. package/dist/conductor/session/select-dispatch-batch.js +36 -2
  140. package/dist/conductor/session/select-dispatch-batch.js.map +1 -1
  141. package/dist/conductor/session/session-event-queue.d.ts +14 -0
  142. package/dist/conductor/session/session-event-queue.d.ts.map +1 -1
  143. package/dist/conductor/session/session-event-queue.js +82 -10
  144. package/dist/conductor/session/session-event-queue.js.map +1 -1
  145. package/dist/conductor/session/session-event.d.ts +1 -1
  146. package/dist/conductor/session/session-event.d.ts.map +1 -1
  147. package/dist/conductor/session-policy.d.ts.map +1 -1
  148. package/dist/conductor/session-policy.js +8 -4
  149. package/dist/conductor/session-policy.js.map +1 -1
  150. package/dist/conductor/submit-operator-input.d.ts +3 -0
  151. package/dist/conductor/submit-operator-input.d.ts.map +1 -1
  152. package/dist/conductor/submit-operator-input.js +21 -0
  153. package/dist/conductor/submit-operator-input.js.map +1 -1
  154. package/dist/config/deep-merge.d.ts.map +1 -1
  155. package/dist/config/deep-merge.js +13 -3
  156. package/dist/config/deep-merge.js.map +1 -1
  157. package/dist/config/defaults.d.ts.map +1 -1
  158. package/dist/config/defaults.js +1 -1
  159. package/dist/config/defaults.js.map +1 -1
  160. package/dist/config/doctor-settings.d.ts +59 -0
  161. package/dist/config/doctor-settings.d.ts.map +1 -0
  162. package/dist/config/doctor-settings.js +343 -0
  163. package/dist/config/doctor-settings.js.map +1 -0
  164. package/dist/config/ensemble-config-digest.d.ts +2 -4
  165. package/dist/config/ensemble-config-digest.d.ts.map +1 -1
  166. package/dist/config/ensemble-config-digest.js +2 -4
  167. package/dist/config/ensemble-config-digest.js.map +1 -1
  168. package/dist/config/index.d.ts +3 -1
  169. package/dist/config/index.d.ts.map +1 -1
  170. package/dist/config/index.js +1 -0
  171. package/dist/config/index.js.map +1 -1
  172. package/dist/config/load-ensemble-config.d.ts.map +1 -1
  173. package/dist/config/load-ensemble-config.js +8 -1
  174. package/dist/config/load-ensemble-config.js.map +1 -1
  175. package/dist/config/parse-config.d.ts.map +1 -1
  176. package/dist/config/parse-config.js +34 -14
  177. package/dist/config/parse-config.js.map +1 -1
  178. package/dist/config/resolve-settings.d.ts +3 -1
  179. package/dist/config/resolve-settings.d.ts.map +1 -1
  180. package/dist/config/resolve-settings.js +7 -4
  181. package/dist/config/resolve-settings.js.map +1 -1
  182. package/dist/config/types.d.ts +14 -5
  183. package/dist/config/types.d.ts.map +1 -1
  184. package/dist/dispatch/attach-worker.js +1 -1
  185. package/dist/dispatch/attach-worker.js.map +1 -1
  186. package/dist/dispatch/worker-acp-session.d.ts +21 -3
  187. package/dist/dispatch/worker-acp-session.d.ts.map +1 -1
  188. package/dist/dispatch/worker-acp-session.js +50 -13
  189. package/dist/dispatch/worker-acp-session.js.map +1 -1
  190. package/dist/ensemble-paths.d.ts +19 -19
  191. package/dist/ensemble-paths.d.ts.map +1 -1
  192. package/dist/ensemble-paths.js +40 -22
  193. package/dist/ensemble-paths.js.map +1 -1
  194. package/dist/issue/resolve-issue-url.d.ts +4 -0
  195. package/dist/issue/resolve-issue-url.d.ts.map +1 -1
  196. package/dist/issue/resolve-issue-url.js +2 -2
  197. package/dist/issue/resolve-issue-url.js.map +1 -1
  198. package/dist/operator-runtime.d.ts +19 -4
  199. package/dist/operator-runtime.d.ts.map +1 -1
  200. package/dist/operator-runtime.js +13 -2
  201. package/dist/operator-runtime.js.map +1 -1
  202. package/dist/profile/types.d.ts +4 -0
  203. package/dist/profile/types.d.ts.map +1 -1
  204. package/dist/profile/types.js +29 -12
  205. package/dist/profile/types.js.map +1 -1
  206. package/dist/representation/escape-terminal-control-characters.d.ts +9 -0
  207. package/dist/representation/escape-terminal-control-characters.d.ts.map +1 -0
  208. package/dist/representation/escape-terminal-control-characters.js +34 -0
  209. package/dist/representation/escape-terminal-control-characters.js.map +1 -0
  210. package/dist/representation/index.d.ts +1 -0
  211. package/dist/representation/index.d.ts.map +1 -1
  212. package/dist/representation/index.js +1 -0
  213. package/dist/representation/index.js.map +1 -1
  214. package/dist/runtime/types.d.ts +9 -0
  215. package/dist/runtime/types.d.ts.map +1 -1
  216. package/dist/runtime/worker-runtime.d.ts +14 -1
  217. package/dist/runtime/worker-runtime.d.ts.map +1 -1
  218. package/dist/runtime/worker-runtime.js +197 -50
  219. package/dist/runtime/worker-runtime.js.map +1 -1
  220. package/dist/runtime/worker-session.d.ts +2 -1
  221. package/dist/runtime/worker-session.d.ts.map +1 -1
  222. package/dist/runtime/worker-session.js +1 -0
  223. package/dist/runtime/worker-session.js.map +1 -1
  224. package/dist/session/export-session-bundle.d.ts +77 -0
  225. package/dist/session/export-session-bundle.d.ts.map +1 -0
  226. package/dist/session/export-session-bundle.js +640 -0
  227. package/dist/session/export-session-bundle.js.map +1 -0
  228. package/dist/session/index.d.ts +6 -3
  229. package/dist/session/index.d.ts.map +1 -1
  230. package/dist/session/index.js +4 -1
  231. package/dist/session/index.js.map +1 -1
  232. package/dist/session/open-question-control.d.ts +9 -0
  233. package/dist/session/open-question-control.d.ts.map +1 -1
  234. package/dist/session/open-question-control.js +26 -1
  235. package/dist/session/open-question-control.js.map +1 -1
  236. package/dist/session/open-questions-store.d.ts +1 -1
  237. package/dist/session/open-questions-store.d.ts.map +1 -1
  238. package/dist/session/open-questions-store.js +3 -6
  239. package/dist/session/open-questions-store.js.map +1 -1
  240. package/dist/session/persisted-file.d.ts +5 -0
  241. package/dist/session/persisted-file.d.ts.map +1 -0
  242. package/dist/session/persisted-file.js +56 -0
  243. package/dist/session/persisted-file.js.map +1 -0
  244. package/dist/session/session-resume-store.d.ts +79 -10
  245. package/dist/session/session-resume-store.d.ts.map +1 -1
  246. package/dist/session/session-resume-store.js +353 -83
  247. package/dist/session/session-resume-store.js.map +1 -1
  248. package/dist/session/session-snapshot-paths.d.ts +11 -0
  249. package/dist/session/session-snapshot-paths.d.ts.map +1 -0
  250. package/dist/session/session-snapshot-paths.js +41 -0
  251. package/dist/session/session-snapshot-paths.js.map +1 -0
  252. package/dist/session/session-state-store.d.ts +2 -2
  253. package/dist/session/session-state-store.d.ts.map +1 -1
  254. package/dist/session/session-state-store.js +5 -16
  255. package/dist/session/session-state-store.js.map +1 -1
  256. package/dist/session/tool-call-snapshot-history.d.ts +12 -1
  257. package/dist/session/tool-call-snapshot-history.d.ts.map +1 -1
  258. package/dist/session/tool-call-snapshot-history.js +75 -70
  259. package/dist/session/tool-call-snapshot-history.js.map +1 -1
  260. package/dist/session/worker-session-history.d.ts +51 -0
  261. package/dist/session/worker-session-history.d.ts.map +1 -0
  262. package/dist/session/worker-session-history.js +654 -0
  263. package/dist/session/worker-session-history.js.map +1 -0
  264. package/dist/workspace/index.d.ts +1 -1
  265. package/dist/workspace/index.d.ts.map +1 -1
  266. package/dist/worktree/worktree.d.ts +6 -1
  267. package/dist/worktree/worktree.d.ts.map +1 -1
  268. package/dist/worktree/worktree.js +123 -2
  269. package/dist/worktree/worktree.js.map +1 -1
  270. package/package.json +9 -4
@@ -1,3 +1,5 @@
1
+ import { stat } from 'node:fs/promises';
2
+ import { resolve as resolvePath } from 'node:path';
1
3
  import { computeAcpModelConfigDigest, computeEnsembleConfigDigest, computeResumeFeasibilityConfigDigest, } from '../config/ensemble-config-digest.js';
2
4
  import { loadEnsembleConfig } from '../config/load-ensemble-config.js';
3
5
  import { computeMcpConfigDigest, resolveMcpServers, } from '../mcp/load-mcp-config.js';
@@ -12,6 +14,7 @@ import { createPermissionDeadlockMonitor, } from '../permission/permission-deadl
12
14
  import { createResolvePermissionTool } from '../permission/resolve-permission-tool.js';
13
15
  import { buildPermissionBrief, getPermissionToolCallId, } from '../permission/permission-brief.js';
14
16
  import { acpSpawnFingerprint, resolveWorkerAcpSpawn, resolvedAcpSpawnToOptions, } from '../acp/resolve-acp-spawn.js';
17
+ import { configurePiWorkerRuntime } from '../acp/pi-runtime.js';
15
18
  import { finalizeResolvedAcpSpawn, validateWorkerAcpPrerequisites, } from '../acp/validate-acp-preset-prerequisites.js';
16
19
  import { profileWorkersToSessionSpecs, sessionStateFromProfile, resolveAgentPromptModule, } from '../profile/types.js';
17
20
  import { WorkerSession } from '../runtime/worker-session.js';
@@ -31,25 +34,31 @@ import { createConductorAgentFactory } from './conductor-agent-factory.js';
31
34
  import { resolveConductorReasoning } from './resolve-conductor-reasoning.js';
32
35
  import { formatConductorAuthRecoveryHint, isConductorSendAuthError, } from './conductor-auth.js';
33
36
  import { resolvePiConductorProvider } from './conductor-pi-auth.js';
37
+ import { closeConductorAgentOnce } from './conductor-send-reconnect.js';
38
+ import { escapeTerminalControlCharacters } from '../representation/escape-terminal-control-characters.js';
39
+ import { cursorBootstrapCompleteForStore } from './cursor-bootstrap-state.js';
34
40
  import { SessionLogger } from './session/session-logger.js';
35
41
  import { SessionEventQueue } from './session/session-event-queue.js';
36
42
  import { bufferDispatchHoldEvents, createDispatchHoldState, pruneStalePermissionEvents, } from './session/dispatch-hold.js';
37
43
  import { createDispatchHoldReminderMonitor, formatDispatchHoldReminderMessage, } from './session/dispatch-hold-reminder-monitor.js';
38
44
  import { operatorInputMaxTurns, resolveMaxTurns, } from './session-policy.js';
39
- import { runConductorSessionDriver } from './conductor-session-driver.js';
45
+ import { runConductorSessionDriver, } from './conductor-session-driver.js';
40
46
  import { isOperatorExitCommand } from './operator-exit.js';
41
47
  import { isOperatorCloseCommand } from './operator-close.js';
42
48
  import { isOperatorReconnectCommand } from './operator-reconnect.js';
43
49
  import { parseOperatorMessageDelivery } from './operator-command.js';
44
- import { assertSessionResumeStoreMatches, requireLocatedSessionResumeStoreForResume, saveSessionResumeStore, SESSION_RESUME_STORE_VERSION, sessionResumeStorePath, } from '../session/session-resume-store.js';
50
+ import { assertSessionResumeStoreMatches, canonicalSessionResumeStorePath, requireLocatedSessionResumeStoreForResume, saveSessionResumeStore, SESSION_RESUME_STORE_VERSION, } from '../session/session-resume-store.js';
45
51
  import { buildSessionModelsSummary, } from '../session/build-session-models-summary.js';
46
- import { saveSessionStatusLine, sessionStateStorePath, loadSessionStateStore, legacySessionStateStorePath, } from '../session/session-state-store.js';
52
+ import { saveSessionStatusLine, sessionStateStorePath, loadSessionStateStore, } from '../session/session-state-store.js';
47
53
  import { loadToolCallSnapshotHistory, pruneExpiredToolCallSnapshotHistoryFiles, saveToolCallSnapshotHistory, toolCallSnapshotHistoryPath, } from '../session/tool-call-snapshot-history.js';
48
- import { loadOpenQuestionsStore, legacyOpenQuestionsStorePath, openQuestionsStorePath, saveOpenQuestionsStore, } from '../session/open-questions-store.js';
54
+ import { loadOpenQuestionsStore, openQuestionsStorePath, saveOpenQuestionsStore, } from '../session/open-questions-store.js';
55
+ import { sessionSnapshotPaths } from '../session/session-snapshot-paths.js';
49
56
  import { CONDUCTOR_MESSAGE_HISTORY_MAX_ENTRIES, OpenQuestionControlOperationError, startOpenQuestionControlServer, } from '../session/open-question-control.js';
50
57
  import { formatPermissionSummaryForOperator } from '../permission/format-permission-summary-for-operator.js';
51
58
  import { enrichSessionUsageWithCost } from '../usage/enrich-session-usage-cost.js';
52
59
  import { SessionUsageTracker } from '../usage/session-usage-tracker.js';
60
+ import { ConductorDispatchLifecycle } from './conductor-dispatch-lifecycle.js';
61
+ import { ConductorRecoveryInbox } from './conductor-recovery-inbox.js';
53
62
  import { enqueueOperatorMessage as enqueueOperatorMessageEvent, submitOperatorInput, } from './submit-operator-input.js';
54
63
  import { createGitHubMonitor, } from '../github/github-monitor.js';
55
64
  import { resolveConductorBackendSetting, resolveConductorPiResourcePaths, resolveGitHubMonitorEnabled, } from '../config/resolve-settings.js';
@@ -63,7 +72,6 @@ import { createRegisterGitHubWatchTool } from '../github/register-github-watch-t
63
72
  import { createUnregisterGitHubWatchTool } from '../github/unregister-github-watch-tool.js';
64
73
  import { compileConductorSystemPrompt } from '../prompt/compile-system-prompt.js';
65
74
  import { loadUserInstructions } from '../prompt/load-user-instructions.js';
66
- import { resolveEnsemblePaths } from '../ensemble-paths.js';
67
75
  const OPEN_QUESTION_CONTROL_DRAIN_TIMEOUT_MS = 5_000;
68
76
  const RECORDED_ANSWER_UNKNOWN_DISPATCH = {
69
77
  outcome: 'unknown',
@@ -168,7 +176,7 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
168
176
  });
169
177
  });
170
178
  attachLegacySessionCallbacks(sessionLogger, options);
171
- const expiredHistory = await pruneExpiredToolCallSnapshotHistoryFiles(options.repoRoot);
179
+ const expiredHistory = await pruneExpiredToolCallSnapshotHistoryFiles({ repoRoot: options.repoRoot, issueUrl: options.issueUrl });
172
180
  if (expiredHistory.failedCount > 0) {
173
181
  sessionLogger.emit({
174
182
  type: 'harness.warning',
@@ -185,6 +193,16 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
185
193
  let lastConductorMessage = null;
186
194
  const conductorMessages = [];
187
195
  const eventQueue = new SessionEventQueue();
196
+ const resumeNoticeDetails = new Set();
197
+ const reportResumeNotice = (message) => {
198
+ if (resumeNoticeDetails.has(message))
199
+ return;
200
+ resumeNoticeDetails.add(message);
201
+ sessionLogger.emit({ type: 'harness.warning', message });
202
+ };
203
+ const recoveryInbox = new ConductorRecoveryInbox();
204
+ const dispatchLifecycle = new ConductorDispatchLifecycle();
205
+ startupCleanup.add(() => recoveryInbox.dispose());
188
206
  const openQuestionDispatchReceipts = new WeakMap();
189
207
  const pendingOpenQuestionDispatchReceipts = new Set();
190
208
  const dispatchHoldState = createDispatchHoldState();
@@ -207,11 +225,13 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
207
225
  let githubMonitorCursor;
208
226
  let githubMonitor;
209
227
  let activeResumeStorePath;
210
- let activeStoreLocation = 'user-path-hash';
228
+ let activeLocatedResumeStore;
229
+ let createdHarnessCliVersion = options.cliPackageVersion;
211
230
  let drainStartupResumeStoreWrites = async () => { };
212
231
  const pendingGitHubRecoveries = [];
213
232
  const conductorCwd = options.conductorCwd ?? process.cwd();
214
233
  let cursorBootstrapRunId;
234
+ const cursorBootstrapState = conductorBackend === 'cursor' ? { status: 'incomplete' } : undefined;
215
235
  const mcpServers = await resolveMcpServers(options.repoRoot, options.mcpConfigOptions);
216
236
  const mcpConfigDigest = computeMcpConfigDigest(mcpServers);
217
237
  const mcpConfigConfigured = Object.keys(mcpServers).length > 0;
@@ -221,10 +241,13 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
221
241
  const locatedResumeStore = await requireLocatedSessionResumeStoreForResume({
222
242
  repoRoot: options.repoRoot,
223
243
  conductorAgentId: options.resumeAgentId,
244
+ issueUrl: options.issueUrl,
224
245
  });
225
246
  const loadedResumeStore = locatedResumeStore.resumeStore;
247
+ activeLocatedResumeStore = locatedResumeStore;
248
+ // A resume keeps its origin version; a legacy store stays without one.
249
+ createdHarnessCliVersion = loadedResumeStore.createdHarnessCliVersion;
226
250
  activeResumeStorePath = locatedResumeStore.path;
227
- activeStoreLocation = locatedResumeStore.location;
228
251
  const savedConfigDigestScheme = loadedResumeStore.ensembleConfigDigestScheme;
229
252
  const effectiveConfigDigestScheme = savedConfigDigestScheme ?? 'full';
230
253
  const currentEnsembleConfigDigest = effectiveConfigDigestScheme === 'resume-feasibility'
@@ -233,17 +256,39 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
233
256
  assertSessionResumeStoreMatches(loadedResumeStore, {
234
257
  conductorAgentId: options.resumeAgentId,
235
258
  issueUrl: options.issueUrl,
236
- repoRoot: options.repoRoot,
237
- conductorBackend,
238
- mcpConfigDigest,
239
- mcpConfigConfigured,
240
- ensembleConfigDigest: currentEnsembleConfigDigest,
241
259
  });
242
- if (loadedResumeStore.ensembleConfigDigest === undefined) {
243
- sessionLogger.emit({
244
- type: 'harness.warning',
245
- message: 'SessionResumeStore has no ensemble config digest; config consistency cannot be verified for this legacy session.',
246
- });
260
+ if (resolvePath(loadedResumeStore.repoRoot) !== resolvePath(options.repoRoot)) {
261
+ reportResumeNotice(`保存時の repoRoot ${loadedResumeStore.repoRoot} と現在の repoRoot ${options.repoRoot} が異なります。現在の clone を使って再開します。Store に commit SHA がないため、保存時 clone にだけある local commit / 未コミット変更は照合・復元できません。`);
262
+ if (!(await pathExists(loadedResumeStore.repoRoot))) {
263
+ reportResumeNotice(`保存時の clone ${loadedResumeStore.repoRoot} は現在参照できません。そこにだけあった未コミット変更は復元できず、Store に commit SHA がないため欠落した commit の有無も照合できません。`);
264
+ }
265
+ }
266
+ if (loadedResumeStore.conductorCwd !== undefined &&
267
+ resolvePath(loadedResumeStore.conductorCwd) !== resolvePath(conductorCwd)) {
268
+ reportResumeNotice(`保存時の conductor cwd ${loadedResumeStore.conductorCwd} は現在の cwd ${conductorCwd} とは異なります。現在の cwd で backend resume を試みます。以前の cwd にだけある local state は引き継がれず、Store に commit SHA がないため commit の差も照合できません。`);
269
+ if (!(await pathExists(loadedResumeStore.conductorCwd))) {
270
+ reportResumeNotice(`保存時の conductor cwd ${loadedResumeStore.conductorCwd} は見つからないため、以前の workspace のローカル状態は復元できません。`);
271
+ }
272
+ }
273
+ const savedBackend = loadedResumeStore.conductorBackend ?? 'cursor';
274
+ if (savedBackend !== conductorBackend) {
275
+ reportResumeNotice(`保存時の conductor backend は ${savedBackend}、現在の設定は ${conductorBackend} です。現在の backend で復元を試みます。復元できなければ session は失敗として扱います。`);
276
+ }
277
+ if (loadedResumeStore.ensembleConfigDigest !== undefined &&
278
+ loadedResumeStore.ensembleConfigDigest !== currentEnsembleConfigDigest) {
279
+ reportResumeNotice('保存時と現在の Ensemble config が異なります。保存時の設定には戻さず、現在の clone の config で復元を試みます。');
280
+ }
281
+ else if (loadedResumeStore.ensembleConfigDigest === undefined) {
282
+ reportResumeNotice('SessionResumeStore に config digest がないため差分を比較できません。現在の clone の config で復元します。');
283
+ }
284
+ if (loadedResumeStore.mcpConfigDigest === undefined
285
+ ? mcpConfigConfigured
286
+ : loadedResumeStore.mcpConfigDigest !== mcpConfigDigest) {
287
+ reportResumeNotice('保存時と現在の MCP config が異なるか、保存時の digest がありません。現在の clone の MCP config を使って復元を試みます。');
288
+ }
289
+ if (JSON.stringify(loadedResumeStore.profile) !==
290
+ JSON.stringify(options.profile)) {
291
+ reportResumeNotice('保存時の team profile と現在の clone で解決した profile が異なります。以降は現在の profile を使います。');
247
292
  }
248
293
  const hasComparableLegacyFullDigest = effectiveConfigDigestScheme === 'full' &&
249
294
  loadedResumeStore.ensembleConfigDigest !== undefined;
@@ -270,6 +315,35 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
270
315
  // acquired resource is released successfully; otherwise keep this record
271
316
  // marked unknown/running so it does not claim a possibly active session is
272
317
  // closed.
318
+ cursorBootstrapRunId = loadedResumeStore.cursorBootstrapRunId;
319
+ if (cursorBootstrapState) {
320
+ const savedBootstrapComplete = loadedResumeStore.cursorBootstrapComplete;
321
+ cursorBootstrapState.status =
322
+ savedBootstrapComplete === undefined
323
+ ? 'unknown'
324
+ : savedBootstrapComplete
325
+ ? 'complete'
326
+ : 'incomplete';
327
+ if (cursorBootstrapState.status === 'unknown') {
328
+ if (options.operatorCanInspectRecovery !== true ||
329
+ !options.bindOperatorInput) {
330
+ throw new Error([
331
+ '旧 Cursor session の SessionResumeStore に bootstrap 状態がなく、compiled prompt が provider に届いたか判定できません。',
332
+ '安全のため backend resume と event dispatch は開始しませんでした。',
333
+ 'stdin / stdout / stderr がすべて対話 TTY の環境で `--continue` し、`/recovery bootstrap assume-complete` または `/recovery bootstrap assume-incomplete` を明示してください。',
334
+ 'complete は prompt を再送せず、incomplete は次の新しい event に prompt を添えます。どちらも失敗 input や旧 queue は再送しません。',
335
+ ].join('\n'));
336
+ }
337
+ sessionLogger.emit({
338
+ type: 'harness.warning',
339
+ message: [
340
+ '旧 Cursor session の SessionResumeStore に bootstrap 状態がなく、compiled prompt が provider に届いたか判定できません。',
341
+ 'operator が `/recovery bootstrap assume-complete` または `/recovery bootstrap assume-incomplete` を選んで判断を保存するまで、すべての event dispatch を停止します。',
342
+ 'どちらの選択も失敗 input や旧 queue を復元・再送しません。',
343
+ ].join(' '),
344
+ });
345
+ }
346
+ }
273
347
  const resumeStore = {
274
348
  ...loadedResumeStore,
275
349
  lifecycle: 'unknown',
@@ -278,7 +352,6 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
278
352
  harnessSocketPath: undefined,
279
353
  updatedAt: Date.now(),
280
354
  };
281
- cursorBootstrapRunId = loadedResumeStore.cursorBootstrapRunId;
282
355
  harnessPhase = 'running';
283
356
  harnessModifiedAt = resumeStore.modifiedAt;
284
357
  let resumeStoreActivationCommitted = false;
@@ -300,6 +373,7 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
300
373
  const history = await loadToolCallSnapshotHistory({
301
374
  repoRoot: options.repoRoot,
302
375
  conductorAgentId: options.resumeAgentId,
376
+ issueUrl: options.issueUrl,
303
377
  });
304
378
  resumeToolCallSnapshots = history.entries;
305
379
  toolCallHistoryNeedsRewrite = history.needsRewrite;
@@ -309,47 +383,8 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
309
383
  message: `${history.warning}; the session will continue without affected history`,
310
384
  });
311
385
  }
312
- const openQuestionsPath = openQuestionsStorePath({
313
- repoRoot: options.repoRoot,
314
- conductorAgentId: options.resumeAgentId,
315
- issueUrl: options.issueUrl,
316
- location: activeStoreLocation,
317
- });
386
+ const openQuestionsPath = sessionSnapshotPaths(options.repoRoot, locatedResumeStore, 'open-questions')[0];
318
387
  let openQuestionsSnapshot = await loadOpenQuestionsStore(openQuestionsPath);
319
- if (!openQuestionsSnapshot) {
320
- const fallbackPaths = [
321
- ...(activeStoreLocation === 'canonical-slug'
322
- ? [
323
- openQuestionsStorePath({
324
- repoRoot: options.repoRoot,
325
- conductorAgentId: options.resumeAgentId,
326
- location: 'user-path-hash',
327
- }),
328
- ]
329
- : []),
330
- legacyOpenQuestionsStorePath({
331
- repoRoot: options.repoRoot,
332
- conductorAgentId: options.resumeAgentId,
333
- }),
334
- ];
335
- for (const fallbackPath of fallbackPaths) {
336
- openQuestionsSnapshot = await loadOpenQuestionsStore(fallbackPath);
337
- if (openQuestionsSnapshot)
338
- break;
339
- }
340
- if (openQuestionsSnapshot) {
341
- try {
342
- await saveOpenQuestionsStore(openQuestionsPath, openQuestionsSnapshot);
343
- }
344
- catch (error) {
345
- const detail = error instanceof Error ? error.message : String(error);
346
- sessionLogger.emit({
347
- type: 'harness.warning',
348
- message: `旧保存先の open question を user 保存先へ移行できませんでした。今回のセッションでは旧 YAML を読み込みます。 error=${detail}`,
349
- });
350
- }
351
- }
352
- }
353
388
  if (openQuestionsSnapshot) {
354
389
  openQuestions.restore(openQuestionsSnapshot);
355
390
  }
@@ -370,9 +405,6 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
370
405
  });
371
406
  }
372
407
  }
373
- activeProfile = resumeStore.profile;
374
- // ResumeStores written before this setting always started every worker eagerly.
375
- activeLazyWorkers = resumeStore.lazyWorkers ?? false;
376
408
  githubMonitorCursor = resumeStore.githubMonitor;
377
409
  for (const [name, worker] of Object.entries(resumeStore.workers)) {
378
410
  workerSessions.set(name, {
@@ -499,23 +531,70 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
499
531
  });
500
532
  },
501
533
  };
534
+ const piAgentDir = resolveConductorPiResourcePaths({
535
+ repoRoot: options.repoRoot,
536
+ config: ensembleConfig,
537
+ }).agentDir;
502
538
  const workers = profileWorkersToSessionSpecs(activeProfile, {
503
539
  defaultAcp: options.defaultAcp,
504
540
  spawnBase,
541
+ repoRoot: options.repoRoot,
542
+ issueNumber: issue.number,
543
+ ...(piAgentDir ? { piAgentDir } : {}),
544
+ onWarning: (message) => sessionLogger.emit({ type: 'harness.warning', message }),
505
545
  });
506
546
  if (workers.length > 0) {
507
547
  validateWorkerAcpPrerequisites(workers.map((worker) => resolveWorkerAcpSpawn({
508
548
  defaultOptions: options.defaultAcp,
509
549
  modelTag: worker.modelTag,
510
550
  })));
511
- finalizeSessionWorkerSpecs(workers, options.defaultAcp);
551
+ finalizeSessionWorkerSpecs(workers, options.repoRoot, issue.number, options.defaultAcp, piAgentDir);
512
552
  }
513
553
  const workerAcpFingerprints = new Map(workers.map((worker) => [worker.name, worker.acpFingerprint]));
514
554
  let teardownStarted = false;
515
555
  const workerWorktree = options.workerWorktree ??
516
556
  (workers.length > 0
517
- ? await resolveWorkerWorkspace(options.repoRoot, issue, options.workerWorktreeMode ?? 'isolated')
557
+ ? await resolveWorkerWorkspace(options.repoRoot, issue, options.workerWorktreeMode ?? 'isolated', {
558
+ resume: Boolean(options.resumeAgentId),
559
+ onNotice: reportResumeNotice,
560
+ })
518
561
  : undefined);
562
+ if (options.resumeAgentId) {
563
+ const currentWorkers = new Map(workers.map((worker) => [worker.name, worker]));
564
+ for (const [name, savedWorker] of workerSessions) {
565
+ if (!currentWorkers.has(name)) {
566
+ reportResumeNotice(`保存 session に worker「${name}」がありますが、現在の profile には定義がありません。この worker session は復元対象から外れます。`);
567
+ continue;
568
+ }
569
+ const currentWorker = currentWorkers.get(name);
570
+ const savedAcpSpawn = savedWorker.acpSpawn;
571
+ const currentAcpSpawn = currentWorker.acpFingerprint;
572
+ if (savedAcpSpawn &&
573
+ currentAcpSpawn &&
574
+ (savedAcpSpawn.preset !== currentAcpSpawn.preset ||
575
+ (savedAcpSpawn.preset === 'pi' &&
576
+ currentAcpSpawn.preset === 'pi' &&
577
+ (savedAcpSpawn.piRuntime ?? 'byo') !==
578
+ (currentAcpSpawn.piRuntime ?? 'byo')) ||
579
+ (savedAcpSpawn.preset === 'custom' &&
580
+ currentAcpSpawn.preset === 'custom' &&
581
+ (savedAcpSpawn.command !== currentAcpSpawn.command ||
582
+ JSON.stringify(savedAcpSpawn.args) !==
583
+ JSON.stringify(currentAcpSpawn.args))))) {
584
+ reportResumeNotice(`worker「${name}」の保存時 ACP preset/runtime と現在の設定が異なります。現在の設定で ACP session/load を試みます。`);
585
+ }
586
+ if (!savedWorker.acpCwd)
587
+ continue;
588
+ const currentCwd = currentWorker.resolvedWorkspacePath ?? workerWorktree?.path;
589
+ if (currentCwd &&
590
+ resolvePath(savedWorker.acpCwd) !== resolvePath(currentCwd)) {
591
+ reportResumeNotice(`worker「${name}」の保存時 ACP cwd ${savedWorker.acpCwd} と現在の workspace ${currentCwd} が異なります。現在の workspace で ACP session/load を試みます。以前の cwd にだけある local changes はコピーされず、Store に commit SHA がないため commit の差も照合できません。`);
592
+ if (!(await pathExists(savedWorker.acpCwd))) {
593
+ reportResumeNotice(`worker「${name}」の保存時 ACP cwd ${savedWorker.acpCwd} は見つかりません。そこにだけあった未コミット変更は復元できず、保存 Store に commit SHA がないため欠落 commit の有無も照合できません。`);
594
+ }
595
+ }
596
+ }
597
+ }
519
598
  if (workerWorktree) {
520
599
  sessionLogger.emit({
521
600
  type: 'harness.worktree',
@@ -627,6 +706,34 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
627
706
  eventQueue.enqueue({ type: 'worker.completed', result });
628
707
  scheduleSessionResumeStoreFlush();
629
708
  },
709
+ onWorkerSessionRecovered: async (notification) => {
710
+ workerSessions.set(notification.name, {
711
+ acpSessionId: notification.acpSessionId,
712
+ acpCwd: notification.acpCwd,
713
+ ...(workerAcpFingerprints.get(notification.name)
714
+ ? { acpSpawn: workerAcpFingerprints.get(notification.name) }
715
+ : {}),
716
+ });
717
+ try {
718
+ await flushSessionResumeStore();
719
+ }
720
+ catch (error) {
721
+ reportResumeStoreFlushFailure(error);
722
+ }
723
+ const detail = notification.reason === 'session_load_failed'
724
+ ? '保存済み ACP session を読み込めなかったため'
725
+ : '保存済み ACP thread の履歴を読めなかったため';
726
+ sessionLogger.emit({
727
+ type: 'harness.warning',
728
+ message: `worker.session.recovered name=${notification.name} kind=${notification.kind}: ${detail}新しい session で再開します。`,
729
+ });
730
+ eventQueue.enqueue({
731
+ type: 'worker.session.recovered',
732
+ name: notification.name,
733
+ kind: notification.kind,
734
+ reason: notification.reason,
735
+ });
736
+ },
630
737
  onWorkerFailed: (failure) => {
631
738
  sessionLogger.emit({ type: 'worker.failed', failure });
632
739
  eventQueue.enqueue({ type: 'worker.failed', failure });
@@ -820,22 +927,8 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
820
927
  repoRoot: options.repoRoot,
821
928
  conductorAgentId: conductorAgent.agentId,
822
929
  issueUrl: options.issueUrl,
823
- location: activeStoreLocation,
824
- }), statusLine, undefined, [
825
- ...(activeStoreLocation === 'canonical-slug'
826
- ? [
827
- sessionStateStorePath({
828
- repoRoot: options.repoRoot,
829
- conductorAgentId: conductorAgent.agentId,
830
- location: 'user-path-hash',
831
- }),
832
- ]
833
- : []),
834
- legacySessionStateStorePath({
835
- repoRoot: options.repoRoot,
836
- conductorAgentId: conductorAgent.agentId,
837
- }),
838
- ]));
930
+ location: 'canonical-slug',
931
+ }), statusLine, undefined));
839
932
  sessionStateWriteQueue = write.catch(() => { });
840
933
  return write;
841
934
  },
@@ -885,16 +978,19 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
885
978
  : {}),
886
979
  ...(conductorBackend === 'pi'
887
980
  ? {
981
+ issueUrl: options.issueUrl,
888
982
  pi: resolveConductorPiResourcePaths({
889
983
  repoRoot: options.repoRoot,
890
984
  config: ensembleConfig,
891
985
  }),
892
- sessionDir: resolveEnsemblePaths({
893
- repoRoot: options.repoRoot,
894
- }).piSessionsDir,
895
- legacySessionDir: resolveEnsemblePaths({
896
- repoRoot: options.repoRoot,
897
- }).legacyPiSessionsDirForCwd(conductorCwd),
986
+ ...(activeLocatedResumeStore
987
+ ? {
988
+ resumeCwds: [
989
+ activeLocatedResumeStore.resumeStore.conductorCwd,
990
+ activeLocatedResumeStore.resumeStore.repoRoot,
991
+ ].filter((cwd) => Boolean(cwd)),
992
+ }
993
+ : {}),
898
994
  }
899
995
  : {}),
900
996
  ...(Object.keys(mcpServers).length > 0 ? { mcpServers } : {}),
@@ -925,19 +1021,42 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
925
1021
  throw error;
926
1022
  }
927
1023
  startupCleanup.add(async () => {
928
- if (options.resumeAgentId && conductorAgent.closeForResume) {
929
- await conductorAgent.closeForResume();
930
- }
931
- else {
932
- await conductorAgent.close();
933
- }
1024
+ await closeConductorAgentOnce(conductorAgent, {
1025
+ forResume: Boolean(options.resumeAgentId),
1026
+ });
934
1027
  });
935
1028
  // Factories may not be abortable while pending. Register the returned agent
936
1029
  // before checking shutdown so a late success is still closed by the guard.
937
1030
  shutdownSignal.throwIfAborted();
1031
+ if (options.resumeAgentId) {
1032
+ const currentSessionStatePath = sessionStateStorePath({
1033
+ repoRoot: options.repoRoot,
1034
+ conductorAgentId: conductorAgent.agentId,
1035
+ issueUrl: options.issueUrl,
1036
+ location: 'canonical-slug',
1037
+ });
1038
+ try {
1039
+ const currentState = await loadSessionStateStore(currentSessionStatePath);
1040
+ if (!currentState &&
1041
+ activeLocatedResumeStore &&
1042
+ resolvePath(activeLocatedResumeStore.resumeStore.repoRoot) !==
1043
+ resolvePath(options.repoRoot)) {
1044
+ reportResumeNotice('保存時 clone に残る legacy session-state YAML は通常読み取り対象ではありません。以前の statusLine と履歴を復元する必要がある場合は、migration tool で canonical slug へ移してください。');
1045
+ }
1046
+ }
1047
+ catch (error) {
1048
+ const detail = error instanceof Error ? error.message : String(error);
1049
+ sessionLogger.emit({
1050
+ type: 'harness.warning',
1051
+ message: `Issue 保存先の session state を読み込めませんでした。 error=${detail}`,
1052
+ });
1053
+ reportResumeNotice(`Issue 保存先の session-state YAML を読み込めませんでした。原因: ${detail}`);
1054
+ }
1055
+ }
938
1056
  const conductorHandle = { conductor: conductorAgent };
939
- activeResumeStorePath ??= sessionResumeStorePath({
1057
+ activeResumeStorePath ??= canonicalSessionResumeStorePath({
940
1058
  repoRoot: options.repoRoot,
1059
+ issueUrl: options.issueUrl,
941
1060
  conductorAgentId: conductorAgent.agentId,
942
1061
  });
943
1062
  // Keep auth/transport reconnects on the same backend factory as the session.
@@ -945,26 +1064,49 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
945
1064
  conductorAgentFactory,
946
1065
  conductorOptions,
947
1066
  retrySuppressionSignal: shutdownIntentController.signal,
948
- onAuthReconnectAttempt: ({ agentId }) => {
1067
+ onAuthReconnectAttempt: ({ agentId, source, }) => {
949
1068
  sessionLogger.emit({
950
1069
  type: 'conductor.auth.reconnect',
951
1070
  agentId,
1071
+ status: 'attempt',
1072
+ ...(source ? { source } : {}),
952
1073
  });
953
1074
  },
954
- onTransportReconnectAttempt: ({ agentId }) => {
1075
+ onAuthReconnectComplete: ({ agentId, success, source, closeFailed, }) => {
1076
+ sessionLogger.emit({
1077
+ type: 'conductor.auth.reconnect',
1078
+ agentId,
1079
+ status: success ? 'succeeded' : 'failed',
1080
+ ...(source ? { source } : {}),
1081
+ });
1082
+ if (closeFailed) {
1083
+ sessionLogger.emit({
1084
+ type: 'harness.warning',
1085
+ message: '旧 conductor の終了処理に失敗したため、リソース解放は確認できません。再接続後の状態を確認してください。',
1086
+ });
1087
+ }
1088
+ },
1089
+ onTransportReconnectAttempt: ({ agentId, source, }) => {
955
1090
  sessionLogger.emit({
956
1091
  type: 'conductor.transport.reconnect',
957
1092
  agentId,
958
1093
  status: 'attempt',
1094
+ ...(source ? { source } : {}),
959
1095
  });
960
1096
  },
961
- onTransportReconnectComplete: ({ agentId, success, error, }) => {
1097
+ onTransportReconnectComplete: ({ agentId, success, source, closeFailed, }) => {
962
1098
  sessionLogger.emit({
963
1099
  type: 'conductor.transport.reconnect',
964
1100
  agentId,
965
1101
  status: success ? 'succeeded' : 'failed',
966
- ...(error ? { error } : {}),
1102
+ ...(source ? { source } : {}),
967
1103
  });
1104
+ if (closeFailed) {
1105
+ sessionLogger.emit({
1106
+ type: 'harness.warning',
1107
+ message: '旧 conductor の終了処理に失敗したため、リソース解放は確認できません。再接続後の状態を確認してください。',
1108
+ });
1109
+ }
968
1110
  },
969
1111
  };
970
1112
  let sessionResumeStoreWriteQueue = Promise.resolve();
@@ -992,14 +1134,23 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
992
1134
  (harnessPhase === 'running' || harnessPhase === 'idle'
993
1135
  ? 'unknown'
994
1136
  : undefined);
1137
+ const cursorBootstrapComplete = cursorBootstrapState
1138
+ ? cursorBootstrapCompleteForStore(cursorBootstrapState)
1139
+ : undefined;
995
1140
  return {
996
1141
  version: SESSION_RESUME_STORE_VERSION,
997
1142
  conductorAgentId: conductorHandle.conductor.agentId,
998
1143
  conductorBackend,
1144
+ ...(createdHarnessCliVersion !== undefined
1145
+ ? { createdHarnessCliVersion }
1146
+ : {}),
999
1147
  conductorCwd,
1000
1148
  ...(cursorBootstrapRunId
1001
1149
  ? { cursorBootstrapRunId }
1002
1150
  : {}),
1151
+ ...(cursorBootstrapComplete !== undefined
1152
+ ? { cursorBootstrapComplete }
1153
+ : {}),
1003
1154
  lazyWorkers: activeLazyWorkers,
1004
1155
  ...(harnessSocketPath ? { harnessSocketPath } : {}),
1005
1156
  issueUrl: options.issueUrl,
@@ -1033,7 +1184,7 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
1033
1184
  repoRoot: options.repoRoot,
1034
1185
  conductorAgentId: conductorHandle.conductor.agentId,
1035
1186
  issueUrl: options.issueUrl,
1036
- location: activeStoreLocation,
1187
+ location: 'canonical-slug',
1037
1188
  }), snapshot);
1038
1189
  };
1039
1190
  const persistSessionResumeStore = async (snapshot, lifecycle) => {
@@ -1121,6 +1272,7 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
1121
1272
  const toolCallHistoryPath = toolCallSnapshotHistoryPath({
1122
1273
  repoRoot: options.repoRoot,
1123
1274
  conductorAgentId: conductorHandle.conductor.agentId,
1275
+ issueUrl: options.issueUrl,
1124
1276
  });
1125
1277
  let toolCallHistoryFlushTimer;
1126
1278
  let toolCallHistoryFlushQueue = Promise.resolve();
@@ -1293,9 +1445,38 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
1293
1445
  let stopReason = 'completed';
1294
1446
  const continueOnConductorError = options.continueOnConductorError ?? false;
1295
1447
  const waitForOperatorExit = options.waitForOperatorExit ?? false;
1448
+ const operatorCanInspectRecovery = options.operatorCanInspectRecovery === true;
1296
1449
  let operatorRequestedExit = false;
1297
1450
  let operatorRequestedClose = false;
1298
1451
  let disposeOperatorInput;
1452
+ const resolveCursorBootstrapUnknown = (assumption) => {
1453
+ if (!operatorCanInspectRecovery ||
1454
+ !cursorBootstrapState ||
1455
+ cursorBootstrapState.status !== 'unknown') {
1456
+ return false;
1457
+ }
1458
+ cursorBootstrapState.status = 'saving';
1459
+ cursorBootstrapState.pendingAssumption = assumption;
1460
+ void flushSessionResumeStore()
1461
+ .then(() => {
1462
+ cursorBootstrapState.status =
1463
+ assumption === 'assume-complete' ? 'complete' : 'incomplete';
1464
+ delete cursorBootstrapState.pendingAssumption;
1465
+ sessionLogger.emit({
1466
+ type: 'harness.warning',
1467
+ message: assumption === 'assume-complete'
1468
+ ? 'operator が Cursor bootstrap を complete と仮定する判断を SessionResumeStore に保存しました。compiled prompt は追加送信しません。'
1469
+ : 'operator が Cursor bootstrap を incomplete と仮定する判断を SessionResumeStore に保存しました。次の新しい event に compiled prompt を添えて送ります。',
1470
+ });
1471
+ })
1472
+ .catch((error) => {
1473
+ cursorBootstrapState.status = 'unknown';
1474
+ delete cursorBootstrapState.pendingAssumption;
1475
+ reportResumeStoreFlushFailure(error);
1476
+ })
1477
+ .finally(() => dispatchLifecycle.wake());
1478
+ return true;
1479
+ };
1299
1480
  const onOperatorMessageQueued = (delivery) => {
1300
1481
  scheduleSessionResumeStoreFlush();
1301
1482
  if (delivery !== 'interrupt')
@@ -1347,23 +1528,33 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
1347
1528
  onOperatorMessageQueued(delivery);
1348
1529
  return true;
1349
1530
  };
1531
+ const requestOperatorShutdown = (mode) => {
1532
+ if (teardownStarted || operatorRequestedExit || operatorRequestedClose) {
1533
+ return false;
1534
+ }
1535
+ if (mode === 'exit') {
1536
+ sessionLogger.emit({ type: 'session.operator_exit' });
1537
+ workerSession.runtime.cancelAllActivePrompts();
1538
+ operatorRequestedExit = true;
1539
+ }
1540
+ else {
1541
+ sessionLogger.emit({ type: 'session.operator_close' });
1542
+ workerSession.runtime.cancelAllActivePrompts();
1543
+ operatorRequestedClose = true;
1544
+ }
1545
+ shutdownIntentController.abort();
1546
+ abortAfterOpenQuestionDrain(operatorTerminationController);
1547
+ return true;
1548
+ };
1350
1549
  if (options.bindOperatorInput) {
1351
1550
  const bindingDispose = options.bindOperatorInput({
1352
1551
  submit: (message, submitOptions) => {
1353
1552
  if (isOperatorExitCommand(message)) {
1354
- sessionLogger.emit({ type: 'session.operator_exit' });
1355
- workerSession.runtime.cancelAllActivePrompts();
1356
- operatorRequestedExit = true;
1357
- shutdownIntentController.abort();
1358
- abortAfterOpenQuestionDrain(operatorTerminationController);
1553
+ requestOperatorShutdown('exit');
1359
1554
  return false;
1360
1555
  }
1361
1556
  if (isOperatorCloseCommand(message)) {
1362
- sessionLogger.emit({ type: 'session.operator_close' });
1363
- workerSession.runtime.cancelAllActivePrompts();
1364
- operatorRequestedClose = true;
1365
- shutdownIntentController.abort();
1366
- abortAfterOpenQuestionDrain(operatorTerminationController);
1557
+ requestOperatorShutdown('close');
1367
1558
  return false;
1368
1559
  }
1369
1560
  if (isOperatorReconnectCommand(message)) {
@@ -1395,6 +1586,21 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
1395
1586
  }]
1396
1587
  : []),
1397
1588
  }),
1589
+ listRecoverySnapshots: () => recoveryInbox.list(),
1590
+ ...(operatorCanInspectRecovery
1591
+ ? {
1592
+ getRecoverySnapshot: (recoveryId) => recoveryInbox.get(recoveryId),
1593
+ }
1594
+ : {}),
1595
+ clearRecoverySnapshot: (recoveryId) => recoveryInbox.remove(recoveryId),
1596
+ continueAfterRecovery: () => dispatchLifecycle.confirmRecovery(),
1597
+ recoveryAttentionAttemptId: () => dispatchLifecycle.pendingAttemptId,
1598
+ ...(cursorBootstrapState && operatorCanInspectRecovery
1599
+ ? {
1600
+ getCursorBootstrapStatus: () => cursorBootstrapState.status,
1601
+ resolveCursorBootstrapUnknown,
1602
+ }
1603
+ : {}),
1398
1604
  });
1399
1605
  if (typeof bindingDispose === 'function') {
1400
1606
  disposeOperatorInput = bindingDispose;
@@ -1445,29 +1651,9 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
1445
1651
  repoRoot: options.repoRoot,
1446
1652
  conductorAgentId: conductorAgent.agentId,
1447
1653
  issueUrl: options.issueUrl,
1448
- location: activeStoreLocation,
1654
+ location: 'canonical-slug',
1449
1655
  });
1450
- const fallbackStatePaths = [
1451
- ...(activeStoreLocation === 'canonical-slug'
1452
- ? [
1453
- sessionStateStorePath({
1454
- repoRoot: options.repoRoot,
1455
- conductorAgentId: conductorAgent.agentId,
1456
- location: 'user-path-hash',
1457
- }),
1458
- ]
1459
- : []),
1460
- legacySessionStateStorePath({
1461
- repoRoot: options.repoRoot,
1462
- conductorAgentId: conductorAgent.agentId,
1463
- }),
1464
- ];
1465
- let sessionState = await loadSessionStateStore(currentStatePath);
1466
- for (const fallbackPath of fallbackStatePaths) {
1467
- if (sessionState)
1468
- break;
1469
- sessionState = await loadSessionStateStore(fallbackPath);
1470
- }
1656
+ const sessionState = await loadSessionStateStore(currentStatePath);
1471
1657
  return {
1472
1658
  ...openQuestions.snapshot(),
1473
1659
  ...(sessionState ? { sessionState } : {}),
@@ -1479,6 +1665,7 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
1479
1665
  }
1480
1666
  return sendOperatorMessage({ message: text, delivery });
1481
1667
  },
1668
+ requestOperatorShutdown: ({ mode }) => requestOperatorShutdown(mode),
1482
1669
  answerOpenQuestion: async ({ questionId, answer }) => {
1483
1670
  if (teardownStarted) {
1484
1671
  throw new OpenQuestionControlOperationError(-32000, 'The session is shutting down.');
@@ -1589,6 +1776,10 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
1589
1776
  conductorHandle,
1590
1777
  sendReconnect,
1591
1778
  eventQueue,
1779
+ recoveryInbox,
1780
+ recoveryBackend: conductorBackend,
1781
+ dispatchLifecycle,
1782
+ operatorCanInspectRecovery,
1592
1783
  dispatchHoldState,
1593
1784
  onDispatchHoldChanged,
1594
1785
  resetOutboundDispatchesThisSend: () => {
@@ -1635,6 +1826,7 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
1635
1826
  // permission.pending) must be dispatched without waiting for a new
1636
1827
  // session briefing.
1637
1828
  skipInitialSend: Boolean(options.resumeAgentId),
1829
+ ...(cursorBootstrapState ? { cursorBootstrapState } : {}),
1638
1830
  onSendStarted: (info) => {
1639
1831
  reportHarnessActivity('conductor', true);
1640
1832
  sessionLogger.emit({
@@ -1652,6 +1844,45 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
1652
1844
  });
1653
1845
  },
1654
1846
  onSendComplete: recordSendComplete,
1847
+ onRecoveryAttention: ({ attemptId, sourceKey, status, failureClass, confirmationRequired, pendingOperatorMessageCount, snapshot, agentAvailable, }) => {
1848
+ const recoveryAction = operatorCanInspectRecovery
1849
+ ? snapshot
1850
+ ? `入力を確認するには /recovery list の後に /recovery show ${snapshot.recoveryId} を実行してください。`
1851
+ : 'この send に operator-authored input は含まれていません。'
1852
+ : 'この実行環境では recovery 本文を確認できません。同一 process 内でも取得できず、process 終了時に破棄されます。';
1853
+ const queueNotice = pendingOperatorMessageCount > 0
1854
+ ? operatorCanInspectRecovery
1855
+ ? `未送信の operator message が ${pendingOperatorMessageCount} 件、既存 queue に残っています。`
1856
+ : `未送信の operator message ${pendingOperatorMessageCount} 件は既存 queue に残っていますが、非 TTY one-shot 終了時に破棄されます。必要なら状態を確認して再実行してください。`
1857
+ : undefined;
1858
+ const confirmationNotice = confirmationRequired
1859
+ ? operatorCanInspectRecovery
1860
+ ? '状態を確認してから必要な続きを新しい指示で送り、後続 dispatch を再開するには /recovery continue を実行してください。'
1861
+ : '失敗した入力は自動再送せず、後続 dispatch も停止しています。非 TTY では本文を確認できないため、状態を確かめてから安全な再開方法を判断してください。'
1862
+ : '明示的な interrupt 入力が queue に残っているため、その入力を次に処理します。/recovery continue は不要です。';
1863
+ sessionLogger.emit({
1864
+ type: 'harness.warning',
1865
+ message: [
1866
+ `conductor send が未完了です (source=${escapeTerminalControlCharacters(sourceKey)}, attempt=${attemptId}, status=${escapeTerminalControlCharacters(status)}, failure=${failureClass}).`,
1867
+ recoveryAction,
1868
+ queueNotice,
1869
+ agentAvailable
1870
+ ? undefined
1871
+ : 'agent は unavailable です。接続を直した後に /reconnect を実行してください。',
1872
+ confirmationNotice,
1873
+ ]
1874
+ .filter((part) => part !== undefined)
1875
+ .join(' '),
1876
+ });
1877
+ },
1878
+ onRecoveryCapacityBlocked: () => {
1879
+ sessionLogger.emit({
1880
+ type: 'harness.warning',
1881
+ message: operatorCanInspectRecovery
1882
+ ? 'recovery inbox の容量が上限に達しました。入力は queue に残して dispatch を待っています。/recovery clear <id> で空きを作るか、TTL 経過後に続行します。'
1883
+ : 'recovery inbox の容量が上限に達しました。入力を保持したまま dispatch を待っています。この実行環境では本文を確認できません。',
1884
+ });
1885
+ },
1655
1886
  onDispatchBatchSettled: ({ events, result, error }) => {
1656
1887
  for (const event of events) {
1657
1888
  if (event.type !== 'operator.message')
@@ -1692,6 +1923,16 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
1692
1923
  cursorBootstrapRunId = info.runId;
1693
1924
  scheduleSessionResumeStoreFlush();
1694
1925
  }
1926
+ const issueDispatchCompleted = info.status === 'finished' ||
1927
+ info.status === 'completed' ||
1928
+ info.status === 'running';
1929
+ if (conductorBackend === 'cursor' &&
1930
+ cursorBootstrapState?.status === 'incomplete' &&
1931
+ issueDispatchCompleted &&
1932
+ (info.dispatchSource === 'initial' || info.historyInbound !== undefined)) {
1933
+ cursorBootstrapState.status = 'complete';
1934
+ scheduleSessionResumeStoreFlush();
1935
+ }
1695
1936
  const responseText = info.result?.trim();
1696
1937
  if (info.status === 'finished' && responseText) {
1697
1938
  const at = Date.now();
@@ -1703,7 +1944,7 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
1703
1944
  if (info.dispatchSource !== 'initial') {
1704
1945
  conductorMessages.push({
1705
1946
  text: responseText,
1706
- inbound: info.inbound,
1947
+ inbound: info.historyInbound ?? info.inbound,
1707
1948
  at,
1708
1949
  sendCount: info.sendCount,
1709
1950
  ...(info.dispatchSource
@@ -1798,6 +2039,9 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
1798
2039
  }
1799
2040
  }
1800
2041
  finally {
2042
+ disposeOperatorInput?.();
2043
+ disposeOperatorInput = undefined;
2044
+ recoveryInbox.dispose();
1801
2045
  const lifecycle = operatorRequestedClose
1802
2046
  ? 'closed'
1803
2047
  : operatorRequestedExit
@@ -1857,7 +2101,6 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
1857
2101
  }
1858
2102
  teardownPhases.openQuestionControl = Date.now() - phaseStart;
1859
2103
  }
1860
- disposeOperatorInput?.();
1861
2104
  if (dispatchHoldReminderMonitor) {
1862
2105
  emitTeardownPhase('dispatchHoldReminderMonitor');
1863
2106
  dispatchHoldReminderMonitor.stop();
@@ -1873,11 +2116,11 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
1873
2116
  // after this point are rejected by the teardown guard above.
1874
2117
  workerSession.runtime.cancelAllActivePrompts();
1875
2118
  if (operatorRequestedClose) {
1876
- // A worker's ACP session exists before its first prompt completes, but
1877
- // workerSessions is normally updated by onWorkerCompleted. Snapshot
1878
- // attached sessions now so force shutdown cannot discard the only
1879
- // completion event that would have persisted a resumable session ID.
1880
- for (const attached of workerSession.runtime.listAttached()) {
2119
+ // A worker's ACP session exists before its first prompt completes, so
2120
+ // snapshot resumable attached sessions before force shutdown. Recovery
2121
+ // bootstrap replacements are excluded until initialized; their prior
2122
+ // workerSessions entry remains available for the next resume.
2123
+ for (const attached of workerSession.runtime.listAttachedForResume()) {
1881
2124
  workerSessions.set(attached.name, {
1882
2125
  acpSessionId: attached.session.sessionId,
1883
2126
  acpCwd: attached.session.acpCwd,
@@ -1936,12 +2179,9 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
1936
2179
  const runConductorClose = async () => {
1937
2180
  emitTeardownPhase('conductor');
1938
2181
  const phaseStart = Date.now();
1939
- if (operatorRequestedClose && conductorHandle.conductor.closeForResume) {
1940
- await conductorHandle.conductor.closeForResume();
1941
- }
1942
- else {
1943
- await conductorHandle.conductor.close();
1944
- }
2182
+ await closeConductorAgentOnce(conductorHandle.conductor, {
2183
+ forResume: operatorRequestedClose,
2184
+ });
1945
2185
  teardownPhases.conductor = Date.now() - phaseStart;
1946
2186
  };
1947
2187
  if (forceShutdown) {
@@ -2078,15 +2318,46 @@ function attachLegacySessionCallbacks(logger, options) {
2078
2318
  }
2079
2319
  });
2080
2320
  }
2081
- function finalizeSessionWorkerSpecs(workers, defaultAcp) {
2321
+ function finalizeSessionWorkerSpecs(workers, repoRoot, issueNumber, defaultAcp, piAgentDir) {
2322
+ const resolvedPiAgentDir = piAgentDir ??
2323
+ resolveConductorPiResourcePaths({
2324
+ repoRoot,
2325
+ config: defaultAcp?.config,
2326
+ env: defaultAcp?.env,
2327
+ }).agentDir;
2082
2328
  for (const worker of workers) {
2083
2329
  const resolved = resolveWorkerAcpSpawn({
2084
2330
  defaultOptions: defaultAcp,
2085
2331
  modelTag: worker.modelTag,
2086
2332
  });
2087
- const finalized = finalizeResolvedAcpSpawn(resolved);
2333
+ const sessionSpawn = resolved.piRuntime === 'builtin'
2334
+ ? configurePiWorkerRuntime(resolved, {
2335
+ repoRoot,
2336
+ issueNumber,
2337
+ workerName: worker.name,
2338
+ ...(resolvedPiAgentDir
2339
+ ? { agentDir: resolvedPiAgentDir }
2340
+ : {}),
2341
+ })
2342
+ : resolved;
2343
+ const finalized = finalizeResolvedAcpSpawn(sessionSpawn);
2088
2344
  worker.spawn = resolvedAcpSpawnToOptions(finalized, worker.spawn);
2089
2345
  worker.acpFingerprint = acpSpawnFingerprint(finalized);
2090
2346
  }
2091
2347
  }
2348
+ async function pathExists(path) {
2349
+ try {
2350
+ await stat(path);
2351
+ return true;
2352
+ }
2353
+ catch (error) {
2354
+ if (typeof error === 'object' &&
2355
+ error !== null &&
2356
+ 'code' in error &&
2357
+ error.code === 'ENOENT') {
2358
+ return false;
2359
+ }
2360
+ throw error;
2361
+ }
2362
+ }
2092
2363
  //# sourceMappingURL=conductor-session.js.map