@agents-ensemble/core 0.21.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 (266) 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 +2 -3
  43. package/dist/conductor/conductor-auth.d.ts.map +1 -1
  44. package/dist/conductor/conductor-auth.js +5 -2
  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 +6 -0
  51. package/dist/conductor/conductor-history.d.ts.map +1 -1
  52. package/dist/conductor/conductor-history.js +11 -6
  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 +27 -5
  67. package/dist/conductor/conductor-send-reconnect.d.ts.map +1 -1
  68. package/dist/conductor/conductor-send-reconnect.js +88 -39
  69. package/dist/conductor/conductor-send-reconnect.js.map +1 -1
  70. package/dist/conductor/conductor-session-driver.d.ts +32 -7
  71. package/dist/conductor/conductor-session-driver.d.ts.map +1 -1
  72. package/dist/conductor/conductor-session-driver.js +474 -233
  73. package/dist/conductor/conductor-session-driver.js.map +1 -1
  74. package/dist/conductor/conductor-session.d.ts +2 -0
  75. package/dist/conductor/conductor-session.d.ts.map +1 -1
  76. package/dist/conductor/conductor-session.js +404 -156
  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 +15 -6
  91. package/dist/conductor/index.d.ts.map +1 -1
  92. package/dist/conductor/index.js +8 -4
  93. package/dist/conductor/index.js.map +1 -1
  94. package/dist/conductor/operator-command.d.ts +1 -1
  95. package/dist/conductor/operator-command.js +1 -1
  96. package/dist/conductor/operator-command.js.map +1 -1
  97. package/dist/conductor/operator-input-binding.d.ts +12 -0
  98. package/dist/conductor/operator-input-binding.d.ts.map +1 -1
  99. package/dist/conductor/pi-auth-recovery-snapshot-store.d.ts +4 -1
  100. package/dist/conductor/pi-auth-recovery-snapshot-store.d.ts.map +1 -1
  101. package/dist/conductor/pi-auth-recovery-snapshot-store.js +4 -1
  102. package/dist/conductor/pi-auth-recovery-snapshot-store.js.map +1 -1
  103. package/dist/conductor/pi-conductor-agent.d.ts +6 -3
  104. package/dist/conductor/pi-conductor-agent.d.ts.map +1 -1
  105. package/dist/conductor/pi-conductor-agent.js +272 -53
  106. package/dist/conductor/pi-conductor-agent.js.map +1 -1
  107. package/dist/conductor/pi-conductor-session-file.d.ts +13 -0
  108. package/dist/conductor/pi-conductor-session-file.d.ts.map +1 -0
  109. package/dist/conductor/pi-conductor-session-file.js +21 -0
  110. package/dist/conductor/pi-conductor-session-file.js.map +1 -0
  111. package/dist/conductor/pi-resource-loader.d.ts +6 -0
  112. package/dist/conductor/pi-resource-loader.d.ts.map +1 -1
  113. package/dist/conductor/pi-resource-loader.js +26 -12
  114. package/dist/conductor/pi-resource-loader.js.map +1 -1
  115. package/dist/conductor/pi-user-root-lock.d.ts +8 -0
  116. package/dist/conductor/pi-user-root-lock.d.ts.map +1 -0
  117. package/dist/conductor/pi-user-root-lock.js +53 -0
  118. package/dist/conductor/pi-user-root-lock.js.map +1 -0
  119. package/dist/conductor/recovery-inbox-command.d.ts +29 -0
  120. package/dist/conductor/recovery-inbox-command.d.ts.map +1 -0
  121. package/dist/conductor/recovery-inbox-command.js +248 -0
  122. package/dist/conductor/recovery-inbox-command.js.map +1 -0
  123. package/dist/conductor/session/events/session-event.d.ts +15 -2
  124. package/dist/conductor/session/events/session-event.d.ts.map +1 -1
  125. package/dist/conductor/session/events/session-log-event-groups.d.ts +1 -1
  126. package/dist/conductor/session/events/session-log-event-groups.d.ts.map +1 -1
  127. package/dist/conductor/session/events/session-log-event-groups.js +1 -0
  128. package/dist/conductor/session/events/session-log-event-groups.js.map +1 -1
  129. package/dist/conductor/session/events/session-log-event.d.ts +4 -0
  130. package/dist/conductor/session/events/session-log-event.d.ts.map +1 -1
  131. package/dist/conductor/session/format-session-event.js +25 -4
  132. package/dist/conductor/session/format-session-event.js.map +1 -1
  133. package/dist/conductor/session/select-dispatch-batch.d.ts +2 -0
  134. package/dist/conductor/session/select-dispatch-batch.d.ts.map +1 -1
  135. package/dist/conductor/session/select-dispatch-batch.js +31 -2
  136. package/dist/conductor/session/select-dispatch-batch.js.map +1 -1
  137. package/dist/conductor/session/session-event-queue.d.ts +5 -0
  138. package/dist/conductor/session/session-event-queue.d.ts.map +1 -1
  139. package/dist/conductor/session/session-event-queue.js +41 -4
  140. package/dist/conductor/session/session-event-queue.js.map +1 -1
  141. package/dist/conductor/session/session-event.d.ts +1 -1
  142. package/dist/conductor/session/session-event.d.ts.map +1 -1
  143. package/dist/conductor/session-policy.d.ts.map +1 -1
  144. package/dist/conductor/session-policy.js +4 -2
  145. package/dist/conductor/session-policy.js.map +1 -1
  146. package/dist/conductor/submit-operator-input.d.ts +3 -0
  147. package/dist/conductor/submit-operator-input.d.ts.map +1 -1
  148. package/dist/conductor/submit-operator-input.js +21 -0
  149. package/dist/conductor/submit-operator-input.js.map +1 -1
  150. package/dist/config/deep-merge.d.ts.map +1 -1
  151. package/dist/config/deep-merge.js +13 -3
  152. package/dist/config/deep-merge.js.map +1 -1
  153. package/dist/config/defaults.d.ts.map +1 -1
  154. package/dist/config/defaults.js +1 -1
  155. package/dist/config/defaults.js.map +1 -1
  156. package/dist/config/doctor-settings.d.ts +59 -0
  157. package/dist/config/doctor-settings.d.ts.map +1 -0
  158. package/dist/config/doctor-settings.js +343 -0
  159. package/dist/config/doctor-settings.js.map +1 -0
  160. package/dist/config/ensemble-config-digest.d.ts +2 -4
  161. package/dist/config/ensemble-config-digest.d.ts.map +1 -1
  162. package/dist/config/ensemble-config-digest.js +2 -4
  163. package/dist/config/ensemble-config-digest.js.map +1 -1
  164. package/dist/config/index.d.ts +3 -1
  165. package/dist/config/index.d.ts.map +1 -1
  166. package/dist/config/index.js +1 -0
  167. package/dist/config/index.js.map +1 -1
  168. package/dist/config/load-ensemble-config.d.ts.map +1 -1
  169. package/dist/config/load-ensemble-config.js +8 -1
  170. package/dist/config/load-ensemble-config.js.map +1 -1
  171. package/dist/config/parse-config.d.ts.map +1 -1
  172. package/dist/config/parse-config.js +34 -14
  173. package/dist/config/parse-config.js.map +1 -1
  174. package/dist/config/resolve-settings.d.ts +3 -1
  175. package/dist/config/resolve-settings.d.ts.map +1 -1
  176. package/dist/config/resolve-settings.js +7 -4
  177. package/dist/config/resolve-settings.js.map +1 -1
  178. package/dist/config/types.d.ts +14 -5
  179. package/dist/config/types.d.ts.map +1 -1
  180. package/dist/dispatch/attach-worker.js +1 -1
  181. package/dist/dispatch/attach-worker.js.map +1 -1
  182. package/dist/dispatch/worker-acp-session.d.ts +21 -3
  183. package/dist/dispatch/worker-acp-session.d.ts.map +1 -1
  184. package/dist/dispatch/worker-acp-session.js +50 -13
  185. package/dist/dispatch/worker-acp-session.js.map +1 -1
  186. package/dist/ensemble-paths.d.ts +19 -19
  187. package/dist/ensemble-paths.d.ts.map +1 -1
  188. package/dist/ensemble-paths.js +40 -22
  189. package/dist/ensemble-paths.js.map +1 -1
  190. package/dist/issue/resolve-issue-url.d.ts +4 -0
  191. package/dist/issue/resolve-issue-url.d.ts.map +1 -1
  192. package/dist/issue/resolve-issue-url.js +2 -2
  193. package/dist/issue/resolve-issue-url.js.map +1 -1
  194. package/dist/operator-runtime.d.ts +17 -4
  195. package/dist/operator-runtime.d.ts.map +1 -1
  196. package/dist/operator-runtime.js +12 -3
  197. package/dist/operator-runtime.js.map +1 -1
  198. package/dist/profile/types.d.ts +4 -0
  199. package/dist/profile/types.d.ts.map +1 -1
  200. package/dist/profile/types.js +29 -12
  201. package/dist/profile/types.js.map +1 -1
  202. package/dist/representation/escape-terminal-control-characters.d.ts +9 -0
  203. package/dist/representation/escape-terminal-control-characters.d.ts.map +1 -0
  204. package/dist/representation/escape-terminal-control-characters.js +34 -0
  205. package/dist/representation/escape-terminal-control-characters.js.map +1 -0
  206. package/dist/representation/index.d.ts +1 -0
  207. package/dist/representation/index.d.ts.map +1 -1
  208. package/dist/representation/index.js +1 -0
  209. package/dist/representation/index.js.map +1 -1
  210. package/dist/runtime/types.d.ts +9 -0
  211. package/dist/runtime/types.d.ts.map +1 -1
  212. package/dist/runtime/worker-runtime.d.ts +14 -1
  213. package/dist/runtime/worker-runtime.d.ts.map +1 -1
  214. package/dist/runtime/worker-runtime.js +197 -50
  215. package/dist/runtime/worker-runtime.js.map +1 -1
  216. package/dist/runtime/worker-session.d.ts +2 -1
  217. package/dist/runtime/worker-session.d.ts.map +1 -1
  218. package/dist/runtime/worker-session.js +1 -0
  219. package/dist/runtime/worker-session.js.map +1 -1
  220. package/dist/session/export-session-bundle.d.ts +77 -0
  221. package/dist/session/export-session-bundle.d.ts.map +1 -0
  222. package/dist/session/export-session-bundle.js +640 -0
  223. package/dist/session/export-session-bundle.js.map +1 -0
  224. package/dist/session/index.d.ts +4 -3
  225. package/dist/session/index.d.ts.map +1 -1
  226. package/dist/session/index.js +2 -1
  227. package/dist/session/index.js.map +1 -1
  228. package/dist/session/open-question-control.d.ts +9 -0
  229. package/dist/session/open-question-control.d.ts.map +1 -1
  230. package/dist/session/open-question-control.js +26 -1
  231. package/dist/session/open-question-control.js.map +1 -1
  232. package/dist/session/open-questions-store.d.ts +1 -1
  233. package/dist/session/open-questions-store.d.ts.map +1 -1
  234. package/dist/session/open-questions-store.js +3 -6
  235. package/dist/session/open-questions-store.js.map +1 -1
  236. package/dist/session/persisted-file.d.ts +5 -0
  237. package/dist/session/persisted-file.d.ts.map +1 -0
  238. package/dist/session/persisted-file.js +56 -0
  239. package/dist/session/persisted-file.js.map +1 -0
  240. package/dist/session/session-resume-store.d.ts +77 -10
  241. package/dist/session/session-resume-store.d.ts.map +1 -1
  242. package/dist/session/session-resume-store.js +346 -83
  243. package/dist/session/session-resume-store.js.map +1 -1
  244. package/dist/session/session-snapshot-paths.d.ts +7 -1
  245. package/dist/session/session-snapshot-paths.d.ts.map +1 -1
  246. package/dist/session/session-snapshot-paths.js +29 -26
  247. package/dist/session/session-snapshot-paths.js.map +1 -1
  248. package/dist/session/session-state-store.d.ts +2 -2
  249. package/dist/session/session-state-store.d.ts.map +1 -1
  250. package/dist/session/session-state-store.js +5 -16
  251. package/dist/session/session-state-store.js.map +1 -1
  252. package/dist/session/tool-call-snapshot-history.d.ts +12 -1
  253. package/dist/session/tool-call-snapshot-history.d.ts.map +1 -1
  254. package/dist/session/tool-call-snapshot-history.js +75 -70
  255. package/dist/session/tool-call-snapshot-history.js.map +1 -1
  256. package/dist/session/worker-session-history.d.ts +13 -1
  257. package/dist/session/worker-session-history.d.ts.map +1 -1
  258. package/dist/session/worker-session-history.js +171 -50
  259. package/dist/session/worker-session-history.js.map +1 -1
  260. package/dist/workspace/index.d.ts +1 -1
  261. package/dist/workspace/index.d.ts.map +1 -1
  262. package/dist/worktree/worktree.d.ts +6 -1
  263. package/dist/worktree/worktree.d.ts.map +1 -1
  264. package/dist/worktree/worktree.js +123 -2
  265. package/dist/worktree/worktree.js.map +1 -1
  266. 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,26 +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';
