cli-jaw 2.2.16 → 2.2.17

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 (411) hide show
  1. package/README.md +10 -1
  2. package/dist/bin/commands/_shared/dashboard-client.js +42 -0
  3. package/dist/bin/commands/_shared/dashboard-client.js.map +1 -0
  4. package/dist/bin/commands/_shared/search-format.js +40 -0
  5. package/dist/bin/commands/_shared/search-format.js.map +1 -0
  6. package/dist/bin/commands/chat-search.js +5 -3
  7. package/dist/bin/commands/chat-search.js.map +1 -1
  8. package/dist/bin/commands/dashboard-chat.js +14 -31
  9. package/dist/bin/commands/dashboard-chat.js.map +1 -1
  10. package/dist/bin/commands/dashboard-memory.js +12 -49
  11. package/dist/bin/commands/dashboard-memory.js.map +1 -1
  12. package/dist/bin/commands/doctor.js +33 -10
  13. package/dist/bin/commands/doctor.js.map +1 -1
  14. package/dist/bin/commands/init.js +13 -1
  15. package/dist/bin/commands/init.js.map +1 -1
  16. package/dist/bin/commands/memory.js +5 -1
  17. package/dist/bin/commands/memory.js.map +1 -1
  18. package/dist/bin/commands/slack.js +57 -3
  19. package/dist/bin/commands/slack.js.map +1 -1
  20. package/dist/bin/commands/tui/input-handler.js +1 -1
  21. package/dist/bin/commands/tui/input-handler.js.map +1 -1
  22. package/dist/bin/commands/tui/slash-command-runner.js +4 -5
  23. package/dist/bin/commands/tui/slash-command-runner.js.map +1 -1
  24. package/dist/bin/postinstall.js +16 -18
  25. package/dist/bin/postinstall.js.map +1 -1
  26. package/dist/lib/media-kind.js +28 -0
  27. package/dist/lib/media-kind.js.map +1 -0
  28. package/dist/lib/upload.js +27 -10
  29. package/dist/lib/upload.js.map +1 -1
  30. package/dist/server.js +35 -51
  31. package/dist/server.js.map +1 -1
  32. package/dist/src/agent/args.js +34 -0
  33. package/dist/src/agent/args.js.map +1 -1
  34. package/dist/src/agent/codex-app-client.js +1016 -135
  35. package/dist/src/agent/codex-app-client.js.map +1 -1
  36. package/dist/src/agent/codex-app-events.js +72 -1
  37. package/dist/src/agent/codex-app-events.js.map +1 -1
  38. package/dist/src/agent/codex-host-pool.js +617 -0
  39. package/dist/src/agent/codex-host-pool.js.map +1 -0
  40. package/dist/src/agent/jwc-runtime.js +12 -4
  41. package/dist/src/agent/jwc-runtime.js.map +1 -1
  42. package/dist/src/agent/kiro-auth.js +10 -4
  43. package/dist/src/agent/kiro-auth.js.map +1 -1
  44. package/dist/src/agent/kiro-runtime.js +45 -8
  45. package/dist/src/agent/kiro-runtime.js.map +1 -1
  46. package/dist/src/agent/lifecycle-handler.js +75 -52
  47. package/dist/src/agent/lifecycle-handler.js.map +1 -1
  48. package/dist/src/agent/memory-flush-controller.js +241 -33
  49. package/dist/src/agent/memory-flush-controller.js.map +1 -1
  50. package/dist/src/agent/prompt-context.js +4 -0
  51. package/dist/src/agent/prompt-context.js.map +1 -1
  52. package/dist/src/agent/runtime-pool.js +64 -34
  53. package/dist/src/agent/runtime-pool.js.map +1 -1
  54. package/dist/src/agent/session-persistence.js +71 -15
  55. package/dist/src/agent/session-persistence.js.map +1 -1
  56. package/dist/src/agent/spawn/queue.js +319 -162
  57. package/dist/src/agent/spawn/queue.js.map +1 -1
  58. package/dist/src/agent/spawn.js +581 -268
  59. package/dist/src/agent/spawn.js.map +1 -1
  60. package/dist/src/bgtask/types.js +1 -1
  61. package/dist/src/bgtask/types.js.map +1 -1
  62. package/dist/src/browser/adaptive-fetch/safety.js +25 -0
  63. package/dist/src/browser/adaptive-fetch/safety.js.map +1 -1
  64. package/dist/src/browser/connection.js +2 -8
  65. package/dist/src/browser/connection.js.map +1 -1
  66. package/dist/src/cli/capability-probe-worker.js +172 -0
  67. package/dist/src/cli/capability-probe-worker.js.map +1 -0
  68. package/dist/src/cli/capability-probe.js +83 -0
  69. package/dist/src/cli/capability-probe.js.map +1 -0
  70. package/dist/src/cli/claude-models.js +2 -2
  71. package/dist/src/cli/claude-models.js.map +1 -1
  72. package/dist/src/cli/cli-status-worker.js +296 -0
  73. package/dist/src/cli/cli-status-worker.js.map +1 -0
  74. package/dist/src/cli/cli-status.js +81 -0
  75. package/dist/src/cli/cli-status.js.map +1 -0
  76. package/dist/src/cli/command-context.js +17 -5
  77. package/dist/src/cli/command-context.js.map +1 -1
  78. package/dist/src/cli/compact.js +56 -19
  79. package/dist/src/cli/compact.js.map +1 -1
  80. package/dist/src/cli/handlers-runtime.js +6 -4
  81. package/dist/src/cli/handlers-runtime.js.map +1 -1
  82. package/dist/src/cli/handlers.js +7 -2
  83. package/dist/src/cli/handlers.js.map +1 -1
  84. package/dist/src/cli/opencodex-models.js +5 -32
  85. package/dist/src/cli/opencodex-models.js.map +1 -1
  86. package/dist/src/cli/opencodex-runtime.js +82 -0
  87. package/dist/src/cli/opencodex-runtime.js.map +1 -0
  88. package/dist/src/cli/readiness.js +35 -18
  89. package/dist/src/cli/readiness.js.map +1 -1
  90. package/dist/src/cli/registry-live.js +8 -2
  91. package/dist/src/cli/registry-live.js.map +1 -1
  92. package/dist/src/cli/registry.js +1 -1
  93. package/dist/src/cli/registry.js.map +1 -1
  94. package/dist/src/core/browser-open-default.js +6 -4
  95. package/dist/src/core/browser-open-default.js.map +1 -1
  96. package/dist/src/core/browser-open.js +11 -15
  97. package/dist/src/core/browser-open.js.map +1 -1
  98. package/dist/src/core/bus.js +7 -1
  99. package/dist/src/core/bus.js.map +1 -1
  100. package/dist/src/core/chat-sessions.js +121 -12
  101. package/dist/src/core/chat-sessions.js.map +1 -1
  102. package/dist/src/core/cli-detect.js +148 -4
  103. package/dist/src/core/cli-detect.js.map +1 -1
  104. package/dist/src/core/codex-config.js +21 -0
  105. package/dist/src/core/codex-config.js.map +1 -1
  106. package/dist/src/core/compact.js +73 -21
  107. package/dist/src/core/compact.js.map +1 -1
  108. package/dist/src/core/config.js +449 -20
  109. package/dist/src/core/config.js.map +1 -1
  110. package/dist/src/core/db.js +225 -0
  111. package/dist/src/core/db.js.map +1 -1
  112. package/dist/src/core/event-bus.js +49 -4
  113. package/dist/src/core/event-bus.js.map +1 -1
  114. package/dist/src/core/event-scope.js +100 -0
  115. package/dist/src/core/event-scope.js.map +1 -0
  116. package/dist/src/core/main-session.js +64 -21
  117. package/dist/src/core/main-session.js.map +1 -1
  118. package/dist/src/core/message-summary.js +12 -2
  119. package/dist/src/core/message-summary.js.map +1 -1
  120. package/dist/src/core/platform-kind.js +139 -0
  121. package/dist/src/core/platform-kind.js.map +1 -0
  122. package/dist/src/core/rate-limit.js +156 -0
  123. package/dist/src/core/rate-limit.js.map +1 -0
  124. package/dist/src/core/runtime-settings.js +165 -17
  125. package/dist/src/core/runtime-settings.js.map +1 -1
  126. package/dist/src/core/session-context.js +7 -0
  127. package/dist/src/core/session-context.js.map +1 -0
  128. package/dist/src/core/session-ops.js +53 -9
  129. package/dist/src/core/session-ops.js.map +1 -1
  130. package/dist/src/core/settings-merge.js +105 -3
  131. package/dist/src/core/settings-merge.js.map +1 -1
  132. package/dist/src/core/settings-watch.js +29 -5
  133. package/dist/src/core/settings-watch.js.map +1 -1
  134. package/dist/src/core/widget-watcher.js +15 -2
  135. package/dist/src/core/widget-watcher.js.map +1 -1
  136. package/dist/src/discord/bot.js +5 -2
  137. package/dist/src/discord/bot.js.map +1 -1
  138. package/dist/src/discord/commands.js +4 -4
  139. package/dist/src/discord/commands.js.map +1 -1
  140. package/dist/src/goal/heartbeat.js +1 -1
  141. package/dist/src/goal/heartbeat.js.map +1 -1
  142. package/dist/src/goal/runtime.js +1 -1
  143. package/dist/src/goal/runtime.js.map +1 -1
  144. package/dist/src/goal-run/controller.js +2 -2
  145. package/dist/src/goal-run/controller.js.map +1 -1
  146. package/dist/src/manager/internal-fetch.js +15 -0
  147. package/dist/src/manager/internal-fetch.js.map +1 -0
  148. package/dist/src/manager/logs.js +2 -1
  149. package/dist/src/manager/logs.js.map +1 -1
  150. package/dist/src/manager/memory/chat-federation.js +104 -11
  151. package/dist/src/manager/memory/chat-federation.js.map +1 -1
  152. package/dist/src/manager/memory/federation.js +100 -0
  153. package/dist/src/manager/memory/federation.js.map +1 -1
  154. package/dist/src/manager/metadata.js +4 -1
  155. package/dist/src/manager/metadata.js.map +1 -1
  156. package/dist/src/manager/notes/assets.js +1 -1
  157. package/dist/src/manager/notes/assets.js.map +1 -1
  158. package/dist/src/manager/notes/frontmatter.js +5 -49
  159. package/dist/src/manager/notes/frontmatter.js.map +1 -1
  160. package/dist/src/manager/notes/link-resolver.js +2 -2
  161. package/dist/src/manager/notes/link-resolver.js.map +1 -1
  162. package/dist/src/manager/notes/remote-assets.js +1 -1
  163. package/dist/src/manager/notes/remote-assets.js.map +1 -1
  164. package/dist/src/manager/notes/routes.js +2 -2
  165. package/dist/src/manager/notes/routes.js.map +1 -1
  166. package/dist/src/manager/notes/store.js +2 -2
  167. package/dist/src/manager/notes/store.js.map +1 -1
  168. package/dist/src/manager/notes/trash.js +1 -1
  169. package/dist/src/manager/notes/trash.js.map +1 -1
  170. package/dist/src/manager/notes/vault-index.js +2 -2
  171. package/dist/src/manager/notes/vault-index.js.map +1 -1
  172. package/dist/src/manager/notes/wiki-links.js +1 -1
  173. package/dist/src/manager/notes/wiki-links.js.map +1 -1
  174. package/dist/src/manager/notes/wiki-routes.js +84 -0
  175. package/dist/src/manager/notes/wiki-routes.js.map +1 -0
  176. package/dist/src/manager/proxy.js +1 -0
  177. package/dist/src/manager/proxy.js.map +1 -1
  178. package/dist/src/manager/routes/dashboard-memory.js +24 -1
  179. package/dist/src/manager/routes/dashboard-memory.js.map +1 -1
  180. package/dist/src/manager/routes/runtime-monitor.js +1 -1
  181. package/dist/src/manager/routes/runtime-monitor.js.map +1 -1
  182. package/dist/src/manager/scan.js +5 -3
  183. package/dist/src/manager/scan.js.map +1 -1
  184. package/dist/src/manager/server.js +20 -6
  185. package/dist/src/manager/server.js.map +1 -1
  186. package/dist/src/manager/telegram-hub/hub-bot.js +4 -3
  187. package/dist/src/manager/telegram-hub/hub-bot.js.map +1 -1
  188. package/dist/src/manager/worker-events.js +2 -1
  189. package/dist/src/manager/worker-events.js.map +1 -1
  190. package/dist/src/memory/heartbeat.js +6 -5
  191. package/dist/src/memory/heartbeat.js.map +1 -1
  192. package/dist/src/memory/indexing.js +162 -45
  193. package/dist/src/memory/indexing.js.map +1 -1
  194. package/dist/src/messaging/channel-validate.js +14 -1
  195. package/dist/src/messaging/channel-validate.js.map +1 -1
  196. package/dist/src/messaging/redact.js +2 -1
  197. package/dist/src/messaging/redact.js.map +1 -1
  198. package/dist/src/messaging/session-key.js +21 -2
  199. package/dist/src/messaging/session-key.js.map +1 -1
  200. package/dist/src/notes/constants.js.map +1 -0
  201. package/dist/src/notes/frontmatter.js +59 -0
  202. package/dist/src/notes/frontmatter.js.map +1 -0
  203. package/dist/src/notes/path-guards.js.map +1 -0
  204. package/dist/src/notes/search.js.map +1 -0
  205. package/dist/src/orchestrator/attestation.js +1 -1
  206. package/dist/src/orchestrator/attestation.js.map +1 -1
  207. package/dist/src/orchestrator/gateway.js +135 -31
  208. package/dist/src/orchestrator/gateway.js.map +1 -1
  209. package/dist/src/orchestrator/pipeline.js +53 -21
  210. package/dist/src/orchestrator/pipeline.js.map +1 -1
  211. package/dist/src/orchestrator/scope.js +36 -2
  212. package/dist/src/orchestrator/scope.js.map +1 -1
  213. package/dist/src/orchestrator/session-lanes.js +87 -0
  214. package/dist/src/orchestrator/session-lanes.js.map +1 -0
  215. package/dist/src/orchestrator/session-work.js +25 -0
  216. package/dist/src/orchestrator/session-work.js.map +1 -0
  217. package/dist/src/orchestrator/state-machine.js +1 -5
  218. package/dist/src/orchestrator/state-machine.js.map +1 -1
  219. package/dist/src/orchestrator/worker-registry.js +20 -10
  220. package/dist/src/orchestrator/worker-registry.js.map +1 -1
  221. package/dist/src/prompt/builder.js +40 -2
  222. package/dist/src/prompt/builder.js.map +1 -1
  223. package/dist/src/prompt/templates/a1-system.md +8 -1
  224. package/dist/src/routes/agent-control.js +44 -8
  225. package/dist/src/routes/agent-control.js.map +1 -1
  226. package/dist/src/routes/chat-sessions.js +34 -6
  227. package/dist/src/routes/chat-sessions.js.map +1 -1
  228. package/dist/src/routes/command.js +63 -8
  229. package/dist/src/routes/command.js.map +1 -1
  230. package/dist/src/routes/events.js +11 -2
  231. package/dist/src/routes/events.js.map +1 -1
  232. package/dist/src/routes/link-preview.js +1 -27
  233. package/dist/src/routes/link-preview.js.map +1 -1
  234. package/dist/src/routes/messages.js +6 -5
  235. package/dist/src/routes/messages.js.map +1 -1
  236. package/dist/src/routes/messaging.js +45 -1
  237. package/dist/src/routes/messaging.js.map +1 -1
  238. package/dist/src/routes/orchestrate.js +53 -33
  239. package/dist/src/routes/orchestrate.js.map +1 -1
  240. package/dist/src/routes/search.js +84 -0
  241. package/dist/src/routes/search.js.map +1 -0
  242. package/dist/src/routes/session-request.js +29 -0
  243. package/dist/src/routes/session-request.js.map +1 -0
  244. package/dist/src/routes/settings.js +110 -3
  245. package/dist/src/routes/settings.js.map +1 -1
  246. package/dist/src/routes/static.js +30 -8
  247. package/dist/src/routes/static.js.map +1 -1
  248. package/dist/src/routes/system.js +1 -1
  249. package/dist/src/routes/system.js.map +1 -1
  250. package/dist/src/routes/wiki.js +122 -0
  251. package/dist/src/routes/wiki.js.map +1 -0
  252. package/dist/src/search/contract.js +2 -0
  253. package/dist/src/search/contract.js.map +1 -0
  254. package/dist/src/search/coordinator.js +153 -0
  255. package/dist/src/search/coordinator.js.map +1 -0
  256. package/dist/src/search/provider.js +40 -0
  257. package/dist/src/search/provider.js.map +1 -0
  258. package/dist/src/search/providers/chat.js +115 -0
  259. package/dist/src/search/providers/chat.js.map +1 -0
  260. package/dist/src/search/providers/memory.js +53 -0
  261. package/dist/src/search/providers/memory.js.map +1 -0
  262. package/dist/src/search/providers/wiki.js +80 -0
  263. package/dist/src/search/providers/wiki.js.map +1 -0
  264. package/dist/src/slack/api.js +10 -1
  265. package/dist/src/slack/api.js.map +1 -1
  266. package/dist/src/slack/attachment-recovery.js +42 -0
  267. package/dist/src/slack/attachment-recovery.js.map +1 -0
  268. package/dist/src/slack/bot.js +120 -41
  269. package/dist/src/slack/bot.js.map +1 -1
  270. package/dist/src/slack/commands.js +24 -8
  271. package/dist/src/slack/commands.js.map +1 -1
  272. package/dist/src/slack/events.js +12 -1
  273. package/dist/src/slack/events.js.map +1 -1
  274. package/dist/src/slack/history.js +108 -0
  275. package/dist/src/slack/history.js.map +1 -0
  276. package/dist/src/slack/inbound-file.js +242 -0
  277. package/dist/src/slack/inbound-file.js.map +1 -0
  278. package/dist/src/slack/inbound-url.js +39 -0
  279. package/dist/src/slack/inbound-url.js.map +1 -0
  280. package/dist/src/slack/ingress.js +140 -0
  281. package/dist/src/slack/ingress.js.map +1 -0
  282. package/dist/src/slack/manifest.js +3 -0
  283. package/dist/src/slack/manifest.js.map +1 -1
  284. package/dist/src/slack/socket.js +1 -1
  285. package/dist/src/slack/socket.js.map +1 -1
  286. package/dist/src/slack/thread-tracker.js +86 -0
  287. package/dist/src/slack/thread-tracker.js.map +1 -0
  288. package/dist/src/telegram/bot.js +21 -11
  289. package/dist/src/telegram/bot.js.map +1 -1
  290. package/dist/src/wiki/config.js +193 -0
  291. package/dist/src/wiki/config.js.map +1 -0
  292. package/dist/src/wiki/entities.js +61 -0
  293. package/dist/src/wiki/entities.js.map +1 -0
  294. package/dist/src/wiki/ontology.js +72 -0
  295. package/dist/src/wiki/ontology.js.map +1 -0
  296. package/dist/src/wiki/prompt.js +172 -0
  297. package/dist/src/wiki/prompt.js.map +1 -0
  298. package/dist/src/wiki/safe-traversal.js +198 -0
  299. package/dist/src/wiki/safe-traversal.js.map +1 -0
  300. package/dist/src/wiki/scaffold.js +77 -0
  301. package/dist/src/wiki/scaffold.js.map +1 -0
  302. package/package.json +1 -1
  303. package/public/css/session-hub.css +266 -0
  304. package/public/dist/assets/AdvancedExport-DYGcCAUq.js +1 -0
  305. package/public/dist/assets/Agent-CNNQ7KkJ.js +1 -0
  306. package/public/dist/assets/{DesignPanel-BGMPhZSx.js → DesignPanel-DcL369nB.js} +1 -1
  307. package/public/dist/assets/{DocPanel-Dz-J_l5M.js → DocPanel-K8A2pm_1.js} +1 -1
  308. package/public/dist/assets/{FolderPanel-DbPhzzvi.js → FolderPanel-CV52EzwG.js} +1 -1
  309. package/public/dist/assets/{MilkdownWysiwygEditor-Em7iC6y9.js → MilkdownWysiwygEditor-BKOCDI4Z.js} +1 -1
  310. package/public/dist/assets/api-B25zNNFJ.js +1 -0
  311. package/public/dist/assets/{app-DX4e6isX.css → app-DjgGeXhJ.css} +1 -1
  312. package/public/dist/assets/app-Dsgp4xpU.js +55 -0
  313. package/public/dist/assets/bgtask-badge-C6h5C-o4.js +1 -0
  314. package/public/dist/assets/{bgtask-badge-qMkKOZx0.js → bgtask-badge-CpavV0Jc.js} +4 -4
  315. package/public/dist/assets/{employees-BnobWM_2.js → employees-BJyEgim7.js} +4 -4
  316. package/public/dist/assets/idb-cache-B5PX63p7.js +1 -0
  317. package/public/dist/assets/{idb-cache-BzzftUET.js → idb-cache-lOntwU3S.js} +1 -1
  318. package/public/dist/assets/{iframe-renderer-C5vRbbl9.js → iframe-renderer-B8k3AvTv.js} +12 -12
  319. package/public/dist/assets/iframe-renderer-BO_GgCTR.js +1 -0
  320. package/public/dist/assets/manager-LkGs6qEJ.css +1 -0
  321. package/public/dist/assets/manager-kXYQyMhR.js +13 -0
  322. package/public/dist/assets/{memory-DF4kPwwC.js → memory-D2RbBVO-.js} +11 -11
  323. package/public/dist/assets/memory-amsvg4Ey.js +1 -0
  324. package/public/dist/assets/message-history-BGHZxvgV.js +1 -0
  325. package/public/dist/assets/message-history-Bw2E_b8L.js +1 -0
  326. package/public/dist/assets/pi-settings-5HVg1sXJ.js +1 -0
  327. package/public/dist/assets/render-BkGcpH7z.js +157 -0
  328. package/public/dist/assets/settings-B1FPORO2.js +56 -0
  329. package/public/dist/assets/settings-CMN-zN4Q.js +1 -0
  330. package/public/dist/assets/settings-core-B_OjGYVN.js +1 -0
  331. package/public/dist/assets/settings-core-CxRwqx86.js +144 -0
  332. package/public/dist/assets/sidebar-DW78DANx.js +49 -0
  333. package/public/dist/assets/skills-Dz-9Yrxr.js +1 -0
  334. package/public/dist/assets/{skills-IGquen_j.js → skills-lWi0ZFiV.js} +6 -6
  335. package/public/dist/assets/{slash-commands-ZRDOL99E.js → slash-commands-DYOI6nKj.js} +1 -1
  336. package/public/dist/assets/slash-commands-xJFCdTTM.js +1 -0
  337. package/public/dist/assets/{trace-drawer-D8To8fYt.js → trace-drawer-DBX9xzmt.js} +1 -1
  338. package/public/dist/assets/{ui-Hx_mNoDf.js → ui-Bx7eqnXt.js} +1 -1
  339. package/public/dist/assets/ui-JOcrYeG-.js +1 -0
  340. package/public/dist/assets/{wiki-link-suggestions-DZQc78gj.js → wiki-link-suggestions-Dp6kBQVu.js} +1 -1
  341. package/public/dist/index.html +4 -4
  342. package/public/dist/manager/index.html +2 -2
  343. package/public/index.html +3 -2
  344. package/public/js/event-channel.ts +25 -0
  345. package/public/js/features/channel-onboarding-flow.ts +16 -1
  346. package/public/js/features/channel-onboarding.ts +20 -2
  347. package/public/js/features/chat-messages.ts +3 -1
  348. package/public/js/features/chat-search.ts +2 -1
  349. package/public/js/features/chat.ts +56 -18
  350. package/public/js/features/elicitation.ts +15 -0
  351. package/public/js/features/idb-cache.ts +34 -0
  352. package/public/js/features/message-history.ts +2 -1
  353. package/public/js/features/session-hub.ts +334 -0
  354. package/public/js/features/settings-cli-status-render.ts +1 -1
  355. package/public/js/features/settings-cli-status.ts +25 -11
  356. package/public/js/features/settings-core.ts +72 -3
  357. package/public/js/features/settings-types.ts +32 -0
  358. package/public/js/features/ui-status.ts +2 -1
  359. package/public/js/main.ts +20 -0
  360. package/public/js/render/compose-block.ts +7 -0
  361. package/public/js/ws.ts +22 -2
  362. package/public/locales/en.json +46 -4
  363. package/public/locales/ja.json +46 -4
  364. package/public/locales/ko.json +46 -4
  365. package/public/locales/zh.json +46 -4
  366. package/public/manager/src/App.tsx +1 -1
  367. package/public/manager/src/InstancePreview.tsx +5 -1
  368. package/public/manager/src/api.ts +2 -1
  369. package/public/manager/src/components/InstanceGroups.tsx +10 -0
  370. package/public/manager/src/components/InstanceListContent.tsx +24 -1
  371. package/public/manager/src/components/InstanceRow.tsx +33 -3
  372. package/public/manager/src/components/InstanceSessionList.tsx +132 -0
  373. package/public/manager/src/hooks/useActiveSessionDisclosure.ts +45 -0
  374. package/public/manager/src/lib/resilient-fetch.ts +93 -0
  375. package/public/manager/src/lib/session-store.ts +216 -0
  376. package/public/manager/src/manager-components.css +97 -0
  377. package/public/manager/src/settings/pages/AdvancedExport.tsx +12 -2
  378. package/public/manager/src/settings/pages/Agent.tsx +113 -1
  379. package/public/manager/src/settings/pages/components/agent/agent-meta.ts +1 -1
  380. package/scripts/burst-scenario.mjs +72 -0
  381. package/scripts/check-redaction-sinks.mjs +1 -0
  382. package/scripts/check-strict-baseline.mjs +1 -1
  383. package/dist/src/manager/notes/constants.js.map +0 -1
  384. package/dist/src/manager/notes/path-guards.js.map +0 -1
  385. package/dist/src/manager/notes/search.js.map +0 -1
  386. package/public/dist/assets/AdvancedExport-DsDpqXoH.js +0 -1
  387. package/public/dist/assets/Agent-BjbZVZ9p.js +0 -1
  388. package/public/dist/assets/api-C9dOxby7.js +0 -1
  389. package/public/dist/assets/app-BzD_ZLvt.js +0 -55
  390. package/public/dist/assets/bgtask-badge-Dqbp-Y4e.js +0 -1
  391. package/public/dist/assets/idb-cache-ZH6lOebk.js +0 -1
  392. package/public/dist/assets/iframe-renderer-Df4KIVyx.js +0 -1
  393. package/public/dist/assets/manager-C4eNDNFt.css +0 -1
  394. package/public/dist/assets/manager-DaN201mN.js +0 -13
  395. package/public/dist/assets/memory-D-bULTHl.js +0 -1
  396. package/public/dist/assets/message-history-C4WOisGx.js +0 -1
  397. package/public/dist/assets/message-history-ZBoza6ew.js +0 -1
  398. package/public/dist/assets/pi-settings-CBUGDAuT.js +0 -1
  399. package/public/dist/assets/render-DYX6GRQ0.js +0 -122
  400. package/public/dist/assets/settings-DbJpGoaX.js +0 -55
  401. package/public/dist/assets/settings-SreihUje.js +0 -1
  402. package/public/dist/assets/settings-core-B1Y5Nb4Z.js +0 -1
  403. package/public/dist/assets/settings-core-DobZWIZ7.js +0 -140
  404. package/public/dist/assets/sidebar-Cp22g0Gv.js +0 -49
  405. package/public/dist/assets/skills-DwPIP08e.js +0 -1
  406. package/public/dist/assets/slash-commands-BBVfwcnW.js +0 -1
  407. package/public/dist/assets/ui-CAicpUZc.js +0 -1
  408. /package/dist/src/{manager/notes → notes}/constants.js +0 -0
  409. /package/dist/src/{manager/notes → notes}/path-guards.js +0 -0
  410. /package/dist/src/{manager/notes → notes}/search.js +0 -0
  411. /package/public/dist/assets/{pi-settings-Dh5lgb87.js → pi-settings-DEuKgvIj.js} +0 -0