53
- import { PiAuthRecoverySnapshotStore } from './pi-auth-recovery-snapshot-store.js';
60
+ import { ConductorDispatchLifecycle } from './conductor-dispatch-lifecycle.js';
61
+ import { ConductorRecoveryInbox } from './conductor-recovery-inbox.js';
54
62
  import { enqueueOperatorMessage as enqueueOperatorMessageEvent, submitOperatorInput, } from './submit-operator-input.js';
55
63
  import { createGitHubMonitor, } from '../github/github-monitor.js';
56
64
  import { resolveConductorBackendSetting, resolveConductorPiResourcePaths, resolveGitHubMonitorEnabled, } from '../config/resolve-settings.js';
@@ -64,7 +72,6 @@ import { createRegisterGitHubWatchTool } from '../github/register-github-watch-t
64
72
  import { createUnregisterGitHubWatchTool } from '../github/unregister-github-watch-tool.js';
65
73
  import { compileConductorSystemPrompt } from '../prompt/compile-system-prompt.js';
66
74
  import { loadUserInstructions } from '../prompt/load-user-instructions.js';
67
- import { resolveEnsemblePaths } from '../ensemble-paths.js';
68
75
  const OPEN_QUESTION_CONTROL_DRAIN_TIMEOUT_MS = 5_000;
69
76
  const RECORDED_ANSWER_UNKNOWN_DISPATCH = {
70
77
  outcome: 'unknown',
@@ -169,7 +176,7 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
169
176
  });
170
177
  });
171
178
  attachLegacySessionCallbacks(sessionLogger, options);
172
- const expiredHistory = await pruneExpiredToolCallSnapshotHistoryFiles(options.repoRoot);
179
+ const expiredHistory = await pruneExpiredToolCallSnapshotHistoryFiles({ repoRoot: options.repoRoot, issueUrl: options.issueUrl });
173
180
  if (expiredHistory.failedCount > 0) {
174
181
  sessionLogger.emit({
175
182
  type: 'harness.warning',
@@ -186,8 +193,16 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
186
193
  let lastConductorMessage = null;
187
194
  const conductorMessages = [];
188
195
  const eventQueue = new SessionEventQueue();
189
- const piAuthRecoverySnapshotStore = new PiAuthRecoverySnapshotStore();
190
- startupCleanup.add(() => piAuthRecoverySnapshotStore.dispose());
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());
191
206
  const openQuestionDispatchReceipts = new WeakMap();
192
207
  const pendingOpenQuestionDispatchReceipts = new Set();
193
208
  const dispatchHoldState = createDispatchHoldState();
@@ -210,12 +225,13 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
210
225
  let githubMonitorCursor;
211
226
  let githubMonitor;
212
227
  let activeResumeStorePath;
228
+ let activeLocatedResumeStore;
213
229
  let createdHarnessCliVersion = options.cliPackageVersion;
214
- let activeStoreLocation = 'user-path-hash';
215
230
  let drainStartupResumeStoreWrites = async () => { };
216
231
  const pendingGitHubRecoveries = [];
217
232
  const conductorCwd = options.conductorCwd ?? process.cwd();
218
233
  let cursorBootstrapRunId;
234
+ const cursorBootstrapState = conductorBackend === 'cursor' ? { status: 'incomplete' } : undefined;
219
235
  const mcpServers = await resolveMcpServers(options.repoRoot, options.mcpConfigOptions);
220
236
  const mcpConfigDigest = computeMcpConfigDigest(mcpServers);
221
237
  const mcpConfigConfigured = Object.keys(mcpServers).length > 0;
@@ -225,12 +241,13 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
225
241
  const locatedResumeStore = await requireLocatedSessionResumeStoreForResume({
226
242
  repoRoot: options.repoRoot,
227
243
  conductorAgentId: options.resumeAgentId,
244
+ issueUrl: options.issueUrl,
228
245
  });
229
246
  const loadedResumeStore = locatedResumeStore.resumeStore;
247
+ activeLocatedResumeStore = locatedResumeStore;
230
248
  // A resume keeps its origin version; a legacy store stays without one.
231
249
  createdHarnessCliVersion = loadedResumeStore.createdHarnessCliVersion;
232
250
  activeResumeStorePath = locatedResumeStore.path;
233
- activeStoreLocation = locatedResumeStore.location;
234
251
  const savedConfigDigestScheme = loadedResumeStore.ensembleConfigDigestScheme;
235
252
  const effectiveConfigDigestScheme = savedConfigDigestScheme ?? 'full';
236
253
  const currentEnsembleConfigDigest = effectiveConfigDigestScheme === 'resume-feasibility'
@@ -239,17 +256,39 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
239
256
  assertSessionResumeStoreMatches(loadedResumeStore, {
240
257
  conductorAgentId: options.resumeAgentId,
241
258
  issueUrl: options.issueUrl,
242
- repoRoot: options.repoRoot,
243
- conductorBackend,
244
- mcpConfigDigest,
245
- mcpConfigConfigured,
246
- ensembleConfigDigest: currentEnsembleConfigDigest,
247
259
  });
248
- if (loadedResumeStore.ensembleConfigDigest === undefined) {
249
- sessionLogger.emit({
250
- type: 'harness.warning',
251
- message: 'SessionResumeStore has no ensemble config digest; config consistency cannot be verified for this legacy session.',
252
- });
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 を使います。');
253
292
  }
254
293
  const hasComparableLegacyFullDigest = effectiveConfigDigestScheme === 'full' &&
255
294
  loadedResumeStore.ensembleConfigDigest !== undefined;
@@ -276,6 +315,35 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
276
315
  // acquired resource is released successfully; otherwise keep this record
277
316
  // marked unknown/running so it does not claim a possibly active session is
278
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
+ }
279
347
  const resumeStore = {
280
348
  ...loadedResumeStore,
281
349
  lifecycle: 'unknown',
@@ -284,7 +352,6 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
284
352
  harnessSocketPath: undefined,
285
353
  updatedAt: Date.now(),
286
354
  };
287
- cursorBootstrapRunId = loadedResumeStore.cursorBootstrapRunId;
288
355
  harnessPhase = 'running';
289
356
  harnessModifiedAt = resumeStore.modifiedAt;
290
357
  let resumeStoreActivationCommitted = false;
@@ -306,6 +373,7 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
306
373
  const history = await loadToolCallSnapshotHistory({
307
374
  repoRoot: options.repoRoot,
308
375
  conductorAgentId: options.resumeAgentId,
376
+ issueUrl: options.issueUrl,
309
377
  });
310
378
  resumeToolCallSnapshots = history.entries;
311
379
  toolCallHistoryNeedsRewrite = history.needsRewrite;
@@ -315,47 +383,8 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
315
383
  message: `${history.warning}; the session will continue without affected history`,
316
384
  });