@@ -10,10 +10,11 @@ import { publish as ssePublish } from '../core/event-bus.js';
10
10
  import { settings, UPLOADS_DIR, detectCli, getProjectDirs } from '../core/config.js';
11
11
  import { migrateLegacyClaudeValue } from '../cli/claude-models.js';
12
12
  import { stripUndefined } from '../core/strip-undefined.js';
13
- import { clearEmployeeSession, getSession, insertMessage, insertMessageWithTraceRun, getRecentMessages, listQueuedMessages, insertQueuedMessage, deleteQueuedMessage, getSessionBucket, clearSessionBucket, setSessionBucketSnapshot, } from '../core/db.js';
13
+ import { clearEmployeeSession, getSession, insertMessage, insertMessageWithTraceRun, getRecentMessages, listQueuedMessages, insertQueuedMessage, deleteQueuedMessage, migrateQueuedMessagesV1ToV2, getSessionBucket, clearSessionBucket, setSessionBucketSnapshot, } from '../core/db.js';
14
14
  import { sanitizeToolLogForDurableStorage } from '../shared/tool-log-sanitize.js';
15
15
  import { buildTaskSnapshot } from '../memory/runtime.js';
16
16
  import { getActiveChatSession } from '../core/chat-sessions.js';
17
+ import { currentSessionScope } from '../core/session-context.js';
17
18
  import { getSystemPrompt, regenerateB } from '../prompt/builder.js';
18
19
  import { extractSessionId, extractFromEvent, extractFromAcpUpdate, extractOutputChunk, logEventSummary, flushClaudeBuffers, flushOpenCodeBuffers } from './events.js';
19
20
  import { detectSmokeResponse } from './smoke-detector.js';