317
385
  }
318
- const openQuestionsPath = openQuestionsStorePath({
319
- repoRoot: options.repoRoot,
320
- conductorAgentId: options.resumeAgentId,
321
- issueUrl: options.issueUrl,
322
- location: activeStoreLocation,
323
- });
386
+ const openQuestionsPath = sessionSnapshotPaths(options.repoRoot, locatedResumeStore, 'open-questions')[0];
324
387
  let openQuestionsSnapshot = await loadOpenQuestionsStore(openQuestionsPath);
325
- if (!openQuestionsSnapshot) {
326
- const fallbackPaths = [
327
- ...(activeStoreLocation === 'canonical-slug'
328
- ? [
329
- openQuestionsStorePath({
330
- repoRoot: options.repoRoot,
331
- conductorAgentId: options.resumeAgentId,
332
- location: 'user-path-hash',
333
- }),
334
- ]
335
- : []),
336
- legacyOpenQuestionsStorePath({
337
- repoRoot: options.repoRoot,
338
- conductorAgentId: options.resumeAgentId,
339
- }),
340
- ];
341
- for (const fallbackPath of fallbackPaths) {
342
- openQuestionsSnapshot = await loadOpenQuestionsStore(fallbackPath);
343
- if (openQuestionsSnapshot)
344
- break;
345
- }
346
- if (openQuestionsSnapshot) {
347
- try {
348
- await saveOpenQuestionsStore(openQuestionsPath, openQuestionsSnapshot);
349
- }
350
- catch (error) {
351
- const detail = error instanceof Error ? error.message : String(error);
352
- sessionLogger.emit({
353
- type: 'harness.warning',
354
- message: `旧保存先の open question を user 保存先へ移行できませんでした。今回のセッションでは旧 YAML を読み込みます。 error=${detail}`,
355
- });
356
- }
357
- }
358
- }
359
388
  if (openQuestionsSnapshot) {
360
389
  openQuestions.restore(openQuestionsSnapshot);
361
390
  }
@@ -376,9 +405,6 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
376
405
  });
377
406
  }
378
407
  }
379
- activeProfile = resumeStore.profile;
380
- // ResumeStores written before this setting always started every worker eagerly.
381
- activeLazyWorkers = resumeStore.lazyWorkers ?? false;
382
408
  githubMonitorCursor = resumeStore.githubMonitor;
383
409
  for (const [name, worker] of Object.entries(resumeStore.workers)) {
384
410
  workerSessions.set(name, {
@@ -505,23 +531,70 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
505
531
  });
506
532
  },
507
533
  };
534
+ const piAgentDir = resolveConductorPiResourcePaths({
535
+ repoRoot: options.repoRoot,
536
+ config: ensembleConfig,
537
+ }).agentDir;
508
538
  const workers = profileWorkersToSessionSpecs(activeProfile, {
509
539
  defaultAcp: options.defaultAcp,
510
540
  spawnBase,
541
+ repoRoot: options.repoRoot,
542
+ issueNumber: issue.number,
543
+ ...(piAgentDir ? { piAgentDir } : {}),
544
+ onWarning: (message) => sessionLogger.emit({ type: 'harness.warning', message }),
511
545
  });
512
546
  if (workers.length > 0) {
513
547
  validateWorkerAcpPrerequisites(workers.map((worker) => resolveWorkerAcpSpawn({
514
548
  defaultOptions: options.defaultAcp,
515
549
  modelTag: worker.modelTag,
516
550
  })));
517
- finalizeSessionWorkerSpecs(workers, options.defaultAcp);
551
+ finalizeSessionWorkerSpecs(workers, options.repoRoot, issue.number, options.defaultAcp, piAgentDir);
518
552
  }
519
553
  const workerAcpFingerprints = new Map(workers.map((worker) => [worker.name, worker.acpFingerprint]));
520
554
  let teardownStarted = false;
521
555
  const workerWorktree = options.workerWorktree ??
522
556
  (workers.length > 0
523
- ? 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
+ })
524
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
+ }
525
598
  if (workerWorktree) {
526
599
  sessionLogger.emit({
527
600
  type: 'harness.worktree',
@@ -633,6 +706,34 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
633
706
  eventQueue.enqueue({ type: 'worker.completed', result });
634
707
  scheduleSessionResumeStoreFlush();
635
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
+ },
636
737
  onWorkerFailed: (failure) => {
637
738
  sessionLogger.emit({ type: 'worker.failed', failure });
638
739
  eventQueue.enqueue({ type: 'worker.failed', failure });
@@ -826,22 +927,8 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
826
927
  repoRoot: options.repoRoot,
827
928
  conductorAgentId: conductorAgent.agentId,
828
929
  issueUrl: options.issueUrl,
829
- location: activeStoreLocation,
830
- }), statusLine, undefined, [
831
- ...(activeStoreLocation === 'canonical-slug'
832
- ? [
833
- sessionStateStorePath({
834
- repoRoot: options.repoRoot,
835
- conductorAgentId: conductorAgent.agentId,
836
- location: 'user-path-hash',
837
- }),
838
- ]
839
- : []),
840
- legacySessionStateStorePath({
841
- repoRoot: options.repoRoot,
842
- conductorAgentId: conductorAgent.agentId,
843
- }),
844
- ]));
930
+ location: 'canonical-slug',
931
+ }), statusLine, undefined));
845
932
  sessionStateWriteQueue = write.catch(() => { });
846
933
  return write;
847
934
  },
@@ -891,16 +978,19 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
891
978
  : {}),
892
979
  ...(conductorBackend === 'pi'
893
980
  ? {
981
+ issueUrl: options.issueUrl,
894
982
  pi: resolveConductorPiResourcePaths({
895
983
  repoRoot: options.repoRoot,
896
984
  config: ensembleConfig,
897
985
  }),
898
- sessionDir: resolveEnsemblePaths({
899
- repoRoot: options.repoRoot,
900
- }).piSessionsDir,
901
- legacySessionDir: resolveEnsemblePaths({
902
- repoRoot: options.repoRoot,
903
- }).legacyPiSessionsDirForCwd(conductorCwd),
986
+ ...(activeLocatedResumeStore
987
+ ? {
988
+ resumeCwds: [
989
+ activeLocatedResumeStore.resumeStore.conductorCwd,
990
+ activeLocatedResumeStore.resumeStore.repoRoot,
991
+ ].filter((cwd) => Boolean(cwd)),
992
+ }
993
+ : {}),
904
994
  }
905
995
  : {}),