@@ -22,35 +23,33 @@ import { resolveMainCli, consumePendingBootstrapPrompt, peekPendingBootstrapProm
22
23
  import { getSessionOwnershipGeneration, persistMainSession, } from './session-persistence.js';
23
24
  import { isCompactMarkerRow } from '../core/compact.js';
24
25
  import { isRuntimeSettingsMutationInFlight, waitForRuntimeSettingsIdle } from '../core/runtime-settings-gate.js';
25
- import { hasBlockingWorkers, hasPendingWorkerReplays, getActiveWorkers, clearAllWorkers } from '../orchestrator/worker-registry.js';
26
+ import { hasBlockingWorkers, hasPendingWorkerReplays, getActiveWorkers, clearAllWorkers, clearWorkersForScope } from '../orchestrator/worker-registry.js';
26
27
  import { sanitizeWorkerProgressTools } from '../orchestrator/worker-progress.js';
27
28
  import { handleAgentExit, setSpawnAgent, setMainMetaHandler } from './lifecycle-handler.js';
28
29
  import { buildServicePath } from '../core/runtime-path.js';
29
- import { resolveOrcScope } from '../orchestrator/scope.js';
30
+ import { LOCAL_SESSION_SCOPE_ACTIVATION, resolveOrcScope } from '../orchestrator/scope.js';
30
31
  import { stripInterviewTracker } from '../orchestrator/sanitize.js';
31
32
  import { beginLiveRun, appendLiveRunText, setLiveRunTraceId, clearLiveRun, replaceLiveRunTools, appendLiveRunTool, getLiveRun } from './live-run-state.js';
32
33
  import { setSpawnRef as setMemorySpawnRef, } from './memory-flush-controller.js';
33
34
  import { applyCliEnvDefaults, buildSessionResumeKey, ensureOpencodeAlwaysAllowPermissions } from './spawn-env.js';
34
- import { buildPromptForArgs, withHistoryPrompt } from './prompt-context.js';
35
+ import { buildPromptForArgs, shouldBuildHistoryBlock, withHistoryPrompt } from './prompt-context.js';
35
36
  import { attachWatchdog, DEFAULT_WATCHDOG_ABSOLUTE_HARD_CAP_MS } from './watchdog.js';
36
37
  import { buildOpencodeRuntimeSnapshot, buildOpencodeSpawnAudit, pushOpencodeRawEvent, resolveOpencodeBinary, } from './opencode-diagnostics.js';
37
38
  import { asCliEventRecord, discriminate, fieldString } from '../types/cli-events.js';
38
39
  import { isJawRuntimeEvent, handleJawRuntimeEvent } from './claude-e-runtime.js';
39
- import { jawRuntime } from './jwc-runtime.js';
40
+ import { jawRuntimesByScope, runtimeForScope } from './jwc-runtime.js';
40
41
  import { applyOutputPolicy, runBeforeSpawnChecks } from '../core/policy-hooks.js';
41
- import { appendTraceEvent, stampTraceTool, startTraceRun } from '../trace/store.js';
42
+ import { appendTraceEvent, finalizeTraceRun, stampTraceTool, startTraceRun } from '../trace/store.js';
42
43
  import { AGY_COMPLETE_KILL_REASON, appendAgyFullText, classifyAgyTranscriptMode, describeAgyFinalSource, extractAgyConversationId, finalizeAgyFallbackText, AGY_PLANNER_ONLY_NOTICE, isAgyIntermediatePlannerText, formatAgyWatchdogContext, resolveAgyEmptyCloseError, formatAgyTimeoutMessage, getAgyQuietCompletionDelayMs, isAgyStaleSessionOutput, normalizeAgyCloseText, shouldFreezeAgyLiveDisplay, stripAgyPromptEchoPrefix, stripAgyResumeReplayPrefix, stripAgyResumeReplayPrefixes, } from './agy-runtime.js';
43
44
  import { detectAgyCapabilities } from './agy-capabilities.js';
44
45
  import { buildAgyBootstrapEnvelope, resolveAgyPromptOrder, } from './agy-bootstrap.js';
45
46
  import { startAgyTranscriptWatcher } from './agy-transcript-watcher.js';
46
- import { appendAssistantTextSegment, emitAgentTool, normalizeAssistantDisplayText, pushTrace } from './events/helpers.js';
47
- import { listKiroConversationIdsForCwd } from './kiro-auth.js';
48
- import { captureKiroSessionIdAfterExit, finalizeKiroFullText, flushKiroStdoutContext, isKiroPlainTextCli, isKiroStaleSessionOutput, parseAiESessionIdFromStderr, processKiroStdoutChunk, } from './kiro-runtime.js';
47
+ import { appendAssistantRawText, appendAssistantTextSegment, emitAgentTool, normalizeAssistantDisplayText, pushTrace } from './events/helpers.js';
48
+ import { captureKiroSessionIdAfterExit, finalizeKiroFullText, flushKiroStdoutContext, isKiroPlainTextCli, isKiroStaleSessionOutput, parseAiESessionIdFromStderr, processKiroStdoutChunk, spawnWithKiroSnapshot, } from './kiro-runtime.js';
49
49
  import { resolveCursorModelVariant } from './cursor-runtime.js';
50
50
  import { normalizePiSettings, spawnPiRpc } from './pi-runtime.js';
51
51
  import { getEmployeeMcpServers } from './mcp-passthrough.js';
52
52
  // ─── State ───────────────────────────────────────────
53
- export let activeProcess = null;
54
53
  export const activeProcesses = new Map(); // agentId → child process
55
54
  /** Kill reason recorded when a duplicate registration reaps the previous child. */
56
55
  const DUP_REGISTRATION_KILL_REASON = 'dup-registration';
@@ -98,12 +97,40 @@ function registerActiveProcess(agentLabel, child) {
98
97
  }
99
98
  activeProcesses.set(agentLabel, child);
100
99
  }