906
996
  ...(Object.keys(mcpServers).length > 0 ? { mcpServers } : {}),
@@ -931,19 +1021,42 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
931
1021
  throw error;
932
1022
  }
933
1023
  startupCleanup.add(async () => {
934
- if (options.resumeAgentId && conductorAgent.closeForResume) {
935
- await conductorAgent.closeForResume();
936
- }
937
- else {
938
- await conductorAgent.close();
939
- }
1024
+ await closeConductorAgentOnce(conductorAgent, {
1025
+ forResume: Boolean(options.resumeAgentId),
1026
+ });
940
1027
  });
941
1028
  // Factories may not be abortable while pending. Register the returned agent
942
1029
  // before checking shutdown so a late success is still closed by the guard.
943
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
+ }
944
1056
  const conductorHandle = { conductor: conductorAgent };
945
- activeResumeStorePath ??= sessionResumeStorePath({
1057
+ activeResumeStorePath ??= canonicalSessionResumeStorePath({
946
1058
  repoRoot: options.repoRoot,
1059
+ issueUrl: options.issueUrl,
947
1060
  conductorAgentId: conductorAgent.agentId,
948
1061
  });
949
1062
  // Keep auth/transport reconnects on the same backend factory as the session.
@@ -951,26 +1064,49 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
951
1064
  conductorAgentFactory,
952
1065
  conductorOptions,
953
1066
  retrySuppressionSignal: shutdownIntentController.signal,
954
- onAuthReconnectAttempt: ({ agentId }) => {
1067
+ onAuthReconnectAttempt: ({ agentId, source, }) => {
1068
+ sessionLogger.emit({
1069
+ type: 'conductor.auth.reconnect',
1070
+ agentId,
1071
+ status: 'attempt',
1072
+ ...(source ? { source } : {}),
1073
+ });
1074
+ },
1075
+ onAuthReconnectComplete: ({ agentId, success, source, closeFailed, }) => {
955
1076
  sessionLogger.emit({
956
1077
  type: 'conductor.auth.reconnect',
957
1078
  agentId,
1079
+ status: success ? 'succeeded' : 'failed',
1080
+ ...(source ? { source } : {}),
958
1081
  });
1082
+ if (closeFailed) {
1083
+ sessionLogger.emit({
1084
+ type: 'harness.warning',
1085
+ message: '旧 conductor の終了処理に失敗したため、リソース解放は確認できません。再接続後の状態を確認してください。',
1086
+ });
1087
+ }
959
1088
  },
960
- onTransportReconnectAttempt: ({ agentId }) => {
1089
+ onTransportReconnectAttempt: ({ agentId, source, }) => {
961
1090
  sessionLogger.emit({
962
1091
  type: 'conductor.transport.reconnect',
963
1092
  agentId,
964
1093
  status: 'attempt',
1094
+ ...(source ? { source } : {}),
965
1095
  });
966
1096
  },
967
- onTransportReconnectComplete: ({ agentId, success, error, }) => {
1097
+ onTransportReconnectComplete: ({ agentId, success, source, closeFailed, }) => {
968
1098
  sessionLogger.emit({
969
1099
  type: 'conductor.transport.reconnect',
970
1100
  agentId,
971
1101
  status: success ? 'succeeded' : 'failed',
972
- ...(error ? { error } : {}),
1102
+ ...(source ? { source } : {}),
973
1103
  });
1104
+ if (closeFailed) {
1105
+ sessionLogger.emit({
1106
+ type: 'harness.warning',
1107
+ message: '旧 conductor の終了処理に失敗したため、リソース解放は確認できません。再接続後の状態を確認してください。',
1108
+ });
1109
+ }
974
1110
  },
975
1111
  };
976
1112
  let sessionResumeStoreWriteQueue = Promise.resolve();
@@ -998,6 +1134,9 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
998
1134
  (harnessPhase === 'running' || harnessPhase === 'idle'
999
1135
  ? 'unknown'
1000
1136
  : undefined);
1137
+ const cursorBootstrapComplete = cursorBootstrapState
1138
+ ? cursorBootstrapCompleteForStore(cursorBootstrapState)
1139
+ : undefined;
1001
1140
  return {
1002
1141
  version: SESSION_RESUME_STORE_VERSION,
1003
1142
  conductorAgentId: conductorHandle.conductor.agentId,
@@ -1009,6 +1148,9 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
1009
1148
  ...(cursorBootstrapRunId
1010
1149
  ? { cursorBootstrapRunId }
1011
1150
  : {}),
1151
+ ...(cursorBootstrapComplete !== undefined
1152
+ ? { cursorBootstrapComplete }
1153
+ : {}),
1012
1154
  lazyWorkers: activeLazyWorkers,
1013
1155
  ...(harnessSocketPath ? { harnessSocketPath } : {}),
1014
1156
  issueUrl: options.issueUrl,
@@ -1042,7 +1184,7 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
1042
1184
  repoRoot: options.repoRoot,
1043
1185
  conductorAgentId: conductorHandle.conductor.agentId,
1044
1186
  issueUrl: options.issueUrl,
1045
- location: activeStoreLocation,
1187
+ location: 'canonical-slug',
1046
1188
  }), snapshot);
1047
1189
  };
1048
1190
  const persistSessionResumeStore = async (snapshot, lifecycle) => {
@@ -1130,6 +1272,7 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
1130
1272
  const toolCallHistoryPath = toolCallSnapshotHistoryPath({
1131
1273
  repoRoot: options.repoRoot,
1132
1274
  conductorAgentId: conductorHandle.conductor.agentId,
1275
+ issueUrl: options.issueUrl,
1133
1276
  });
1134
1277
  let toolCallHistoryFlushTimer;
1135
1278
  let toolCallHistoryFlushQueue = Promise.resolve();
@@ -1302,9 +1445,38 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
1302
1445
  let stopReason = 'completed';
1303
1446
  const continueOnConductorError = options.continueOnConductorError ?? false;
1304
1447
  const waitForOperatorExit = options.waitForOperatorExit ?? false;
1448
+ const operatorCanInspectRecovery = options.operatorCanInspectRecovery === true;
1305
1449
  let operatorRequestedExit = false;
1306
1450
  let operatorRequestedClose = false;
1307
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
+ };
1308
1480
  const onOperatorMessageQueued = (delivery) => {
1309
1481
  scheduleSessionResumeStoreFlush();
1310
1482
  if (delivery !== 'interrupt')
@@ -1356,23 +1528,33 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
1356
1528
  onOperatorMessageQueued(delivery);
1357
1529
  return true;
1358
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
+ };
1359
1549
  if (options.bindOperatorInput) {
1360
1550
  const bindingDispose = options.bindOperatorInput({
1361
1551
  submit: (message, submitOptions) => {
1362
1552
  if (isOperatorExitCommand(message)) {
1363
- sessionLogger.emit({ type: 'session.operator_exit' });
1364
- workerSession.runtime.cancelAllActivePrompts();
1365
- operatorRequestedExit = true;
1366
- shutdownIntentController.abort();
1367
- abortAfterOpenQuestionDrain(operatorTerminationController);
1553
+ requestOperatorShutdown('exit');
1368
1554
  return false;
1369
1555
  }
1370
1556
  if (isOperatorCloseCommand(message)) {
1371
- sessionLogger.emit({ type: 'session.operator_close' });
1372
- workerSession.runtime.cancelAllActivePrompts();
1373
- operatorRequestedClose = true;
1374
- shutdownIntentController.abort();
1375
- abortAfterOpenQuestionDrain(operatorTerminationController);
1557
+ requestOperatorShutdown('close');
1376
1558
  return false;
1377
1559
  }
1378
1560
  if (isOperatorReconnectCommand(message)) {
@@ -1404,9 +1586,21 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
1404
1586
  }]
1405
1587
  : []),
1406
1588
  }),
1407
- listPiAuthRecoverySnapshots: () => piAuthRecoverySnapshotStore.list(),
1408
- getPiAuthRecoverySnapshot: (recoveryId) => piAuthRecoverySnapshotStore.get(recoveryId),
1409
- clearPiAuthRecoverySnapshot: (recoveryId) => piAuthRecoverySnapshotStore.remove(recoveryId),
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
+ : {}),
1410
1604
  });
1411
1605
  if (typeof bindingDispose === 'function') {
1412
1606
  disposeOperatorInput = bindingDispose;
@@ -1457,29 +1651,9 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
1457
1651
  repoRoot: options.repoRoot,
1458
1652
  conductorAgentId: conductorAgent.agentId,
1459
1653
  issueUrl: options.issueUrl,
1460
- location: activeStoreLocation,
1654
+ location: 'canonical-slug',
1461
1655
  });
1462
- const fallbackStatePaths = [
1463
- ...(activeStoreLocation === 'canonical-slug'
1464
- ? [
1465
- sessionStateStorePath({
1466
- repoRoot: options.repoRoot,
1467
- conductorAgentId: conductorAgent.agentId,
1468
- location: 'user-path-hash',
1469
- }),
1470
- ]
1471
- : []),
1472
- legacySessionStateStorePath({
1473
- repoRoot: options.repoRoot,
1474
- conductorAgentId: conductorAgent.agentId,
1475
- }),
1476
- ];
1477
- let sessionState = await loadSessionStateStore(currentStatePath);
1478
- for (const fallbackPath of fallbackStatePaths) {
1479
- if (sessionState)
1480
- break;
1481
- sessionState = await loadSessionStateStore(fallbackPath);
1482
- }
1656
+ const sessionState = await loadSessionStateStore(currentStatePath);
1483
1657
  return {
1484
1658
  ...openQuestions.snapshot(),
1485
1659
  ...(sessionState ? { sessionState } : {}),
@@ -1491,6 +1665,7 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
1491
1665
  }
1492
1666
  return sendOperatorMessage({ message: text, delivery });
1493
1667
  },