101
- let currentMainMeta = null;
102
- export function getCurrentMainMeta() {
103
- return currentMainMeta;
100
+ export const activeMainProcesses = new Map();
101
+ export function getCurrentMainMeta(scopeKey) {
102
+ const scope = scopeKey ?? currentSessionScope()?.scope ?? 'default';
103
+ return activeMainProcesses.get(scope)?.meta ?? null;
104
104
  }
105
- export function setCurrentMainMeta(meta) {
106
- currentMainMeta = meta;
105
+ export function setCurrentMainMeta(scopeKeyOrMeta, nextMeta) {
106
+ const scopeKey = typeof scopeKeyOrMeta === 'string'
107
+ ? scopeKeyOrMeta
108
+ : currentSessionScope()?.scope ?? 'default';
109
+ const meta = typeof scopeKeyOrMeta === 'string' ? nextMeta ?? null : scopeKeyOrMeta;
110
+ const run = activeMainProcesses.get(scopeKey);
111
+ if (!meta) {
112
+ activeMainProcesses.delete(scopeKey);
113
+ return;
114
+ }
115
+ if (run) {
116
+ run.meta = meta;
117
+ }
118
+ else {
119
+ activeMainProcesses.set(scopeKey, {
120
+ process: null,
121
+ starting: false,
122
+ steering: false,
123
+ ownerGeneration: 0,
124
+ meta,
125
+ });
126
+ }
127
+ }
128
+ export function releaseMainRun(scopeKey, child, ownerGeneration) {
129
+ const run = activeMainProcesses.get(scopeKey);
130
+ if (!run || run.process !== child || run.ownerGeneration !== ownerGeneration)
131
+ return false;
132
+ activeMainProcesses.delete(scopeKey);
133
+ return true;
107
134
  }
108
135
  export function buildAiERuntimeStatusMeta(cli, provider, model) {
109
136
  if (cli !== 'ai-e')
@@ -230,7 +257,7 @@ export function killAgentById(agentId) {
230
257
  }
231
258
  export { memoryFlushCounter, flushCycleCount } from './memory-flush-controller.js';
232
259
  const queueCtrl = createQueueController({
233
- isSpawnBusy: () => Boolean(activeProcess) || mainSpawnStarting || steerInProgress || queueCtrl.isRetryPending(),
260
+ isSpawnBusy: (scopeKey) => isAgentBusy(scopeKey),
234
261
  hasBlockingWorkers,
235
262
  hasPendingWorkerReplays,
236
263
  insertMessage,
@@ -238,30 +265,41 @@ const queueCtrl = createQueueController({
238
265
  insertQueuedMessage,
239
266
  deleteQueuedMessage,
240
267
  listQueuedMessages: listQueuedMessages,
268
+ migrateQueuedMessagesV1ToV2,
241
269
  broadcast,
242
270
  importPipeline: () => import('../orchestrator/pipeline.js'),
243
271
  getWorkingDir: () => settings["workingDir"] || null,
272
+ isMultiSessionEnabled: () => settings["multiSession"]?.enabled === true,
273
+ isLocalSessionScopeEnabled: () => LOCAL_SESSION_SCOPE_ACTIVATION,
244
274
  });
245
- export const { messageQueue, enqueueMessage, removeQueuedMessage, processQueue, setQueueHold, clearQueueHold, getQueueHoldId, clearRetryTimer, resetFallbackState, getFallbackState, getQueuedMessageSnapshotForScope, } = queueCtrl;
246
- let mainSpawnStarting = false;
247
- let cancelPendingMainSpawn = null;
248
- let steerInProgress = false;
249
- let cancelActiveCodexAppTurn = null;
250
- let cancelActivePiTurn = null;
275
+ export const { messageQueue, enqueueMessage, removeQueuedMessage, processQueue, setQueueHold, clearQueueHold, getQueueHoldId, isScopedQueue, isRetryPending, isQueueBusy, clearRetryTimer,
276
+ // Exposed so DELETE's 409 paths can be driven end-to-end against the
277
+ // production controller instead of an isolated instance.
278
+ retryStateForScope, resetFallbackState, getFallbackState, getQueuedMessageSnapshotForScope, purgeQueueOnStop, } = queueCtrl;
251
279
  const piProfileFingerprintKey = crypto.randomBytes(32);
252
- export function setSteerInProgress(v) {
253
- const was = steerInProgress;
254
- steerInProgress = v;
255
- if (was && !v)
256
- queueMicrotask(() => processQueue());
280
+ export function setSteerInProgress(scopeKeyOrValue, nextValue) {
281
+ const scopeKey = typeof scopeKeyOrValue === 'string' ? scopeKeyOrValue : 'default';
282
+ const value = typeof scopeKeyOrValue === 'boolean' ? scopeKeyOrValue : nextValue === true;
283
+ const run = activeMainProcesses.get(scopeKey);
284
+ if (!run)
285
+ return;
286
+ const was = run.steering;
287
+ run.steering = value;
288
+ if (was && !value)
289
+ queueMicrotask(() => { void processQueue(scopeKey); });
257
290
  }
258
- export function isSteerInProgress() {
259
- return steerInProgress;
291
+ export function isSteerInProgress(scopeKey = 'default') {
292
+ return activeMainProcesses.get(scopeKey)?.steering === true;
260
293
  }
261
- export function isAgentBusy() {
262
- // jwc in-process turns hold no ChildProcess, so activeProcess stays null
263
- // fold the resident runtime's own busy flag in (110.3 §D).
264
- return !!activeProcess || jawRuntime.busy || queueCtrl.isRetryPending() || mainSpawnStarting || steerInProgress;
294
+ export function isAgentBusy(scopeKey = 'default') {
295
+ if (scopeKey === null) {
296
+ return activeMainProcesses.size > 0
297
+ || [...jawRuntimesByScope.values()].some(runtime => runtime.busy)
298
+ || queueCtrl.isRetryPending(null);
299
+ }
300
+ return activeMainProcesses.has(scopeKey)
301
+ || runtimeForScope(scopeKey).busy
302
+ || queueCtrl.isRetryPending(scopeKey);
265
303
  }
266
304
  // ─── Kill / Steer ────────────────────────────────────
267
305
  // [I2] Per-process kill reason map (replaces global variable to avoid cross-process confusion)
@@ -272,25 +310,28 @@ const CLAUDE_E_STEER_WAIT_MS = 30_000;
272
310
  const CLAUDE_E_STEER_KILL_ESCALATION_MS = 8_000;
273
311
  const DEFAULT_CODEX_APP_TURN_IDLE_MS = 300_000;
274
312
  const DEFAULT_CODEX_APP_TURN_ABS_MS = 2 * 60 * 60_000;
313
+ const DEFAULT_CODEX_APP_ACQUIRE_WAIT_MS = 60_000;
314
+ const CODEX_APP_ACQUIRE_RETRY_BACKOFF_MAX_MS = 250;
275
315
  function configuredPositiveMs(value, fallback) {
276
316
  const parsed = Number(value);
277
317
  return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
278
318
  }
279
- function getActiveMainCli() {
280
- return typeof currentMainMeta?.cli === 'string' ? currentMainMeta.cli : null;
319
+ function getActiveMainCli(scopeKey) {
320
+ const cli = activeMainProcesses.get(scopeKey)?.meta.cli;
321
+ return typeof cli === 'string' ? cli : null;
281
322
  }
282
- function isActiveAiEPtyRuntime() {
283
- const cli = getActiveMainCli();
323
+ function isActiveAiEPtyRuntime(scopeKey) {
324
+ const cli = getActiveMainCli(scopeKey);
284
325
  return cli === 'claude-e' || cli === 'ai-e';
285
326
  }
286
- function getKillPolicy(reason) {
287
- if (reason === 'steer' && isActiveAiEPtyRuntime()) {
327
+ function getKillPolicy(scopeKey, reason) {
328
+ if (reason === 'steer' && isActiveAiEPtyRuntime(scopeKey)) {
288
329
  return { signal: 'SIGINT', escalationMs: CLAUDE_E_STEER_KILL_ESCALATION_MS };
289
330
  }
290
331
  return { signal: 'SIGTERM', escalationMs: DEFAULT_KILL_ESCALATION_MS };
291
332
  }
292
- export function getSteerWaitMsForActiveAgent() {
293
- return isActiveAiEPtyRuntime() ? CLAUDE_E_STEER_WAIT_MS : DEFAULT_STEER_WAIT_MS;
333
+ export function getSteerWaitMsForActiveAgent(scopeKey = 'default') {
334
+ return isActiveAiEPtyRuntime(scopeKey) ? CLAUDE_E_STEER_WAIT_MS : DEFAULT_STEER_WAIT_MS;
294
335
  }
295
336
  /** Get kill reason for a process (by PID), consuming it */
296
337
  function consumeKillReason(pid) {
@@ -303,81 +344,78 @@ function consumeKillReason(pid) {
303
344
  }
304
345
  /**
305
346
  * Fix A: 사용자 stop은 메모리 큐 + DB persisted_queue + frontend pending row를
306
- * 모두 폐기한다. exit handler의 processQueue() 자동 드레인이 stop 직후 잔존
347
+ * 모두 폐기한다. exit handler의 scoped queue 자동 드레인이 stop 직후 잔존
307
348
  * 메시지를 "스스로 steer" 처럼 실행하던 회귀를 차단.
308
349
  */
309
350
  /**
310
351
  * Fix C2: 사용자 stop 시 worker-registry 도 비운다.
311
- * gateway.submitMessage가 isAgentBusy() 외에 hasBlockingWorkers()/hasPendingWorkerReplays()
352
+ * gateway.submitMessage가 scoped main/worker/replay 상태를 모두 검사하므로,
312
353
  * 도 검사하므로, 이걸 비우지 않으면 stop 직후 새 메시지가 busy 분기 → 큐로 떨어지고
313
354
  * 프론트는 (1) 낙관 bubble + (2) applyQueuedOverlay 가 만든 queued bubble = 2개를 보여준다.
314
355
  */
315
- function clearWorkerSlotsOnStop(reason) {
316
- const active = getActiveWorkers().length;
317
- if (active === 0 && !hasPendingWorkerReplays())
356
+ function clearWorkerSlotsOnStop(scopeKey, reason) {
357
+ const active = getActiveWorkers(scopeKey).length;
358
+ if (active === 0 && !hasPendingWorkerReplays(scopeKey))
318
359
  return;
319
- clearAllWorkers();
320
- console.log(`[jaw:stop] cleared worker registry (active=${active}, reason=${reason})`);
360
+ clearWorkersForScope(scopeKey);
361
+ console.log(`[jaw:stop] cleared worker registry (active=${active}, scope=${scopeKey}, reason=${reason})`);
321
362
  }
322
- function clearMainLiveRunOnStop(reason) {
323
- if (reason !== 'api' && reason !== 'user' && reason !== 'steer')
363
+ function clearMainLiveRunOnStop(scopeKey, reason) {
364
+ if (reason !== 'api' && reason !== 'user' && reason !== 'steer' && reason !== 'interrupt')
324
365
  return;
325
- const scope = currentMainMeta?.scopeId || resolveOrcScope(stripUndefined({
326
- origin: currentMainMeta?.origin || 'web',
327
- chatId: currentMainMeta?.chatId,
328
- workingDir: settings["workingDir"] || null,
329
- }));
330
- clearLiveRun(scope);
366
+ clearLiveRun(scopeKey);
331
367
  }
332
368
  /**
333
369
  * jwc turns run in-process (no ChildProcess), so the SIGTERM/SIGKILL paths
334
370
  * below never touch them — abort the resident runtime session explicitly or
335
371
  * /api/stop is a no-op while jwc streams (devlog 260703 tui_steer_esc_rca).
336
372
  */
337
- function abortInProcessRuntimeOnStop(reason) {
338
- if (reason !== 'api' && reason !== 'user' && reason !== 'steer')
373
+ function abortInProcessRuntimeOnStop(scopeKey, reason) {
374
+ if (reason !== 'api' && reason !== 'user' && reason !== 'steer' && reason !== 'interrupt')
339
375
  return false;
340
- if (!jawRuntime.busy)
376
+ if (getActiveMainCli(scopeKey) !== 'jwc')
341
377
  return false;
342
- jawRuntime.abort().catch((err) => {
378
+ const runtime = runtimeForScope(scopeKey);
379
+ if (!runtime.busy)
380
+ return false;
381
+ runtime.abort().catch((err) => {
343
382
  console.warn('[jaw:stop] jwc abort failed', err?.message || err);
344
383
  });
345
384
  return true;
346
385
  }
347
- export function killActiveAgent(reason = 'user') {
348
- const hadTimer = queueCtrl.isRetryPending();
349
- const cancelledPendingMain = cancelPendingMainSpawn ? (cancelPendingMainSpawn(reason), true) : false;
350
- clearRetryTimer(false); // stop 의도: 큐 재개 안 함
351
- clearMainLiveRunOnStop(reason);
352
- const abortedInProcess = abortInProcessRuntimeOnStop(reason);
386
+ export function killActiveAgent(scopeKeyOrReason = 'user', scopedReason) {
387
+ const scopeKey = scopedReason === undefined ? 'default' : scopeKeyOrReason;
388
+ const reason = scopedReason ?? scopeKeyOrReason;
389
+ const run = activeMainProcesses.get(scopeKey);
390
+ const hadTimer = queueCtrl.isRetryPending(scopeKey);
391
+ const cancelledPendingMain = run?.cancelPending ? (run.cancelPending(reason), true) : false;
392
+ clearRetryTimer(scopeKey, false);
393
+ if (!cancelledPendingMain)
394
+ clearMainLiveRunOnStop(scopeKey, reason);
395
+ const abortedInProcess = abortInProcessRuntimeOnStop(scopeKey, reason);
353
396
  // Fix A: 사용자 stop은 큐도 폐기. steer/internal kill은 큐 보존.
354
397
  // Fix C2: worker registry 도 비워서 hasBlockingWorkers/hasPendingWorkerReplays가 즉시 false.
355
398
  if (reason === 'api' || reason === 'user') {
356
- queueCtrl.purgeQueueOnStop(reason);
357
- clearWorkerSlotsOnStop(reason);
399
+ queueCtrl.purgeQueueOnStop(scopeKey, reason);
400
+ clearWorkerSlotsOnStop(scopeKey, reason);
358
401
  }
359
- if (cancelActiveCodexAppTurn && getActiveMainCli() === 'codex-app') {
360
- if (activeProcess?.pid)
361
- killReasons.set(activeProcess.pid, reason);
362
- console.log(`[jaw:kill] reason=${reason} cli=codex-app action=lease.cancel`);
363
- cancelActiveCodexAppTurn(reason);
364
- if (reason === 'api' || reason === 'user' || reason === 'steer')
365
- activeProcess = null;
402
+ if (run?.cancelTurn && (getActiveMainCli(scopeKey) === 'codex-app' || getActiveMainCli(scopeKey) === 'pi')) {
403
+ if (run.process?.pid)
404
+ killReasons.set(run.process.pid, reason);
405
+ console.log(`[jaw:kill] reason=${reason} scope=${scopeKey} cli=${getActiveMainCli(scopeKey)} action=lease.cancel`);
406
+ run.cancelTurn(reason);
407
+ if (reason === 'api' || reason === 'user' || reason === 'steer' || reason === 'interrupt')
408
+ activeMainProcesses.delete(scopeKey);
366
409
  return true;
367
410
  }
368
- if (cancelActivePiTurn && getActiveMainCli() === 'pi') {
369
- if (activeProcess?.pid)
370
- killReasons.set(activeProcess.pid, reason);
371
- console.log(`[jaw:kill] reason=${reason} cli=pi action=lease.cancel`);
372
- cancelActivePiTurn(reason);
373
- if (reason === 'api' || reason === 'user' || reason === 'steer')
374
- activeProcess = null;
375
- return true;
411
+ const activeProcess = run?.process ?? null;
412
+ if (!activeProcess) {
413
+ if (reason === 'api' || reason === 'user' || reason === 'steer' || reason === 'interrupt')
414
+ activeMainProcesses.delete(scopeKey);
415
+ return hadTimer || cancelledPendingMain || abortedInProcess;
376
416
  }
377
- if (!activeProcess)
378
- return hadTimer || cancelledPendingMain || abortedInProcess; // timer/gated spawn/jwc abort 취소도 "killed" 취급
379
- const policy = getKillPolicy(reason);
380
- console.log(`[jaw:kill] reason=${reason} cli=${getActiveMainCli() || 'unknown'} signal=${policy.signal} escalationMs=${policy.escalationMs}`);
417
+ const policy = getKillPolicy(scopeKey, reason);
418
+ console.log(`[jaw:kill] reason=${reason} scope=${scopeKey} cli=${getActiveMainCli(scopeKey) || 'unknown'} signal=${policy.signal} escalationMs=${policy.escalationMs}`);
381
419
  if (activeProcess.pid)
382
420
  killReasons.set(activeProcess.pid, reason);
383
421
  try {
@@ -411,25 +449,23 @@ export function killActiveAgent(reason = 'user') {
411
449
  proc.stdout?.destroy();
412
450
  proc.stderr?.destroy();
413
451
  }, policy.escalationMs);
414
- // Fix C1: 사용자 stop/steer 시 isAgentBusy()가 즉시 false가 되도록 참조를 동기 해제.
452
+ // Fix C1: 사용자 stop/steer 시 해당 scope busy가 즉시 false가 되도록 참조를 동기 해제.
415
453
  // 실제 child 종료는 위 setTimeout SIGKILL이 백그라운드에서 마무리.
416
454
  // exit handler의 setActiveProcess(null) / activeProcesses.delete 는 idempotent.
417
- if (reason === 'api' || reason === 'user' || reason === 'steer') {
418
- activeProcess = null;
455
+ if (reason === 'api' || reason === 'user' || reason === 'steer' || reason === 'interrupt') {
456
+ activeMainProcesses.delete(scopeKey);
419
457
  }
420
458
  return true;
421
459
  }
422
460
  export function killAllAgents(reason = 'user') {
423
- const hadTimer = queueCtrl.isRetryPending();
424
- const cancelledPendingMain = cancelPendingMainSpawn ? (cancelPendingMainSpawn(reason), true) : false;
425
- clearRetryTimer(false); // stop 의도: 큐 재개 안 함
426
- clearMainLiveRunOnStop(reason);
427
- const abortedInProcess = abortInProcessRuntimeOnStop(reason);
428
- // Fix A: 사용자 stop은 큐도 폐기. Fix C2: worker 슬롯도 비움.
429
- if (reason === 'api' || reason === 'user') {
430
- queueCtrl.purgeQueueOnStop(reason);
431
- clearWorkerSlotsOnStop(reason);
461
+ const hadTimer = queueCtrl.isRetryPending(null);
462
+ const mainScopes = [...activeMainProcesses.keys()];
463
+ let killedMain = false;
464
+ for (const scopeKey of mainScopes) {
465
+ killedMain = killActiveAgent(scopeKey, reason) || killedMain;
432
466
  }
467
+ if (reason === 'api' || reason === 'user')
468
+ queueCtrl.purgeQueueOnStop(null, reason);
433
469
  let killed = 0;
434
470
  for (const [id, proc] of activeProcesses) {
435
471
  console.log(`[jaw:killAll] killing ${id}, reason=${reason}`);
@@ -465,23 +501,21 @@ export function killAllAgents(reason = 'user') {
465
501
  ref.stderr?.destroy();
466
502
  }, 2000);
467
503
  }
468
- // Also kill main activeProcess if not in map
469
- if (activeProcess && !activeProcesses.has('main')) {
470
- killActiveAgent(reason);
471
- }
472
- // Fix C1: 사용자 stop 시 isAgentBusy() 즉시 false. 실제 종료는 백그라운드 SIGKILL.
473
504
  if (reason === 'api' || reason === 'user') {
474
- activeProcess = null;
475
505
  activeProcesses.clear();
506
+ activeMainProcesses.clear();
507
+ clearAllWorkers();
476
508
  }
477
- return killed > 0 || !!activeProcess || hadTimer || cancelledPendingMain || abortedInProcess;
509
+ return killed > 0 || killedMain || hadTimer;
478
510
  }
479
- export function waitForProcessEnd(timeoutMs = 3000) {
480
- if (!activeProcess)
511
+ export function waitForProcessEnd(scopeKeyOrTimeout = 'default', scopedTimeout = 3000) {
512
+ const scopeKey = typeof scopeKeyOrTimeout === 'string' ? scopeKeyOrTimeout : 'default';
513
+ const timeoutMs = typeof scopeKeyOrTimeout === 'number' ? scopeKeyOrTimeout : scopedTimeout;
514
+ if (!activeMainProcesses.has(scopeKey))
481
515
  return Promise.resolve();
482
516
  return new Promise(resolve => {
483
517
  const check = setInterval(() => {
484
- if (!activeProcess) {
518
+ if (!activeMainProcesses.has(scopeKey)) {
485
519
  clearInterval(check);
486
520
  resolve();
487
521
  }
@@ -489,21 +523,35 @@ export function waitForProcessEnd(timeoutMs = 3000) {
489
523
  setTimeout(() => { clearInterval(check); resolve(); }, timeoutMs);
490
524
  });
491
525
  }
492
- export async function steerAgent(newPrompt, source) {
493
- const steerWaitMs = getSteerWaitMsForActiveAgent();
494
- const wasRunning = killActiveAgent('steer');
526
+ export function canSteerAgent(scopeKey) {
527
+ const run = activeMainProcesses.get(scopeKey);
528
+ return run?.meta.cli === 'jwc' && jawRuntimesByScope.get(scopeKey)?.busy === true;
529
+ }
530
+ export async function steerAgent(scopeKey, newPrompt, source, meta) {
531
+ const run = activeMainProcesses.get(scopeKey);
532
+ const runtime = runtimeForScope(scopeKey);
533
+ const chatSessionId = meta?.chatSessionId || run?.meta.chatSessionId || getActiveChatSession();
534
+ if (run?.meta.cli === 'jwc' && runtime.busy) {
535
+ insertMessage.run('user', newPrompt, source, '', settings["workingDir"] || null, chatSessionId);
536
+ broadcast('new_message', { role: 'user', content: newPrompt, source, scope: scopeKey, sessionId: chatSessionId });
537
+ broadcast('steer_started', stripUndefined({ prompt: newPrompt, origin: source || 'web', scope: scopeKey, sessionId: chatSessionId, target: meta?.target, chatId: meta?.chatId, requestId: meta?.requestId, remoteKey: meta?.remoteKey, replyViaTarget: meta?.replyViaTarget }));
538
+ await runtime.steer(settings["workingDir"] || process.cwd(), newPrompt);
539
+ return;
540
+ }
541
+ const steerWaitMs = getSteerWaitMsForActiveAgent(scopeKey);
542
+ const wasRunning = killActiveAgent(scopeKey, 'steer');
495
543
  if (wasRunning)
496
- await waitForProcessEnd(steerWaitMs);
497
- insertMessage.run('user', newPrompt, source, '', settings["workingDir"] || null, getActiveChatSession());
498
- broadcast('new_message', { role: 'user', content: newPrompt, source });
499
- broadcast('steer_started', { prompt: newPrompt, origin: source || 'web' });
544
+ await waitForProcessEnd(scopeKey, steerWaitMs);
545
+ insertMessage.run('user', newPrompt, source, '', settings["workingDir"] || null, chatSessionId);
546
+ broadcast('new_message', { role: 'user', content: newPrompt, source, scope: scopeKey, sessionId: chatSessionId });
547
+ broadcast('steer_started', { prompt: newPrompt, origin: source || 'web', scope: scopeKey });
500
548
  const { orchestrate, orchestrateContinue, orchestrateReset, isContinueIntent, isResetIntent } = await import('../orchestrator/pipeline.js');
501
549
  const origin = source || 'web';
502
550
  const task = isResetIntent(newPrompt)
503
- ? orchestrateReset({ origin, _skipInsert: true })
551
+ ? orchestrateReset({ origin, scope: scopeKey, chatSessionId, _skipInsert: true })
504
552
  : isContinueIntent(newPrompt)
505
- ? orchestrateContinue({ origin, _skipInsert: true })
506
- : orchestrate(newPrompt, { origin, _skipInsert: true });
553
+ ? orchestrateContinue({ origin, scope: scopeKey, chatSessionId, _skipInsert: true })
554
+ : orchestrate(newPrompt, { origin, scope: scopeKey, chatSessionId, _skipInsert: true });
507
555
  task.catch((err) => {
508
556
  console.error('[steer:orchestrate]', err.message);
509
557
  broadcast('orchestrate_done', { text: `[error] ${err.message}`, error: true, origin });
@@ -539,8 +587,8 @@ function formatCliUnavailableMessage(cli, detected) {
539
587
  }
540
588
  return `CLI '${cli}' not found in PATH. Run \`jaw doctor --json\`.`;
541
589
  }
542
- function buildHistoryBlock(currentPrompt, workingDir, maxSessions = 10, maxTotalChars = 8000) {
543
- const recent = getRecentMessages.all(workingDir || null, getActiveChatSession(), Math.max(1, maxSessions * 2));
590
+ function buildHistoryBlock(currentPrompt, workingDir, chatSessionId, maxSessions = 10, maxTotalChars = 8000) {
591
+ const recent = getRecentMessages.all(workingDir || null, chatSessionId, Math.max(1, maxSessions * 2));
544
592
  if (!recent.length)
545
593
  return '';
546
594
  const promptText = String(currentPrompt || '').trim();
@@ -605,18 +653,21 @@ function isStaleWorklogHistoryArtifact(text) {
605
653
  '正在从上一个 worklog 继续。',
606
654
  ].some(marker => value.includes(marker));
607
655
  }
608
- function getLatestAssistantContentForAgyResume(workingDir) {
609
- const rows = getRecentMessages.all(workingDir || null, getActiveChatSession(), 12);
656
+ // The session is passed in rather than read globally: the replay is prepended to THIS
657
+ // run's prompt, so it has to come from this run's conversation and not from whichever
658
+ // one happens to be active (073 §2.5a).
659
+ function getLatestAssistantContentForAgyResume(workingDir, chatSessionId) {
660
+ const rows = getRecentMessages.all(workingDir || null, chatSessionId, 12);
610
661
  const row = rows.find((msg) => msg.role === 'assistant' && typeof msg.content === 'string' && msg.content.trim().length > 0);
611
662
  return row?.content || null;
612
663
  }
613
- function getRecentAssistantContentsForAgyResume(workingDir) {
614
- const rows = getRecentMessages.all(workingDir || null, getActiveChatSession(), 20);
664
+ function getRecentAssistantContentsForAgyResume(workingDir, chatSessionId) {
665
+ const rows = getRecentMessages.all(workingDir || null, chatSessionId, 20);
615
666
  return rows
616
667
  .filter((msg) => msg.role === 'assistant' && typeof msg.content === 'string' && msg.content.trim().length > 0)
617
668
  .map((msg) => String(msg.content || '').trim());
618
669
  }
619
- import { buildArgs, buildResumeArgs, formatAgyPrintTimeout, resolveAiEProvider, resolveSessionBucket } from './args.js';
670
+ import { buildArgs, buildResumeArgs, formatAgyPrintTimeout, resolveAiEProvider, resolveScopedSessionBucket, resolveSessionBucket } from './args.js';
620
671
  export { buildArgs, buildResumeArgs, resolveAiEProvider, resolveSessionBucket };
621
672
  const warnedAgyCapabilityFallbacks = new Set();
622
673
  // ─── Upload wrapper ──────────────────────────────────
@@ -625,9 +676,10 @@ export { buildMediaPrompt, buildMediaPromptMany };
625
676
  // ─── Spawn Agent ─────────────────────────────────────
626
677
  import { AcpClient } from '../cli/acp-client.js';
627
678
  import { CodexAppClient, isRecoverableResumeError } from './codex-app-client.js';
628
- import { acquireCodexAppRuntime, acquirePiRuntime } from './runtime-pool.js';
679
+ import { acquireCodexAppRuntime, acquirePiRuntime, } from './runtime-pool.js';
680
+ import { acquireCodexAppLane, CodexHostGenerationStaleError, prepareCodexAppHost, } from './codex-host-pool.js';
629
681
  import { loadCatalogEfforts, resolveCatalogPath, validateModelEffort } from './codex-app-catalog.js';
630
- import { extractFromCodexAppEvent } from './codex-app-events.js';
682
+ import { listenCodexAppTurnAdapter, } from './codex-app-events.js';
631
683
  import { canGuardedAgyResume, resolveAgyNativeResume, shouldEmitHeartbeat, shouldResumeBucketSession } from './spawn/resume.js';
632
684
  export { canGuardedAgyResume, resolveAgyNativeResume, shouldEmitHeartbeat, shouldResumeBucketSession };
633
685
  import { createQueueController, FALLBACK_MAX_RETRIES } from './spawn/queue.js';
@@ -649,19 +701,57 @@ export function spawnAgent(prompt, opts = {}) {
649
701
  const gateEligibleMain = mainManaged && !opts.agentId && !opts.internal && !opts._isFallback && !opts._isSmokeContinuation && !opts._isGoalContinuation;
650
702
  const isEmployee = !mainManaged;
651
703
  const empTag = isEmployee ? { isEmployee: true } : {};
704
+ const multiSessionEnabled = settings["multiSession"]?.enabled === true;
705
+ const capturedScope = currentSessionScope();
706
+ const scopeKey = multiSessionEnabled
707
+ ? (opts.scopeKey || capturedScope?.scope || resolveOrcScope(stripUndefined({
708
+ origin,
709
+ target: opts.target,
710
+ chatId: opts.chatId,
711
+ workingDir: settings["workingDir"] || null,
712
+ persistedScopeId: opts.remoteKey,
713
+ multiSessionEnabled,
714
+ })))
715
+ : 'default';
716
+ const chatSessionId = multiSessionEnabled
717
+ ? (opts.chatSessionId || capturedScope?.chatSessionId || getActiveChatSession())
718
+ : getActiveChatSession();
719
+ if (multiSessionEnabled) {
720
+ opts = stripUndefined({
721
+ ...opts,
722
+ scopeKey,
723
+ chatSessionId,
724
+ ...(opts.remoteKey ? { remoteKey: opts.remoteKey } : {}),
725
+ });
726
+ }
727
+ let mainRun = mainManaged ? activeMainProcesses.get(scopeKey) : undefined;
728
+ if (mainManaged && mainRun && !opts._settingsGateWaited) {
729
+ console.log(`[jaw] Agent already running for scope=${scopeKey}, skipping`);
730
+ return { child: null, promise: Promise.resolve({ text: '', code: -1 }) };
731
+ }
732
+ if (mainManaged && !mainRun) {
733
+ mainRun = {
734
+ process: null,
735
+ starting: false,
736
+ steering: false,
737
+ ownerGeneration: 0,
738
+ meta: { origin, scopeId: scopeKey, chatSessionId, ...(opts.remoteKey ? { remoteKey: opts.remoteKey } : {}) },
739
+ };
740
+ activeMainProcesses.set(scopeKey, mainRun);
741
+ }
652
742
  if (gateEligibleMain && !opts._settingsGateWaited && isRuntimeSettingsMutationInFlight()) {
653
- if (activeProcess || queueCtrl.isRetryPending() || mainSpawnStarting) {
743
+ if (queueCtrl.isRetryPending(scopeKey) || mainRun?.starting) {
654
744
  console.log('[jaw] Agent already running, skipping');
655
745
  return { child: null, promise: Promise.resolve({ text: '', code: -1 }) };
656
746
  }
657
- mainSpawnStarting = true;
747
+ mainRun.starting = true;
658
748
  let cancelled = false;
659
749
  let cancelReason = 'user';
660
750
  const cancelThisSpawn = (reason) => {
661
751
  cancelled = true;
662
752
  cancelReason = reason;
663
753
  };
664
- cancelPendingMainSpawn = cancelThisSpawn;
754
+ mainRun.cancelPending = cancelThisSpawn;
665
755
  const promise = (async () => {
666
756
  try {
667
757
  await waitForRuntimeSettingsIdle();
@@ -672,10 +762,12 @@ export function spawnAgent(prompt, opts = {}) {
672
762
  return await next.promise;
673
763
  }
674
764
  finally {
675
- if (cancelPendingMainSpawn === cancelThisSpawn)
676
- cancelPendingMainSpawn = null;
677
- mainSpawnStarting = false;
678
- processQueue();
765
+ const latest = activeMainProcesses.get(scopeKey);
766
+ if (latest?.cancelPending === cancelThisSpawn)
767
+ delete latest.cancelPending;
768
+ if (latest)
769
+ latest.starting = false;
770
+ void processQueue(scopeKey);
679
771
  }
680
772
  })();
681
773
  return { child: null, promise };
@@ -684,39 +776,46 @@ export function spawnAgent(prompt, opts = {}) {
684
776
  // Skip for employee spawns — distribute.ts manages AGENTS.md isolation
685
777
  if (!opts.internal && !opts._isFallback && !opts.agentId)
686
778
  regenerateB();
687
- const liveScope = resolveOrcScope(stripUndefined({ origin, chatId: opts.chatId, workingDir: settings["workingDir"] || null }));
779
+ const liveScope = scopeKey;
688
780
  // Employee must not pollute boss's liveRun (see devlog 260423_employee_liverun_contamination)
689
781
  const effectiveLiveScope = mainManaged ? liveScope : null;
690
- // INVARIANT: 모든 외부 호출은 gateway.ts isAgentBusy()를 거침.
691
- // 직접 spawnAgent 호출 시 retryPendingTimer도 확인할 것.
692
- if ((activeProcess || (mainSpawnStarting && gateEligibleMain && !opts._settingsGateWaited)) && mainManaged) {
782
+ // INVARIANT: 모든 외부 호출은 gateway.ts scoped busy admission을 거침.
783
+ // 직접 spawnAgent 호출 시 scope별 retry state도 확인할 것.
784
+ if (mainManaged && mainRun?.starting && gateEligibleMain && !opts._settingsGateWaited) {
693
785
  console.log('[jaw] Agent already running, skipping');
694
786
  return { child: null, promise: Promise.resolve({ text: '', code: -1 }) };
695
787
  }
696
788
  // Capture Boss main session channel so disconnected worker results can be
697
789
  // replayed to the correct origin/chatId later. Cleared in lifecycle-handler.
698
790
  if (mainManaged) {
699
- setCurrentMainMeta(stripUndefined({
791
+ setCurrentMainMeta(scopeKey, stripUndefined({
700
792
  origin,
701
793
  target: opts.target,
702
794
  chatId: opts.chatId,
703
795
  requestId: opts.requestId,
704
796
  replyViaTarget: opts.replyViaTarget,
705
797
  scopeId: liveScope,
798
+ chatSessionId,
799
+ ...(opts.remoteKey ? { remoteKey: opts.remoteKey } : {}),
706
800
  }));
707
801
  }
708
802
  let resolve;
709
803
  const resultPromise = new Promise(r => { resolve = r; });
710
804
  const session = getSession() ?? {};
711
- const ownerGeneration = getSessionOwnershipGeneration();
805
+ const persistenceOwner = getSessionOwnershipGeneration(scopeKey);
806
+ const ownerGeneration = persistenceOwner.global;
807
+ if (mainRun)
808
+ mainRun.ownerGeneration = ownerGeneration;
712
809
  let cli = resolveMainCli(opts.cli, settings, session);
810
+ if (mainRun)
811
+ mainRun.meta.cli = cli;
713
812
  // Phase 52: Bootstrap consumption is moved BELOW the bucket-aware `isResume`
714
813
  // computation so we can use the authoritative per-bucket resume decision
715
814
  // instead of the legacy `isResumeGuess` heuristic. See comment near line 762.
716
815
  // ─── Fallback retry: skip to fallback if retries exhausted ───
717
816
  if (!opts._isFallback && !opts.internal) {
718
- const st = queueCtrl.fallbackState.get(cli);
719
- if (st && st.retriesLeft <= 0) {
817
+ const st = queueCtrl.fallbackStateForScope(scopeKey).get(cli);
818
+ if (st?.fallbackCli && st.retriesLeft <= 0) {
720
819
  const fbAvail = detectCli(st.fallbackCli)?.available;
721
820
  if (fbAvail) {
722
821
  console.log(`[jaw:fallback] ${cli} retries exhausted → direct ${st.fallbackCli}`);
@@ -730,8 +829,9 @@ export function spawnAgent(prompt, opts = {}) {
730
829
  // ─── jwc in-process branch (110.3 §B) ───────────────────────────────
731
830
  // Resident engine, no ChildProcess. Mirrors the main-managed lifecycle
732
831
  // (insertMessage → beginLiveRun → run → persist → clearLiveRun → processQueue)
733
- // so isAgentBusy()/queue/SSE behave identically. Employees fall through.
832
+ // so scoped busy/queue/SSE behave identically. Employees fall through.
734
833
  if (cli === 'jwc' && mainManaged && !opts.internal) {
834
+ const jawRuntime = runtimeForScope(scopeKey);
735
835
  const jwcLabel = 'main';
736
836
  const jwcOverrides = settings["activeOverrides"]?.['jwc'];
737
837
  const jwcPerCli = settings["perCli"]?.['jwc'];
@@ -739,9 +839,9 @@ export function spawnAgent(prompt, opts = {}) {
739
839
  const jwcProvider = jwcOverrides?.['provider'] || jwcPerCli?.['provider'] || 'anthropic';
740
840
  const jwcCwd = settings["workingDir"] || process.cwd();
741
841
  if (!opts._skipInsert) {
742
- insertMessage.run('user', prompt, 'jwc', jwcModel, settings["workingDir"] || null, getActiveChatSession());
842
+ insertMessage.run('user', prompt, 'jwc', jwcModel, settings["workingDir"] || null, chatSessionId);
743
843
  }
744
- mainSpawnStarting = true;
844
+ mainRun.starting = true;
745
845
  beginLiveRun(liveScope, 'jwc');
746
846
  broadcast('agent_status', { running: true, agentId: jwcLabel, cli: 'jwc' });
747
847
  const jwcEffort = jwcOverrides?.['effort'] || jwcPerCli?.['effort'] || '';
@@ -753,9 +853,9 @@ export function spawnAgent(prompt, opts = {}) {
753
853
  const rawFinalText = result.code === 0 ? live.text : result.text;
754
854
  const finalText = applyOutputPolicy(rawFinalText, { scope: 'main' }).text;
755
855
  // Persist may throw (better-sqlite3 is sync: DB lock / schema). Cleanup MUST
756
- // still run or mainSpawnStarting sticks true and the jwc queue deadlocks.
856
+ // still run or this scope's starting flag stays true and its queue deadlocks.
757
857
  try {
758
- insertMessageWithTraceRun.run('assistant', finalText, 'jwc', jwcModel, null, JSON.stringify(sanitizeToolLogForDurableStorage(live.toolLog)), settings["workingDir"] || null, live.traceRunId || null, getActiveChatSession());
858
+ insertMessageWithTraceRun.run('assistant', finalText, 'jwc', jwcModel, null, JSON.stringify(sanitizeToolLogForDurableStorage(live.toolLog)), settings["workingDir"] || null, live.traceRunId || null, chatSessionId);
759
859
  broadcast('agent_done', { text: finalText, origin, ...(result.code === 0 ? {} : { error: true }) });
760
860
  }
761
861
  catch (err) {
@@ -765,10 +865,11 @@ export function spawnAgent(prompt, opts = {}) {
765
865
  finally {
766
866
  clearLiveRun(liveScope);
767
867
  broadcast('agent_status', { running: false, agentId: jwcLabel, cli: 'jwc' });
768
- mainSpawnStarting = false;
868
+ mainRun.starting = false;
769
869
  jawRuntime.setLiveScope(undefined);
870
+ releaseMainRun(scopeKey, null, ownerGeneration);
770
871
  resolve({ text: finalText, code: result.code });
771
- processQueue();
872
+ void processQueue(scopeKey);
772
873
  }
773
874
  };
774
875
  // jawRuntime.prompt is designed never to reject, but guard defensively so a
@@ -798,14 +899,18 @@ export function spawnAgent(prompt, opts = {}) {
798
899
  ? migrateLegacyClaudeValue(requestedModel)
799
900
  : requestedModel;
800
901
  const runtimeModel = cli === 'cursor' ? resolveCursorModelVariant(model, effort) : model;
902
+ const codexMultiplexMain = cli === 'codex-app' && mainManaged && !opts.agentId
903
+ && settings["runtime"]?.codexApp?.multiplex === true;
801
904
  if (mainManaged) {
802
- setCurrentMainMeta(stripUndefined({
905
+ setCurrentMainMeta(scopeKey, stripUndefined({
803
906
  origin,
804
907
  target: opts.target,
805
908
  chatId: opts.chatId,
806
909
  requestId: opts.requestId,
807
910
  replyViaTarget: opts.replyViaTarget,
808
911
  scopeId: liveScope,
912
+ chatSessionId,
913
+ ...(opts.remoteKey ? { remoteKey: opts.remoteKey } : {}),
809
914
  cli,
810
915
  model: runtimeModel,
811
916
  effectiveProvider,
@@ -821,7 +926,12 @@ export function spawnAgent(prompt, opts = {}) {
821
926
  // Bucket-aware resume: codex-spark is kept in its own session bucket so
822
927
  // cross-model resume (gpt-5.4 ↔ gpt-5.3-codex-spark) doesn't send a
823
928
  // mismatched session_id to the server.
824
- const currentBucket = resolveSessionBucket(cli, runtimeModel, effectiveProvider);
929
+ // Every runtime now keys its bucket by scope (073 §2.1), which replaces the guard 072
930
+ // put here. That guard gave a non-default scope no bucket at all — no resume, no
931
+ // snapshot, no stale clear — because sharing one was worse. Having its own is better
932
+ // than either. The default scope keeps the bare bucket name, so a session that existed
933
+ // before this change continues the conversation it was already in.
934
+ const currentBucket = resolveScopedSessionBucket(cli, runtimeModel, effectiveProvider, scopeKey, effort, 'fallback', codexMultiplexMain);
825
935
  const envDefaultsCli = cli === 'ai-e' ? effectiveProvider : cli;
826
936
  const cliEnv = applyCliEnvDefaults(envDefaultsCli, opts.env);
827
937
  const spawnEnv = makeCleanEnv(cliEnv);
@@ -839,7 +949,7 @@ export function spawnAgent(prompt, opts = {}) {
839
949
  sessionId: bucketSessionId, bucketUpdatedAt, requestedModel: runtimeModel, bucketModel,
840
950
  cwd: settings['workingDir'] || '', lastRunCwd: bucketRow?.last_run_cwd,
841
951
  lastRunClean: bucketRow?.last_run_clean, lastRunMeta: bucketRow?.last_run_meta,
842
- freshBootstrap: forceNew || opts._skipResume === true || Boolean(peekPendingBootstrapPrompt()),
952
+ freshBootstrap: forceNew || opts._skipResume === true || Boolean(peekPendingBootstrapPrompt(scopeKey)),
843
953
  });
844
954
  if (cli === 'agy')
845
955
  console.log(`[agy-resume] ${agyResumeDecision.ok ? 'resume' : 'fresh'} reason=${agyResumeDecision.reason}`);
@@ -859,16 +969,20 @@ export function spawnAgent(prompt, opts = {}) {
859
969
  // Using `isResume` (bucket-aware) instead of legacy `isResumeGuess` so cross-model
860
970
  // toggles (e.g. gpt-5.4 ↔ gpt-5.3-codex-spark) get the bootstrap they need.
861
971
  if (!opts.agentId && !opts.internal && !isResume) {
862
- const pending = consumePendingBootstrapPrompt();
972
+ const pending = consumePendingBootstrapPrompt(scopeKey);
863
973
  if (pending) {
864
974
  console.log(`[jaw:compact] injecting bootstrap (${pending.length} chars)`);
865
975
  prompt = `${pending}\n\n---\n\n${prompt}`;
866
976
  }
867
977
  }
868
978
  if (!empSid && !forceNew && bucketSessionId && !canResumeBucketSession) {
869
- if (!peekPendingBootstrapPrompt()) {
979
+ if (!peekPendingBootstrapPrompt(scopeKey)) {
870
980
  import('../core/compact.js')
871
- .then(({ autoCompactRefresh }) => autoCompactRefresh({ workDir: settings["workingDir"] || null, instructions: '', cli, model }))
981
+ .then(({ autoCompactRefresh }) => autoCompactRefresh({
982
+ workDir: settings["workingDir"] || null, instructions: '', cli, model: runtimeModel, scopeKey,
983
+ chatSessionId,
984
+ ...(currentBucket ? { sessionBucket: currentBucket } : {}),
985
+ }))
872
986
  .catch(() => { });
873
987
  }
874
988
  try {
@@ -934,9 +1048,14 @@ export function spawnAgent(prompt, opts = {}) {
934
1048
  prompt = `${ts}\n${projLine}${prompt}${memoryNudge}`;
935
1049
  }
936
1050
  const resumeSessionId = empSid || (isResume ? bucketSessionId : null);
937
- const needsHistory = !opts._skipHistory && (!isResume || cli === 'pi');
1051
+ const needsHistory = shouldBuildHistoryBlock({
1052
+ skipHistory: opts._skipHistory === true,
1053
+ isResume,
1054
+ cli,
1055
+ codexMultiplexMain,
1056
+ });
938
1057
  const historyBlock = needsHistory
939
- ? buildHistoryBlock(prompt, settings["workingDir"], 10, 8000)
1058
+ ? buildHistoryBlock(prompt, settings["workingDir"], chatSessionId, 10, 8000)
940
1059
  : '';
941
1060
  let agyBootstrap = null;
942
1061
  let promptForArgs = buildPromptForArgs({
@@ -948,10 +1067,10 @@ export function spawnAgent(prompt, opts = {}) {
948
1067
  isResume,
949
1068
  });
950
1069
  const agyResumeReplayPrefix = cli === 'agy' && isResume
951
- ? getLatestAssistantContentForAgyResume(settings["workingDir"])
1070
+ ? getLatestAssistantContentForAgyResume(settings["workingDir"], chatSessionId)
952
1071
  : null;
953
1072
  const agyResumeReplayPrefixes = cli === 'agy' && isResume
954
- ? getRecentAssistantContentsForAgyResume(settings["workingDir"])
1073
+ ? getRecentAssistantContentsForAgyResume(settings["workingDir"], chatSessionId)
955
1074
  : [];
956
1075
  const claudeBin = (cli === 'claude-e' || (cli === 'ai-e' && effectiveProvider === 'claude'))
957
1076
  ? detectCli('claude').path
@@ -1045,8 +1164,8 @@ export function spawnAgent(prompt, opts = {}) {
1045
1164
  promptChars: promptForArgs.length + (sysPrompt?.length || 0),
1046
1165
  prompt: `${sysPrompt || ''}\n${promptForArgs}`,
1047
1166
  });
1048
- if (policyVerdicts.length && currentMainMeta)
1049
- currentMainMeta.policyVerdicts = policyVerdicts;
1167
+ if (policyVerdicts.length && mainRun)
1168
+ mainRun.meta.policyVerdicts = policyVerdicts;
1050
1169
  // ─── DIFF-A: Preflight — verify CLI binary exists before spawn ───
1051
1170
  const detected = detectCli(cli);
1052
1171
  const resolvedOpencodeBinary = cli === 'opencode'
@@ -1062,8 +1181,10 @@ export function spawnAgent(prompt, opts = {}) {
1062
1181
  clearLiveRun(liveScope);
1063
1182
  broadcast('agent_done', { text: `❌ ${msg}`, error: true, origin, ...empTag }, isEmployee ? 'internal' : 'public');
1064
1183
  resolve({ text: '', code: 127 });
1065
- if (mainManaged)
1066
- processQueue();
1184
+ if (mainManaged) {
1185
+ releaseMainRun(scopeKey, null, ownerGeneration);
1186
+ void processQueue(scopeKey);
1187
+ }
1067
1188
  cleanupEmployeeTmpDir(spawnCwd, settings["workingDir"], agentLabel);
1068
1189
  return { child: null, promise: resultPromise };
1069
1190
  }
@@ -1113,8 +1234,9 @@ export function spawnAgent(prompt, opts = {}) {
1113
1234
  throw new Error('Copilot ACP process was not created');
1114
1235
  }
1115
1236
  if (mainManaged)
1116
- activeProcess = child;
1117
- registerActiveProcess(agentLabel, child);
1237
+ mainRun.process = child;
1238
+ else
1239
+ registerActiveProcess(agentLabel, child);
1118
1240
  if (!opts.internal)
1119
1241
  broadcast('agent_status', { running: true, agentId: agentLabel, cli, ...empTag });
1120
1242
  if (mainManaged && !opts.internal)
@@ -1129,19 +1251,21 @@ export function spawnAgent(prompt, opts = {}) {
1129
1251
  opts.lifecycle?.onExit?.(null);
1130
1252
  const msg = `Copilot ACP spawn failed: ${err.message}`;
1131
1253
  console.error(`[acp:error] ${msg}`);
1132
- activeProcesses.delete(agentLabel);
1133
1254
  if (mainManaged) {
1134
- activeProcess = null;
1255
+ releaseMainRun(scopeKey, child, ownerGeneration);
1135
1256
  clearLiveRun(liveScope);
1136
1257
  broadcast('agent_status', { running: false, agentId: agentLabel });
1137
1258
  }
1259
+ else {
1260
+ activeProcesses.delete(agentLabel);
1261
+ }
1138
1262
  broadcast('agent_done', { text: `❌ ${msg}`, error: true, origin, ...empTag }, isEmployee ? 'internal' : 'public');
1139
1263
  resolve({ text: '', code: 1 });
1140
1264
  if (mainManaged)
1141
- processQueue();
1265
+ void processQueue(scopeKey);
1142
1266
  });
1143
1267
  if (mainManaged && !opts.internal && !opts._skipInsert) {
1144
- insertMessage.run('user', prompt, cli, model, settings["workingDir"] || null, getActiveChatSession());
1268
+ insertMessage.run('user', prompt, cli, model, settings["workingDir"] || null, chatSessionId);
1145
1269
  }
1146
1270
  if (!opts.internal)
1147
1271
  broadcast('agent_status', { status: 'running', cli, agentId: agentLabel, ...empTag }, traceAudience);
@@ -1317,7 +1441,7 @@ export function spawnAgent(prompt, opts = {}) {
1317
1441
  }
1318
1442
  // If loadSession failed (or not resuming), inject history into prompt
1319
1443
  const needsHistoryFallback = isResume && !loadSessionOk;
1320
- const fallbackHistory = needsHistoryFallback && !opts._skipHistory ? buildHistoryBlock(prompt, settings["workingDir"]) : '';
1444
+ const fallbackHistory = needsHistoryFallback && !opts._skipHistory ? buildHistoryBlock(prompt, settings["workingDir"], chatSessionId) : '';
1321
1445
  const acpPrompt = needsHistoryFallback
1322
1446
  ? withHistoryPrompt(prompt, fallbackHistory)
1323
1447
  : (isResume ? prompt : withHistoryPrompt(prompt, historyBlock));
@@ -1330,7 +1454,8 @@ export function spawnAgent(prompt, opts = {}) {
1330
1454
  // which skips the exit handler's code===0 gate, losing session continuity.
1331
1455
  const persistedAcpSessionId = ctx.sessionId;
1332
1456
  if (persistedAcpSessionId && persistMainSession(stripUndefined({
1333
- ownerGeneration,
1457
+ persistenceOwner,
1458
+ scopeKey,
1334
1459
  forceNew,
1335
1460
  employeeSessionId: empSid,
1336
1461
  sessionId: persistedAcpSessionId,
@@ -1340,6 +1465,11 @@ export function spawnAgent(prompt, opts = {}) {
1340
1465
  resumeKey,
1341
1466
  effort: cfg.effort || '',
1342
1467
  skipSessionPersist: opts._skipSessionPersist === true,
1468
+ // Without this the save falls back to the bare, unscoped bucket, and a
1469
+ // successful turn in another session writes its vendor id into the row
1470
+ // the default session resumes from. The exit handler below passes the
1471
+ // same value; this path runs first and must not disagree with it.
1472
+ scopedBucket: currentBucket,
1343
1473
  }))) {
1344
1474
  console.log(`[jaw:session] saved ${cli} session=${persistedAcpSessionId.slice(0, 12)}... (pre-shutdown)`);
1345
1475
  }
@@ -1376,14 +1506,18 @@ export function spawnAgent(prompt, opts = {}) {
1376
1506
  handleAgentExit({
1377
1507
  ctx, code: acpCode, cli, model, agentLabel, mainManaged, origin,
1378
1508
  resumeKey,
1379
- prompt, opts, cfg, ownerGeneration, forceNew, empSid,
1509
+ prompt, opts, cfg, ownerGeneration, persistenceOwner, forceNew, empSid,
1380
1510
  isResume, wasKilled, wasSteer, smokeResult,
1381
1511
  effortDefault: '', costLine: '',
1382
1512
  resolve: resolve,
1383
1513
  activeProcesses,
1384
- setActiveProcess: (v) => { activeProcess = v; },
1385
- retryState: queueCtrl.retryState,
1386
- fallbackState: queueCtrl.fallbackState,
1514
+ scopeKey,
1515
+ scopedBucket: currentBucket,
1516
+ chatSessionId,
1517
+ childProcess: child,
1518
+ releaseMainRun,
1519
+ retryState: queueCtrl.retryStateForScope(scopeKey),
1520
+ fallbackState: queueCtrl.fallbackStateForScope(scopeKey),
1387
1521
  fallbackMaxRetries: FALLBACK_MAX_RETRIES,
1388
1522
  processQueue,
1389
1523
  }).catch((err) => {
@@ -1515,8 +1649,8 @@ export function spawnAgent(prompt, opts = {}) {
1515
1649
  return leaseCancel;
1516
1650
  };
1517
1651
  const cancelHook = (_reason) => { void requestCancel(); };
1518
- if (lease)
1519
- cancelActivePiTurn = cancelHook;
1652
+ if (lease && mainRun)
1653
+ mainRun.cancelTurn = cancelHook;
1520
1654
  const piWatchdog = attachWatchdog(child, agentLabel, (reason) => {
1521
1655
  console.log(`[jaw:watchdog] cancelling ${agentLabel} (pi) — ${reason}`);
1522
1656
  ctx.stallReason = reason;
@@ -1524,8 +1658,9 @@ export function spawnAgent(prompt, opts = {}) {
1524
1658
  });
1525
1659
  ctx.stallWatchdog = piWatchdog;
1526
1660
  if (mainManaged)
1527
- activeProcess = child;
1528
- registerActiveProcess(agentLabel, child);
1661
+ mainRun.process = child;
1662
+ else
1663
+ registerActiveProcess(agentLabel, child);
1529
1664
  if (!opts.internal)
1530
1665
  broadcast('agent_status', { running: true, agentId: agentLabel, cli, provider: profile.id, ...empTag });
1531
1666
  if (mainManaged && !opts.internal) {
@@ -1533,15 +1668,15 @@ export function spawnAgent(prompt, opts = {}) {
1533
1668
  setLiveRunTraceId(liveScope, traceRunId);
1534
1669
  }
1535
1670
  if (mainManaged && !opts.internal && !opts._skipInsert) {
1536
- insertMessage.run('user', prompt, cli, runtimeModel, settings["workingDir"] || null, getActiveChatSession());
1671
+ insertMessage.run('user', prompt, cli, runtimeModel, settings["workingDir"] || null, chatSessionId);
1537
1672
  }
1538
1673
  if (!opts.internal)
1539
1674
  broadcast('agent_status', { status: 'running', cli, agentId: agentLabel, provider: profile.id, ...empTag }, traceAudience);
1540
1675
  const releaseLease = async () => {
1541
1676
  if (leaseCancel)
1542
1677
  await leaseCancel;
1543
- if (cancelActivePiTurn === cancelHook)
1544
- cancelActivePiTurn = null;
1678
+ if (mainRun?.cancelTurn === cancelHook)
1679
+ delete mainRun.cancelTurn;
1545
1680
  if (lease)
1546
1681
  lease.release();
1547
1682
  else
@@ -1568,14 +1703,18 @@ export function spawnAgent(prompt, opts = {}) {
1568
1703
  return handleAgentExit({
1569
1704
  ctx, code: result.code, cli, model: runtimeModel, effectiveProvider: profile.id, agentLabel, mainManaged, origin,
1570
1705
  resumeKey,
1571
- prompt, opts, cfg, ownerGeneration, forceNew, empSid,
1706
+ prompt, opts, cfg, ownerGeneration, persistenceOwner, forceNew, empSid,
1572
1707
  isResume: false, wasKilled, wasSteer, smokeResult,
1573
1708
  effortDefault: 'medium', costLine: '',
1574
1709
  resolve: resolve,
1575
1710
  activeProcesses,
1576
- setActiveProcess: (v) => { activeProcess = v; },
1577
- retryState: queueCtrl.retryState,
1578
- fallbackState: queueCtrl.fallbackState,
1711
+ scopeKey,
1712
+ scopedBucket: currentBucket,
1713
+ chatSessionId,
1714
+ childProcess: child,
1715
+ releaseMainRun,
1716
+ retryState: queueCtrl.retryStateForScope(scopeKey),
1717
+ fallbackState: queueCtrl.fallbackStateForScope(scopeKey),
1579
1718
  fallbackMaxRetries: FALLBACK_MAX_RETRIES,
1580
1719
  processQueue,
1581
1720
  });
@@ -1588,14 +1727,18 @@ export function spawnAgent(prompt, opts = {}) {
1588
1727
  handleAgentExit({
1589
1728
  ctx, code: 1, cli, model: runtimeModel, effectiveProvider: profile.id, agentLabel, mainManaged, origin,
1590
1729
  resumeKey,
1591
- prompt, opts, cfg, ownerGeneration, forceNew, empSid,
1730
+ prompt, opts, cfg, ownerGeneration, persistenceOwner, forceNew, empSid,
1592
1731
  isResume: false, wasKilled: false, wasSteer: false, smokeResult: detectSmokeResponse('', [], 1, cli),
1593
1732
  effortDefault: 'medium', costLine: '',
1594
1733
  resolve: resolve,
1595
1734
  activeProcesses,
1596
- setActiveProcess: (v) => { activeProcess = v; },
1597
- retryState: queueCtrl.retryState,
1598
- fallbackState: queueCtrl.fallbackState,
1735
+ scopeKey,
1736
+ scopedBucket: currentBucket,
1737
+ chatSessionId,
1738
+ childProcess: child,
1739
+ releaseMainRun,
1740
+ retryState: queueCtrl.retryStateForScope(scopeKey),
1741
+ fallbackState: queueCtrl.fallbackStateForScope(scopeKey),
1599
1742
  fallbackMaxRetries: FALLBACK_MAX_RETRIES,
1600
1743
  processQueue,
1601
1744
  }).catch((handleErr) => {
@@ -1620,10 +1763,10 @@ export function spawnAgent(prompt, opts = {}) {
1620
1763
  .update(profile.apiKey || '')
1621
1764
  .digest('hex')
1622
1765
  .slice(0, 12);
1623
- mainSpawnStarting = true;
1766
+ mainRun.starting = true;
1624
1767
  void acquirePiRuntime({
1625
1768
  key: {
1626
- scopeKey: `chat:${getActiveChatSession()}`,
1769
+ scopeKey,
1627
1770
  cwd: spawnCwd,
1628
1771
  profileId: profile.id,
1629
1772
  fullEndpoint: profile.endpoint,
@@ -1637,20 +1780,21 @@ export function spawnAgent(prompt, opts = {}) {
1637
1780
  instructions: piSysPrompt,
1638
1781
  forceNew,
1639
1782
  }).then((lease) => {
1640
- mainSpawnStarting = false;
1783
+ mainRun.starting = false;
1641
1784
  ctx.sessionId = lease.session.sessionId;
1642
1785
  console.log(`[jaw:pi:pool] reused=${lease.reused} sessionId=${lease.session.sessionId || 'new'}`);
1643
1786
  const done = lease.session.sendPrompt(piPrompt, { effort, onEvent: onPiEvent })
1644
1787
  .then((result) => ({ ...result, code: 0, sessionId: lease.session.sessionId }));
1645
1788
  runPiTurn(lease.session.child, done, lease);
1646
1789
  }).catch((err) => {
1647
- mainSpawnStarting = false;
1790
+ mainRun.starting = false;
1648
1791
  console.error(`[jaw:pi:pool] acquire failed: ${err.message}`);
1649
1792
  clearLiveRun(liveScope);
1650
1793
  broadcast('agent_status', { running: false, agentId: agentLabel });
1651
1794
  broadcast('agent_done', { text: `❌ Pi RPC acquire failed: ${err.message}`, error: true, origin }, 'public');
1795
+ releaseMainRun(scopeKey, null, ownerGeneration);
1652
1796
  resolve({ text: '', code: 1 });
1653
- processQueue();
1797
+ void processQueue(scopeKey);
1654
1798
  });
1655
1799
  return { child: null, promise: resultPromise };
1656
1800
  }
@@ -1664,7 +1808,7 @@ export function spawnAgent(prompt, opts = {}) {
1664
1808
  }
1665
1809
  }
1666
1810
  if (mainManaged && !opts.internal && !opts._skipInsert) {
1667
- insertMessage.run('user', prompt, cli, model, settings["workingDir"] || null, getActiveChatSession());
1811
+ insertMessage.run('user', prompt, cli, model, settings["workingDir"] || null, chatSessionId);
1668
1812
  }
1669
1813
  if (!opts.internal)
1670
1814
  broadcast('agent_status', { status: 'running', cli, agentId: agentLabel, ...empTag }, traceAudience);
@@ -1711,13 +1855,7 @@ export function spawnAgent(prompt, opts = {}) {
1711
1855
  settleTurn = resolveTurn;
1712
1856
  rejectTurn = rejectTurnPromise;
1713
1857
  });
1714
- const handleNotification = (method, params) => {
1715
- markCodexProgress();
1716
- if (method === 'turn/completed' || method === 'turn/started' || method === 'error') {
1717
- console.log(`[codex-app:notify] ${method}`);
1718
- }
1719
- appendTraceEvent({ runId: ctx.traceRunId, source: 'codex_app_raw', eventType: method, raw: params });
1720
- const parsed = extractFromCodexAppEvent(method, params, ctx);
1858
+ const consumeCodexAppEvent = (method, parsed) => {
1721
1859
  if (!parsed) {
1722
1860
  if (method === 'turn/completed')
1723
1861
  settleTurn();
@@ -1748,7 +1886,11 @@ export function spawnAgent(prompt, opts = {}) {
1748
1886
  }
1749
1887
  if (parsed.text) {
1750
1888
  flushCodexAppThinking();
1751
- const segment = appendAssistantTextSegment(ctx, parsed.text);
1889
+ // codex-app streams item/agentMessage/delta at TOKEN granularity;
1890
+ // the segment formatter would inject "\n- " between unjoined
1891
+ // tokens ("이"+"지만" → "이\n- 지만"). Raw-append like the plain
1892
+ // `claude` text_delta path (events/index.ts) instead.
1893
+ const segment = appendAssistantRawText(ctx, parsed.text);
1752
1894
  if (segment) {
1753
1895
  broadcastAgentOutput(ctx, agentLabel, cli, segment, empTag, traceAudience);
1754
1896
  lastVisibleBroadcastTs = Date.now();
@@ -1786,13 +1928,14 @@ export function spawnAgent(prompt, opts = {}) {
1786
1928
  }
1787
1929
  };
1788
1930
  const effectiveFastMode = cfg.fastMode ?? settings["perCli"]?.["codex"]?.fastMode ?? false;
1789
- const runCodexAppTurn = async (appClient, lease) => {
1931
+ const runCodexAppTurn = async (appClient, lease, laneScope) => {
1790
1932
  const child = appClient.proc;
1791
1933
  if (!child)
1792
1934
  throw new Error('Codex AppServer process was not created');
1793
1935
  if (mainManaged)
1794
- activeProcess = child;
1795
- registerActiveProcess(agentLabel, child);
1936
+ mainRun.process = child;
1937
+ else
1938
+ registerActiveProcess(agentLabel, child);
1796
1939
  if (!opts.internal)
1797
1940
  broadcast('agent_status', { running: true, agentId: agentLabel, cli, ...empTag });
1798
1941
  const processExit = { value: null };
@@ -1813,8 +1956,8 @@ export function spawnAgent(prompt, opts = {}) {
1813
1956
  return leaseCancel;
1814
1957
  };
1815
1958
  const cancelHook = (_reason) => { void requestLeaseCancel(); };
1816
- if (lease)
1817
- cancelActiveCodexAppTurn = cancelHook;
1959
+ if (lease && mainRun)
1960
+ mainRun.cancelTurn = cancelHook;
1818
1961
  const watchdogTimeout = (kind) => {
1819
1962
  if (watchdogCancel)
1820
1963
  return;
@@ -1829,8 +1972,23 @@ export function spawnAgent(prompt, opts = {}) {
1829
1972
  idleTimer = setTimeout(() => { watchdogTimeout('idle'); }, idleMs);
1830
1973
  absoluteTimer = setTimeout(() => { watchdogTimeout('absolute'); }, absoluteMs);
1831
1974
  markCodexProgress = resetIdleTimer;
1832
- const scope = appClient.listenTurn({
1833
- onNotification: handleNotification,
1975
+ const listener = listenCodexAppTurnAdapter(appClient, lease, laneScope, ctx, {
1976
+ onProgress: () => { markCodexProgress(); },
1977
+ onRawNotification: (method, params) => {
1978
+ if (method === 'turn/completed' || method === 'turn/started' || method === 'error') {
1979
+ console.log(`[codex-app:notify] ${method}`);
1980
+ }
1981
+ appendTraceEvent({ runId: ctx.traceRunId, source: 'codex_app_raw', eventType: method, raw: params });
1982
+ },
1983
+ onDiagnosticNotification: (entry) => {
1984
+ appendTraceEvent({
1985
+ runId: ctx.traceRunId,
1986
+ source: 'codex_app_raw',
1987
+ eventType: 'unrouted-notification',
1988
+ raw: entry,
1989
+ });
1990
+ },
1991
+ onEvent: consumeCodexAppEvent,
1834
1992
  onStderr: handleStderr,
1835
1993
  onExit: (code, signal) => {
1836
1994
  processExit.value = { code, signal };
@@ -1850,9 +2008,16 @@ export function spawnAgent(prompt, opts = {}) {
1850
2008
  const initResult = await appClient.initialize();
1851
2009
  if (process.env["DEBUG"])
1852
2010
  console.log('[codex-app:init]', JSON.stringify(initResult).slice(0, 200));
2011
+ const threadOptions = {
2012
+ model,
2013
+ effort,
2014
+ cwd: spawnCwd,
2015
+ fastMode: effectiveFastMode,
2016
+ instructions: sysPrompt,
2017
+ };
1853
2018
  if (isResume && resumeSessionId) {
1854
2019
  try {
1855
- await appClient.resumeThread(resumeSessionId, { instructions: sysPrompt });
2020
+ await appClient.resumeThread(laneScope, resumeSessionId, threadOptions);
1856
2021
  console.log(`[codex-app:session] resumeThread OK: ${resumeSessionId.slice(0, 12)}...`);
1857
2022
  }
1858
2023
  catch (resumeErr) {
@@ -1862,27 +2027,29 @@ export function spawnAgent(prompt, opts = {}) {
1862
2027
  console.warn(`[codex-app:session] resumeThread FAILED (recoverable): ${message} — starting new thread`);
1863
2028
  if (empSid && opts.agentId)
1864
2029
  clearEmployeeSession.run(opts.agentId);
1865
- await appClient.startThread({ instructions: sysPrompt, cwd: spawnCwd });
2030
+ await appClient.startThread(laneScope, threadOptions);
1866
2031
  }
1867
2032
  }
1868
2033
  else {
1869
- await appClient.startThread({ instructions: sysPrompt, cwd: spawnCwd });
2034
+ await appClient.startThread(laneScope, threadOptions);
1870
2035
  }
1871
- ctx.sessionId = appClient.threadId;
2036
+ ctx.sessionId = appClient.getThreadId(laneScope) ?? '';
1872
2037
  }
1873
2038
  const shouldPrependHistory = lease
1874
- ? !lease.resumedThread
2039
+ ? !(lease.resumedThread || lease.reused)
1875
2040
  : !(isResume && Boolean(resumeSessionId));
1876
2041
  const codexAppPrompt = (shouldPrependHistory && historyBlock)
1877
2042
  ? `${historyBlock}\n\n[User Message]\n${prompt}`
1878
2043
  : prompt;
1879
- await Promise.race([appClient.startTurn(codexAppPrompt), turnDone]);
2044
+ const startTurn = appClient.startTurn(laneScope, codexAppPrompt);
2045
+ await Promise.race([startTurn, turnDone]);
1880
2046
  await turnDone;
1881
2047
  turnCompleted = !turnReportedFailure;
1882
2048
  flushCodexAppThinking();
1883
- const persistedThreadId = lease?.threadId ?? appClient.threadId;
2049
+ const persistedThreadId = lease?.threadId ?? appClient.getThreadId(laneScope);
1884
2050
  if (persistedThreadId && persistMainSession(stripUndefined({
1885
- ownerGeneration,
2051
+ persistenceOwner,
2052
+ scopeKey,
1886
2053
  forceNew,
1887
2054
  employeeSessionId: empSid,
1888
2055
  sessionId: persistedThreadId,
@@ -1892,6 +2059,11 @@ export function spawnAgent(prompt, opts = {}) {
1892
2059
  resumeKey,
1893
2060
  effort: cfg.effort || '',
1894
2061
  skipSessionPersist: opts._skipSessionPersist === true,
2062
+ ...(lease?.bucketKey ? { codexAppBucket: lease.bucketKey } : {}),
2063
+ // With multiplex off there is no lease bucket key, and without this the
2064
+ // save lands on the bare `codex-app` row that belongs to the default
2065
+ // session. codex-app is the default runtime, so that is the common case.
2066
+ scopedBucket: currentBucket,
1895
2067
  }))) {
1896
2068
  console.log(`[jaw:session] saved ${cli} session=${persistedThreadId.slice(0, 12)}... (pre-shutdown)`);
1897
2069
  }
@@ -1909,9 +2081,9 @@ export function spawnAgent(prompt, opts = {}) {
1909
2081
  markCodexProgress = () => { };
1910
2082
  if (watchdogCancel)
1911
2083
  await watchdogCancel;
1912
- if (cancelActiveCodexAppTurn === cancelHook)
1913
- cancelActiveCodexAppTurn = null;
1914
- scope.dispose();
2084
+ if (mainRun?.cancelTurn === cancelHook)
2085
+ delete mainRun.cancelTurn;
2086
+ listener.dispose();
1915
2087
  if (lease)
1916
2088
  lease.release();
1917
2089
  else {
@@ -1920,7 +2092,11 @@ export function spawnAgent(prompt, opts = {}) {
1920
2092
  cleanupEmployeeTmpDir(spawnCwd, settings["workingDir"], agentLabel);
1921
2093
  }
1922
2094
  }
1923
- const exitCode = turnCompleted ? 0 : (processExit.value?.code ?? 1);
2095
+ // A turn that never completed is a failure even when the process it
2096
+ // was running on exited cleanly. Trusting the child's status here
2097
+ // reports success for a turn that produced nothing, which is exactly
2098
+ // what happens when a shared host is closed mid-turn.
2099
+ const exitCode = turnCompleted ? 0 : (processExit.value?.code || 1);
1924
2100
  opts.lifecycle?.onExit?.(exitCode);
1925
2101
  const killReason = consumeKillReason(child.pid);
1926
2102
  if (processExit.value && processExit.value.code !== 0 && !killReason) {
@@ -1934,14 +2110,19 @@ export function spawnAgent(prompt, opts = {}) {
1934
2110
  await handleAgentExit({
1935
2111
  ctx, code: exitCode, cli, model, agentLabel, mainManaged, origin,
1936
2112
  resumeKey,
1937
- prompt, opts, cfg, ownerGeneration, forceNew, empSid,
2113
+ prompt, opts, cfg, ownerGeneration, persistenceOwner, forceNew, empSid,
1938
2114
  isResume, wasKilled, wasSteer, smokeResult,
1939
2115
  effortDefault: '', costLine: '',
1940
2116
  resolve: resolve,
1941
2117
  activeProcesses,
1942
- setActiveProcess: (v) => { activeProcess = v; },
1943
- retryState: queueCtrl.retryState,
1944
- fallbackState: queueCtrl.fallbackState,
2118
+ scopeKey,
2119
+ scopedBucket: currentBucket,
2120
+ chatSessionId,
2121
+ ...(lease?.bucketKey ? { codexAppBucket: lease.bucketKey } : {}),
2122
+ childProcess: child,
2123
+ releaseMainRun,
2124
+ retryState: queueCtrl.retryStateForScope(scopeKey),
2125
+ fallbackState: queueCtrl.fallbackStateForScope(scopeKey),
1945
2126
  fallbackMaxRetries: FALLBACK_MAX_RETRIES,
1946
2127
  processQueue,
1947
2128
  }).catch((err) => {
@@ -1949,38 +2130,160 @@ export function spawnAgent(prompt, opts = {}) {
1949
2130
  });
1950
2131
  };
1951
2132
  if (opts.agentId) {
2133
+ const employeeLaneScope = `employee:${opts.agentId}`;
1952
2134
  const appClient = new CodexAppClient({
1953
2135
  binary: detected.path || 'codex', workDir: spawnCwd, env: spawnEnv,
1954
- model, effort, fastMode: effectiveFastMode,
1955
2136
  });
1956
2137
  appClient.spawn();
1957
2138
  const child = appClient.proc;
1958
2139
  if (!child)
1959
2140
  throw new Error('Codex AppServer process was not created');
1960
- void runCodexAppTurn(appClient, null);
2141
+ void runCodexAppTurn(appClient, null, employeeLaneScope);
1961
2142
  return { child, promise: resultPromise };
1962
2143
  }
1963
- mainSpawnStarting = true;
1964
- void acquireCodexAppRuntime({
1965
- binary: detected.path || 'codex', env: spawnEnv,
1966
- key: {
1967
- scopeKey: `chat:${getActiveChatSession()}`,
1968
- cwd: spawnCwd, model, effort, fastMode: effectiveFastMode,
1969
- },
1970
- storedThreadId: resumeSessionId || null,
1971
- instructions: sysPrompt,
1972
- forceNew,
1973
- }).then(async (lease) => {
1974
- mainSpawnStarting = false;
1975
- await runCodexAppTurn(lease.client, lease);
2144
+ mainRun.starting = true;
2145
+ const acquireCodexAppForTurn = async () => {
2146
+ let cancelled = false;
2147
+ let cancelReason = 'user';
2148
+ const cancelThisAcquire = (reason) => {
2149
+ cancelled = true;
2150
+ cancelReason = reason;
2151
+ };
2152
+ const acquireWasCancelled = () => cancelled || activeMainProcesses.get(scopeKey) !== mainRun;
2153
+ try {
2154
+ if (!codexMultiplexMain) {
2155
+ const lease = await acquireCodexAppRuntime({
2156
+ binary: detected.path || 'codex', env: spawnEnv,
2157
+ route: 'legacy',
2158
+ key: {
2159
+ scopeKey,
2160
+ cwd: spawnCwd, model, effort, fastMode: effectiveFastMode,
2161
+ },
2162
+ storedThreadId: resumeSessionId || null,
2163
+ instructions: sysPrompt,
2164
+ forceNew,
2165
+ });
2166
+ return { kind: 'lease', lease };
2167
+ }
2168
+ mainRun.cancelPending = cancelThisAcquire;
2169
+ const waitMs = configuredPositiveMs(process.env["CODEX_APP_ACQUIRE_WAIT_MS"], DEFAULT_CODEX_APP_ACQUIRE_WAIT_MS);
2170
+ const deadlineAt = Date.now() + waitMs;
2171
+ let lastStaleError = null;
2172
+ let staleAttempts = 0;
2173
+ const deadlineError = (stage) => lastStaleError
2174
+ ?? new Error(`Codex App ${stage} timed out after ${waitMs}ms`);
2175
+ const awaitWithinDeadline = async (stage, pending, onLateValue) => {
2176
+ let deadlineWon = false;
2177
+ let timeout;
2178
+ void pending.then((value) => {
2179
+ if (deadlineWon)
2180
+ onLateValue?.(value);
2181
+ }, () => { });
2182
+ const remainingMs = deadlineAt - Date.now();
2183
+ if (remainingMs <= 0) {
2184
+ deadlineWon = true;
2185
+ throw deadlineError(stage);
2186
+ }
2187
+ const deadline = new Promise((_resolveDeadline, rejectDeadline) => {
2188
+ timeout = setTimeout(() => {
2189
+ deadlineWon = true;
2190
+ rejectDeadline(deadlineError(stage));
2191
+ }, remainingMs);
2192
+ });
2193
+ try {
2194
+ return await Promise.race([pending, deadline]);
2195
+ }
2196
+ finally {
2197
+ if (timeout)
2198
+ clearTimeout(timeout);
2199
+ }
2200
+ };
2201
+ for (;;) {
2202
+ if (acquireWasCancelled())
2203
+ return { kind: 'cancelled', reason: cancelReason };
2204
+ // Check the budget before spawning more work. awaitWithinDeadline()
2205
+ // only measures what remains once the promise already exists, so a
2206
+ // backoff that consumed the last of the budget would still get to
2207
+ // start one more prepare.
2208
+ if (deadlineAt - Date.now() <= 0)
2209
+ throw lastStaleError ?? deadlineError('prepare');
2210
+ try {
2211
+ const prepared = await awaitWithinDeadline('prepare', prepareCodexAppHost({
2212
+ binary: detected.path || 'codex', cwd: spawnCwd,
2213
+ fastMode: effectiveFastMode, env: spawnEnv, model, effort,
2214
+ }));
2215
+ if (acquireWasCancelled())
2216
+ return { kind: 'cancelled', reason: cancelReason };
2217
+ const lease = await awaitWithinDeadline('acquire', acquireCodexAppLane(prepared, {
2218
+ scopeKey,
2219
+ bucketKey: currentBucket,
2220
+ storedThreadId: resumeSessionId || null,
2221
+ instructions: sysPrompt,
2222
+ forceNew,
2223
+ waitMs: deadlineAt - Date.now(),
2224
+ }), (lateLease) => { lateLease.release(); });
2225
+ if (acquireWasCancelled()) {
2226
+ lease.release();
2227
+ return { kind: 'cancelled', reason: cancelReason };
2228
+ }
2229
+ return { kind: 'lease', lease };
2230
+ }
2231
+ catch (err) {
2232
+ if (!(err instanceof CodexHostGenerationStaleError))
2233
+ throw err;
2234
+ lastStaleError = err;
2235
+ if (acquireWasCancelled())
2236
+ return { kind: 'cancelled', reason: cancelReason };
2237
+ const remainingMs = deadlineAt - Date.now();
2238
+ if (remainingMs <= 0)
2239
+ throw lastStaleError;
2240
+ staleAttempts += 1;
2241
+ const backoffMs = Math.min(remainingMs, CODEX_APP_ACQUIRE_RETRY_BACKOFF_MAX_MS, 25 * staleAttempts);
2242
+ await new Promise((done) => { setTimeout(done, backoffMs); });
2243
+ }
2244
+ }
2245
+ }
2246
+ finally {
2247
+ const latest = activeMainProcesses.get(scopeKey);
2248
+ if (latest?.cancelPending === cancelThisAcquire)
2249
+ delete latest.cancelPending;
2250
+ mainRun.starting = false;
2251
+ }
2252
+ };
2253
+ void acquireCodexAppForTurn().then(async (outcome) => {
2254
+ // A run that never started a turn owns nothing but its own map slot.
2255
+ // releaseMainRun() matches on (process, ownerGeneration), and a pending
2256
+ // run has process=null while sharing the global generation with whatever
2257
+ // replaced it, so calling it here would delete the replacement's entry.
2258
+ // Compare the captured object instead and only drop our own slot.
2259
+ const abandonTurn = (lease) => {
2260
+ lease?.release();
2261
+ finalizeTraceRun(traceRunId, 'interrupted');
2262
+ clearLiveRun(liveScope);
2263
+ broadcast('agent_status', { running: false, agentId: agentLabel });
2264
+ resolve({ text: '', code: -1 });
2265
+ if (activeMainProcesses.get(scopeKey) === mainRun)
2266
+ activeMainProcesses.delete(scopeKey);
2267
+ void processQueue(scopeKey);
2268
+ };
2269
+ if (outcome.kind === 'cancelled') {
2270
+ abandonTurn(null);
2271
+ return;
2272
+ }
2273
+ const lease = outcome.lease;
2274
+ if (activeMainProcesses.get(scopeKey) !== mainRun) {
2275
+ abandonTurn(lease);
2276
+ return;
2277
+ }
2278
+ await runCodexAppTurn(lease.client, lease, lease.laneScope);
1976
2279
  }).catch((err) => {
1977
- mainSpawnStarting = false;
1978
2280
  console.error(`[codex-app:pool] acquire failed: ${err.message}`);
1979
2281
  clearLiveRun(liveScope);
1980
2282
  broadcast('agent_status', { running: false, agentId: agentLabel });
1981
2283
  broadcast('agent_done', { text: `❌ Codex AppServer acquire failed: ${err.message}`, error: true, origin }, 'public');
2284
+ releaseMainRun(scopeKey, null, ownerGeneration);
1982
2285
  resolve({ text: '', code: 1 });
1983
- processQueue();
2286
+ void processQueue(scopeKey);
1984
2287
  });
1985
2288
  return { child: null, promise: resultPromise };
1986
2289
  }
@@ -1997,15 +2300,23 @@ export function spawnAgent(prompt, opts = {}) {
1997
2300
  if (opencodeSpawnAudit) {
1998
2301
  console.log(`[jaw:opencode:audit] ${JSON.stringify(opencodeSpawnAudit)}`);
1999
2302
  }
2000
- const child = spawn(spawnCommand, args, {
2303
+ // The snapshot has to predate the child; the helper owns that ordering (073 §2.4).
2304
+ const kiroPlainText = isKiroPlainTextCli(cli, effectiveProvider);
2305
+ const { child, kiroConversationIdsBefore, kiroSpawnStartedAt } = spawnWithKiroSnapshot({
2306
+ kiroPlainText,
2307
+ isFreshMainRun: !isResume && !empSid,
2001
2308
  cwd: spawnCwd,
2002
- env: spawnEnv,
2003
- stdio: ['pipe', 'pipe', 'pipe'],
2004
- ...(windowsSpawnUsesShell ? { shell: true } : {}),
2309
+ spawn: () => spawn(spawnCommand, args, {
2310
+ cwd: spawnCwd,
2311
+ env: spawnEnv,
2312
+ stdio: ['pipe', 'pipe', 'pipe'],
2313
+ ...(windowsSpawnUsesShell ? { shell: true } : {}),
2314
+ }),
2005
2315
  });
2006
2316
  if (mainManaged)
2007
- activeProcess = child;
2008
- registerActiveProcess(agentLabel, child);
2317
+ mainRun.process = child;
2318
+ else
2319
+ registerActiveProcess(agentLabel, child);
2009
2320
  if (!opts.internal)
2010
2321
  broadcast('agent_status', { running: true, agentId: agentLabel, cli, ...runtimeStatusMeta, ...empTag });
2011
2322
  if (mainManaged && !opts.internal)
@@ -2050,19 +2361,21 @@ export function spawnAgent(prompt, opts = {}) {
2050
2361
  ? `CLI '${cli}' 실행 실패 (ENOEXEC). PATH의 실행 파일이 바이너리 또는 shebang 스크립트가 아닙니다. \`jaw doctor --json\`으로 깨진 shim을 확인하세요.`
2051
2362
  : `CLI '${cli}' 실행 실패: ${err.message}`;
2052
2363
  console.error(`[jaw:${agentLabel}:error] ${msg}`);
2053
- activeProcesses.delete(agentLabel);
2054
2364
  if (mainManaged) {
2055
- activeProcess = null;
2365
+ releaseMainRun(scopeKey, child, ownerGeneration);
2056
2366
  clearLiveRun(liveScope);
2057
2367
  broadcast('agent_status', { running: false, agentId: agentLabel });
2058
2368
  }
2369
+ else {
2370
+ activeProcesses.delete(agentLabel);
2371
+ }
2059
2372
  broadcast('agent_done', { text: `❌ ${msg}`, error: true, origin, ...empTag }, isEmployee ? 'internal' : 'public');
2060
2373
  resolve({ text: '', code: 127 });
2061
2374
  if (mainManaged)
2062
- processQueue();
2375
+ void processQueue(scopeKey);
2063
2376
  });
2064
2377
  if (mainManaged && !opts.internal && !opts._skipInsert) {
2065
- insertMessage.run('user', prompt, cli, runtimeModel, settings["workingDir"] || null, getActiveChatSession());
2378
+ insertMessage.run('user', prompt, cli, runtimeModel, settings["workingDir"] || null, chatSessionId);
2066
2379
  }
2067
2380
  if (cli === 'claude') {
2068
2381
  child.stdin.write(isResume ? prompt : withHistoryPrompt(prompt, historyBlock));
@@ -2082,11 +2395,6 @@ export function spawnAgent(prompt, opts = {}) {
2082
2395
  const traceRunId = startTraceRun({ cli, model: runtimeModel, workingDir: settings["workingDir"] || null, agentLabel, audience: traceAudience });
2083
2396
  if (mainManaged && !opts.internal)
2084
2397
  setLiveRunTraceId(liveScope, traceRunId);
2085
- const kiroPlainText = isKiroPlainTextCli(cli, effectiveProvider);
2086
- const kiroSpawnStartedAt = kiroPlainText ? Date.now() - 1000 : 0;
2087
- const kiroConversationIdsBefore = (kiroPlainText && !isResume && !empSid)
2088
- ? listKiroConversationIdsForCwd(spawnCwd)
2089
- : null;
2090
2398
  // Native `agy --conversation ... -p` may emit only the current answer.
2091
2399
  // Length-based replay trimming can therefore swallow the whole new answer.
2092
2400
  const agyResumeOffset = 0;
@@ -2468,7 +2776,7 @@ export function spawnAgent(prompt, opts = {}) {
2468
2776
  if (cli === 'agy' && isResume && (agyGuardedStaleDetected || isAgyStaleSessionOutput(ctx.fullText))) {
2469
2777
  console.log(`[jaw:agy] stale session detected (Warning: conversation not found) — clearing bucket`);
2470
2778
  try {
2471
- const bucket = resolveSessionBucket(cli, runtimeModel, effectiveProvider);
2779
+ const bucket = currentBucket;
2472
2780
  clearSessionBucket.run(bucket);
2473
2781
  }
2474
2782
  catch (e) {
@@ -2476,9 +2784,10 @@ export function spawnAgent(prompt, opts = {}) {
2476
2784
  }
2477
2785
  ctx.sessionId = null;
2478
2786
  if (agyResumeDecision.ok && !opts._agyStaleFreshRetry) {
2479
- activeProcesses.delete(agentLabel);
2480
2787
  if (mainManaged)
2481
- activeProcess = null;
2788
+ releaseMainRun(scopeKey, child, ownerGeneration);
2789
+ else
2790
+ activeProcesses.delete(agentLabel);
2482
2791
  const { promise: freshPromise } = spawnAgent(prompt, {
2483
2792
  ...opts, _agyStaleFreshRetry: true, _skipResume: true, _skipInsert: true,
2484
2793
  });
@@ -2506,7 +2815,7 @@ export function spawnAgent(prompt, opts = {}) {
2506
2815
  if (isResume && isKiroStaleSessionOutput(ctx.fullText)) {
2507
2816
  console.log('[jaw:kiro] stale session detected in output — clearing bucket');
2508
2817
  try {
2509
- const bucket = resolveSessionBucket(cli, runtimeModel, effectiveProvider);
2818
+ const bucket = currentBucket;
2510
2819
  clearSessionBucket.run(bucket);
2511
2820
  }
2512
2821
  catch (e) {
@@ -2651,14 +2960,18 @@ export function spawnAgent(prompt, opts = {}) {
2651
2960
  handleAgentExit({
2652
2961
  ctx, code: effectiveExitCode, cli, model: runtimeModel, effectiveProvider, agentLabel, mainManaged, origin,
2653
2962
  resumeKey,
2654
- prompt, opts, cfg, ownerGeneration, forceNew, empSid,
2963
+ prompt, opts, cfg, ownerGeneration, persistenceOwner, forceNew, empSid,
2655
2964
  isResume, wasKilled, wasSteer, smokeResult,
2656
2965
  effortDefault: cli === 'grok' ? '' : 'medium', costLine,
2657
2966
  resolve: resolve,
2658
2967
  activeProcesses,
2659
- setActiveProcess: (v) => { activeProcess = v; },
2660
- retryState: queueCtrl.retryState,
2661
- fallbackState: queueCtrl.fallbackState,
2968
+ scopeKey,
2969
+ scopedBucket: currentBucket,
2970
+ chatSessionId,
2971
+ childProcess: child,
2972
+ releaseMainRun,
2973
+ retryState: queueCtrl.retryStateForScope(scopeKey),
2974
+ fallbackState: queueCtrl.fallbackStateForScope(scopeKey),
2662
2975
  fallbackMaxRetries: FALLBACK_MAX_RETRIES,
2663
2976
  processQueue,
2664
2977
  ...(agyTotalOutputLen > 0 ? { outputLen: agyTotalOutputLen } : {}),