1668
+ requestOperatorShutdown: ({ mode }) => requestOperatorShutdown(mode),
1494
1669
  answerOpenQuestion: async ({ questionId, answer }) => {
1495
1670
  if (teardownStarted) {
1496
1671
  throw new OpenQuestionControlOperationError(-32000, 'The session is shutting down.');
@@ -1601,7 +1776,10 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
1601
1776
  conductorHandle,
1602
1777
  sendReconnect,
1603
1778
  eventQueue,
1604
- piAuthRecoverySnapshotStore,
1779
+ recoveryInbox,
1780
+ recoveryBackend: conductorBackend,
1781
+ dispatchLifecycle,
1782
+ operatorCanInspectRecovery,
1605
1783
  dispatchHoldState,
1606
1784
  onDispatchHoldChanged,
1607
1785
  resetOutboundDispatchesThisSend: () => {
@@ -1648,6 +1826,7 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
1648
1826
  // permission.pending) must be dispatched without waiting for a new
1649
1827
  // session briefing.
1650
1828
  skipInitialSend: Boolean(options.resumeAgentId),
1829
+ ...(cursorBootstrapState ? { cursorBootstrapState } : {}),
1651
1830
  onSendStarted: (info) => {
1652
1831
  reportHarnessActivity('conductor', true);
1653
1832
  sessionLogger.emit({
@@ -1665,12 +1844,43 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
1665
1844
  });
1666
1845
  },
1667
1846
  onSendComplete: recordSendComplete,
1668
- onPiAuthRecoverySnapshotStored: ({ snapshot, evictedRecoveryIds }) => {
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 は不要です。';
1669
1863
  sessionLogger.emit({
1670
1864
  type: 'harness.warning',
1671
- message: evictedRecoveryIds.length > 0
1672
- ? `Pi recovery inbox reached its four-dispatch limit; evicted ${evictedRecoveryIds.join(', ')}. Snapshot ${snapshot.recoveryId} remains available for local TTY inspection until ${new Date(snapshot.expiresAt).toISOString()}.`
1673
- : `Pi authentication failure snapshot ${snapshot.recoveryId} is available for local TTY inspection until ${new Date(snapshot.expiresAt).toISOString()}. Use /recovery list, then /recovery show <id>.`,
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 を待っています。この実行環境では本文を確認できません。',
1674
1884
  });
1675
1885
  },
1676
1886
  onDispatchBatchSettled: ({ events, result, error }) => {
@@ -1713,6 +1923,16 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
1713
1923
  cursorBootstrapRunId = info.runId;
1714
1924
  scheduleSessionResumeStoreFlush();
1715
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
+ }
1716
1936
  const responseText = info.result?.trim();
1717
1937
  if (info.status === 'finished' && responseText) {
1718
1938
  const at = Date.now();
@@ -1724,7 +1944,7 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
1724
1944
  if (info.dispatchSource !== 'initial') {
1725
1945
  conductorMessages.push({
1726
1946
  text: responseText,
1727
- inbound: info.inbound,
1947
+ inbound: info.historyInbound ?? info.inbound,
1728
1948
  at,
1729
1949
  sendCount: info.sendCount,
1730
1950
  ...(info.dispatchSource
@@ -1821,7 +2041,7 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
1821
2041
  finally {
1822
2042
  disposeOperatorInput?.();
1823
2043
  disposeOperatorInput = undefined;
1824
- piAuthRecoverySnapshotStore.dispose();
2044
+ recoveryInbox.dispose();
1825
2045
  const lifecycle = operatorRequestedClose
1826
2046
  ? 'closed'
1827
2047
  : operatorRequestedExit
@@ -1896,11 +2116,11 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
1896
2116
  // after this point are rejected by the teardown guard above.
1897
2117
  workerSession.runtime.cancelAllActivePrompts();
1898
2118
  if (operatorRequestedClose) {
1899
- // A worker's ACP session exists before its first prompt completes, but
1900
- // workerSessions is normally updated by onWorkerCompleted. Snapshot
1901
- // attached sessions now so force shutdown cannot discard the only
1902
- // completion event that would have persisted a resumable session ID.
1903
- 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()) {
1904
2124
  workerSessions.set(attached.name, {
1905
2125
  acpSessionId: attached.session.sessionId,
1906
2126
  acpCwd: attached.session.acpCwd,
@@ -1959,12 +2179,9 @@ async function runConductorSessionWithStartupGuard(options, startupCleanup) {
1959
2179
  const runConductorClose = async () => {
1960
2180
  emitTeardownPhase('conductor');
1961
2181
  const phaseStart = Date.now();
1962
- if (operatorRequestedClose && conductorHandle.conductor.closeForResume) {
1963
- await conductorHandle.conductor.closeForResume();
1964
- }
1965
- else {
1966
- await conductorHandle.conductor.close();
1967
- }
2182
+ await closeConductorAgentOnce(conductorHandle.conductor, {
2183
+ forResume: operatorRequestedClose,
2184
+ });
1968
2185
  teardownPhases.conductor = Date.now() - phaseStart;
1969
2186
  };
1970
2187
  if (forceShutdown) {
@@ -2101,15 +2318,46 @@ function attachLegacySessionCallbacks(logger, options) {
2101
2318
  }
2102
2319
  });
2103
2320
  }
2104
- 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;
2105
2328
  for (const worker of workers) {
2106
2329
  const resolved = resolveWorkerAcpSpawn({
2107
2330
  defaultOptions: defaultAcp,
2108
2331
  modelTag: worker.modelTag,
2109
2332
  });
2110
- 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);
2111
2344
  worker.spawn = resolvedAcpSpawnToOptions(finalized, worker.spawn);
2112
2345
  worker.acpFingerprint = acpSpawnFingerprint(finalized);
2113
2346
  }
2114
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
+ }
2115
2363
  //# sourceMappingURL=conductor-session.js.map