cli-jaw 2.17.62 → 2.17.63

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 (360) hide show
  1. package/README.ja.md +17 -17
  2. package/README.ko.md +17 -17
  3. package/README.md +17 -17
  4. package/README.zh-CN.md +17 -17
  5. package/dist/bin/commands/tui/input-handler.js +19 -0
  6. package/dist/bin/commands/tui/input-handler.js.map +1 -1
  7. package/dist/bin/commands/tui/overlays.js +8 -4
  8. package/dist/bin/commands/tui/overlays.js.map +1 -1
  9. package/dist/bin/star-prompt.js +1 -1
  10. package/dist/bin/star-prompt.js.map +1 -1
  11. package/dist/server.js +14 -1
  12. package/dist/server.js.map +1 -1
  13. package/dist/src/agent/claude-runtime-pool.js +2 -1
  14. package/dist/src/agent/claude-runtime-pool.js.map +1 -1
  15. package/dist/src/agent/claude-runtime-run.js +28 -7
  16. package/dist/src/agent/claude-runtime-run.js.map +1 -1
  17. package/dist/src/agent/lifecycle-handler.js +34 -6
  18. package/dist/src/agent/lifecycle-handler.js.map +1 -1
  19. package/dist/src/agent/runtime/claude-sdk-events.js +136 -9
  20. package/dist/src/agent/runtime/claude-sdk-events.js.map +1 -1
  21. package/dist/src/agent/runtime/claude-sdk-history-loader.js +24 -0
  22. package/dist/src/agent/runtime/claude-sdk-history-loader.js.map +1 -0
  23. package/dist/src/agent/runtime/claude-sdk-hooks.js +10 -4
  24. package/dist/src/agent/runtime/claude-sdk-hooks.js.map +1 -1
  25. package/dist/src/agent/runtime/claude-sdk-messages.js +25 -2
  26. package/dist/src/agent/runtime/claude-sdk-messages.js.map +1 -1
  27. package/dist/src/agent/runtime/claude-sdk-options.js +36 -3
  28. package/dist/src/agent/runtime/claude-sdk-options.js.map +1 -1
  29. package/dist/src/agent/runtime/claude-sdk-permissions.js +32 -9
  30. package/dist/src/agent/runtime/claude-sdk-permissions.js.map +1 -1
  31. package/dist/src/agent/runtime/claude-sdk-session.js +165 -17
  32. package/dist/src/agent/runtime/claude-sdk-session.js.map +1 -1
  33. package/dist/src/agent/runtime/claude-sdk-version.js +8 -0
  34. package/dist/src/agent/runtime/claude-sdk-version.js.map +1 -0
  35. package/dist/src/agent/spawn/backend-codex-app.js +619 -0
  36. package/dist/src/agent/spawn/backend-codex-app.js.map +1 -0
  37. package/dist/src/agent/spawn/backend-context.js +2 -0
  38. package/dist/src/agent/spawn/backend-context.js.map +1 -0
  39. package/dist/src/agent/spawn/backend-copilot.js +385 -0
  40. package/dist/src/agent/spawn/backend-copilot.js.map +1 -0
  41. package/dist/src/agent/spawn/backend-native-acp.js +379 -0
  42. package/dist/src/agent/spawn/backend-native-acp.js.map +1 -0
  43. package/dist/src/agent/spawn/backend-pi.js +565 -0
  44. package/dist/src/agent/spawn/backend-pi.js.map +1 -0
  45. package/dist/src/agent/spawn/types.js +2 -0
  46. package/dist/src/agent/spawn/types.js.map +1 -0
  47. package/dist/src/agent/spawn.js +44 -1858
  48. package/dist/src/agent/spawn.js.map +1 -1
  49. package/dist/src/cli/claude-default-model-boot.js +36 -0
  50. package/dist/src/cli/claude-default-model-boot.js.map +1 -0
  51. package/dist/src/cli/claude-model-discovery.js +13 -1
  52. package/dist/src/cli/claude-model-discovery.js.map +1 -1
  53. package/dist/src/cli/claude-models.js +7 -1
  54. package/dist/src/cli/claude-models.js.map +1 -1
  55. package/dist/src/cli/commands.js +5 -15
  56. package/dist/src/cli/commands.js.map +1 -1
  57. package/dist/src/cli/tui/composer.js +3 -3
  58. package/dist/src/cli/tui/composer.js.map +1 -1
  59. package/dist/src/cli/tui/skill-mention.js +15 -0
  60. package/dist/src/cli/tui/skill-mention.js.map +1 -0
  61. package/dist/src/code-mode/manager.js +174 -9
  62. package/dist/src/code-mode/manager.js.map +1 -1
  63. package/dist/src/code-mode/providers/catalog.js +5 -3
  64. package/dist/src/code-mode/providers/catalog.js.map +1 -1
  65. package/dist/src/code-mode/providers/claude-history.js +132 -0
  66. package/dist/src/code-mode/providers/claude-history.js.map +1 -0
  67. package/dist/src/code-mode/providers/claude.js +74 -7
  68. package/dist/src/code-mode/providers/claude.js.map +1 -1
  69. package/dist/src/code-mode/providers/codex-app.js +7 -1
  70. package/dist/src/code-mode/providers/codex-app.js.map +1 -1
  71. package/dist/src/code-mode/session.js +158 -11
  72. package/dist/src/code-mode/session.js.map +1 -1
  73. package/dist/src/code-mode/store.js +416 -28
  74. package/dist/src/code-mode/store.js.map +1 -1
  75. package/dist/src/command-contract/catalog.js +3 -22
  76. package/dist/src/command-contract/catalog.js.map +1 -1
  77. package/dist/src/core/codex-config.js +3 -62
  78. package/dist/src/core/codex-config.js.map +1 -1
  79. package/dist/src/core/config.js +116 -14
  80. package/dist/src/core/config.js.map +1 -1
  81. package/dist/src/core/runtime-settings.js +20 -10
  82. package/dist/src/core/runtime-settings.js.map +1 -1
  83. package/dist/src/core/settings-watch.js +1 -0
  84. package/dist/src/core/settings-watch.js.map +1 -1
  85. package/dist/src/core/skill-mentions.js +83 -0
  86. package/dist/src/core/skill-mentions.js.map +1 -0
  87. package/dist/src/manager/registry.js +5 -1
  88. package/dist/src/manager/registry.js.map +1 -1
  89. package/dist/src/manager/routes/desktop-status.js +1 -1
  90. package/dist/src/manager/routes/desktop-status.js.map +1 -1
  91. package/dist/src/orchestrator/pipeline.js +11 -1
  92. package/dist/src/orchestrator/pipeline.js.map +1 -1
  93. package/dist/src/prompt/builder.js +10 -4
  94. package/dist/src/prompt/builder.js.map +1 -1
  95. package/dist/src/prompt/templates/a1-system.md +1 -1
  96. package/dist/src/routes/code-native.js +51 -3
  97. package/dist/src/routes/code-native.js.map +1 -1
  98. package/dist/src/routes/settings.js +1 -4
  99. package/dist/src/routes/settings.js.map +1 -1
  100. package/dist/src/routes/skills.js +7 -0
  101. package/dist/src/routes/skills.js.map +1 -1
  102. package/dist/src/shared/skill-mention.js +61 -0
  103. package/dist/src/shared/skill-mention.js.map +1 -0
  104. package/dist/src/shared/tool-input.js +137 -0
  105. package/dist/src/shared/tool-input.js.map +1 -0
  106. package/package.json +7 -5
  107. package/public/css/activity.css +83 -20
  108. package/public/css/layout.css +2 -2
  109. package/public/css/sidebar.css +18 -0
  110. package/public/css/variables.css +23 -2
  111. package/public/dist/.vite/manifest.json +302 -334
  112. package/public/dist/assets/Activity-DPLlOJ46.js +1 -0
  113. package/public/dist/assets/AdvancedExport-BgOgGvc5.js +1 -0
  114. package/public/dist/assets/Agent-O_U8ny90.js +1 -0
  115. package/public/dist/assets/Browser-m6VynQXV.js +1 -0
  116. package/public/dist/assets/BrowserPanel-Bb-Bge6B.js +3 -0
  117. package/public/dist/assets/ChannelEnablementControl-BSuKQhTW.js +85 -0
  118. package/public/dist/assets/ChannelSetupEntry-DHLoZEzJ.js +1 -0
  119. package/public/dist/assets/ChannelsDiscord-B-G8vsYy.js +1 -0
  120. package/public/dist/assets/ChannelsSlack-oPuh922z.js +1 -0
  121. package/public/dist/assets/ChannelsTelegram-PmTv43KQ.js +1 -0
  122. package/public/dist/assets/CodeCanvas-CVbdp0S-.js +3 -0
  123. package/public/dist/assets/DashboardMeta-BqEiJrvr.js +1 -0
  124. package/public/dist/assets/DesignPanel-BOb-HMbl.js +2 -0
  125. package/public/dist/assets/{Developer-BdKPcw8Z.js → Developer-CbySB7_Z.js} +1 -1
  126. package/public/dist/assets/{DiffPanel-DtJuvU5y.js → DiffPanel-D9Cch5FG.js} +1 -1
  127. package/public/dist/assets/Display-C0Zdamcb.js +1 -0
  128. package/public/dist/assets/Display-DU4_76cS.js +1 -0
  129. package/public/dist/assets/DocPanel-DvtFw8AP.js +1 -0
  130. package/public/dist/assets/{Embedding-DvdTj1BF.js → Embedding-CCFMOB_2.js} +1 -1
  131. package/public/dist/assets/Employees-BanvF58d.js +1 -0
  132. package/public/dist/assets/FolderPanel-CJvwRwIV.js +3 -0
  133. package/public/dist/assets/Heartbeat-uyi_MpUd.js +1 -0
  134. package/public/dist/assets/InlineWarn-Cq4z28F3.js +1 -0
  135. package/public/dist/assets/MarkdownRenderer-Cc3DeHvm.js +1 -0
  136. package/public/dist/assets/{MarkdownRenderer-DhonsAPC.js → MarkdownRenderer-DzN66XVb.js} +1 -1
  137. package/public/dist/assets/Mcp-rjPoL_Zo.js +4 -0
  138. package/public/dist/assets/Memory-C9I33pIi.js +1 -0
  139. package/public/dist/assets/{MilkdownWysiwygEditor-D6JaLDE4.js → MilkdownWysiwygEditor-xObhviw7.js} +1 -1
  140. package/public/dist/assets/ModelProvider-fiUktMaO.js +1 -0
  141. package/public/dist/assets/Network-CuqFrOvR.js +1 -0
  142. package/public/dist/assets/NotesGraphWorkspace-DO9LtVGF.js +1 -0
  143. package/public/dist/assets/PanelLayoutProvider-DRZ4QkBC.js +1 -0
  144. package/public/dist/assets/Permissions-BltXiZnz.js +1 -0
  145. package/public/dist/assets/{Permissions-BwiMEoTV.js → Permissions-DcCbFMIq.js} +1 -1
  146. package/public/dist/assets/Profile-DUL7-RE_.js +1 -0
  147. package/public/dist/assets/Prompts-C9vY8ivQ.js +1 -0
  148. package/public/dist/assets/SpeechKeys-BJ8V7U5E.js +1 -0
  149. package/public/dist/assets/TelegramHub-DtyjLGFR.js +1 -0
  150. package/public/dist/assets/{TerminalPanel-Bu9i-P2f.js → TerminalPanel-c1xhiy53.js} +1 -1
  151. package/public/dist/assets/app-BjKzqXCv.css +1 -0
  152. package/public/dist/assets/app-CZwx0V6K.js +59 -0
  153. package/public/dist/assets/bgtask-badge-CnVkcp3c.js +1 -0
  154. package/public/dist/assets/{bgtask-badge-DfiHuzxT.js → bgtask-badge-Cp26S8Uq.js} +1 -1
  155. package/public/dist/assets/client-platform-WO8m6kPQ.js +1 -0
  156. package/public/dist/assets/{employees-BQaJNp-u.js → employees-CsvI0_5V.js} +1 -1
  157. package/public/dist/assets/error-normalize-clOi-AAA.js +1 -0
  158. package/public/dist/assets/fields-CicOWY-X.js +1 -0
  159. package/public/dist/assets/iframe-renderer-DWx9v2Iz.js +1 -0
  160. package/public/dist/assets/iframe-renderer-DmHYOI1A.js +142 -0
  161. package/public/dist/assets/{react-C_pPTFsM.js → jsx-runtime-CkEUZM6i.js} +1 -1
  162. package/public/dist/assets/manager-CCXm5Yfe.js +5 -0
  163. package/public/dist/assets/manager-D9rhKFKc.css +1 -0
  164. package/public/dist/assets/manager-shortcuts-Cqmr0mEq.js +1 -0
  165. package/public/dist/assets/{memory-EG1B51up.js → memory-C6qtAMdJ.js} +1 -1
  166. package/public/dist/assets/memory-PPofQPeB.js +1 -0
  167. package/public/dist/assets/message-history-DcjBuubA.js +4 -0
  168. package/public/dist/assets/message-history-bw4qF01Z.js +1 -0
  169. package/public/dist/assets/page-shell-DfinU3mb.js +1 -0
  170. package/public/dist/assets/react-dom-URBLzV3w.js +1 -0
  171. package/public/dist/assets/{render-Dp07lZAJ.js → render-DfYro-fG.js} +1 -1
  172. package/public/dist/assets/{settings-jlI4X-zt.js → settings-CvsLWlJn.js} +38 -30
  173. package/public/dist/assets/settings-DMiQe469.js +1 -0
  174. package/public/dist/assets/settings-DgAlohx9.js +1 -0
  175. package/public/dist/assets/settings-core-BH7J_Rbm.js +1 -0
  176. package/public/dist/assets/{settings-core-B5Uf_lWY.js → settings-core-pnpw-eZv.js} +1 -1
  177. package/public/dist/assets/{settings-embedding-BfWzWKMM.js → settings-embedding-BRW0WZvh.js} +3 -3
  178. package/public/dist/assets/{settings-embedding-DhEdT86W.css → settings-embedding-CUQ8Is3y.css} +1 -1
  179. package/public/dist/assets/settings-types-CYKy0xz4.js +1 -0
  180. package/public/dist/assets/shared-D1begqvi.js +1 -0
  181. package/public/dist/assets/{sidebar-BUemG2ZN.js → sidebar-C_80w4iY.js} +3 -3
  182. package/public/dist/assets/{skills-CKUcEN9m.js → skills-D_2adqge.js} +1 -1
  183. package/public/dist/assets/skills-DeNVAVgt.js +1 -0
  184. package/public/dist/assets/{slash-commands-D34GgHFX.js → slash-commands-B_Hq5RkC.js} +2 -2
  185. package/public/dist/assets/slash-commands-Cj5qoRWc.js +1 -0
  186. package/public/dist/assets/tool-input-DI7uu8Ah.js +2 -0
  187. package/public/dist/assets/{trace-drawer-DBmK-5F_.js → trace-drawer-B6XuPodh.js} +1 -1
  188. package/public/dist/assets/ui-BbR_fFiK.js +1 -0
  189. package/public/dist/assets/{ui-vm2iuLcS.js → ui-D8xzm7DN.js} +1 -1
  190. package/public/dist/index.html +11 -3
  191. package/public/dist/manager/index.html +3 -3
  192. package/public/dist/settings/index.html +2 -2
  193. package/public/index.html +9 -1
  194. package/public/js/features/activity-history.ts +50 -21
  195. package/public/js/features/activity-rows.ts +158 -10
  196. package/public/js/features/activity-view.ts +5 -5
  197. package/public/js/features/chat.ts +2 -0
  198. package/public/js/features/employees.ts +1 -0
  199. package/public/js/features/resize-guard.ts +39 -0
  200. package/public/js/features/settings-cli-status.ts +49 -17
  201. package/public/js/features/settings-types.ts +39 -1
  202. package/public/js/features/skill-mentions.ts +143 -0
  203. package/public/js/main.ts +15 -0
  204. package/public/js/virtual-scroll.ts +67 -1
  205. package/public/locales/en.json +2 -0
  206. package/public/locales/ja.json +2 -0
  207. package/public/locales/ko.json +2 -0
  208. package/public/locales/zh.json +2 -0
  209. package/public/manager/src/AppChrome.tsx +2 -0
  210. package/public/manager/src/InstancePreview.tsx +1 -1
  211. package/public/manager/src/SidebarRailRouter.tsx +3 -1
  212. package/public/manager/src/browser-panel/BrowserPanel.tsx +47 -12
  213. package/public/manager/src/browser-panel/browser-address-state.ts +11 -0
  214. package/public/manager/src/client-platform.ts +18 -0
  215. package/public/manager/src/code/CodeCanvas.tsx +4 -2
  216. package/public/manager/src/code/CodeComposer.tsx +22 -10
  217. package/public/manager/src/code/CodeDraftEmptyState.tsx +64 -0
  218. package/public/manager/src/code/CodeSessionList.tsx +154 -40
  219. package/public/manager/src/code/CodeTranscript.tsx +104 -16
  220. package/public/manager/src/code/CodeWorkbench.tsx +21 -11
  221. package/public/manager/src/code/CodeWorkspaceHeader.tsx +5 -10
  222. package/public/manager/src/code/ComposerFooter.tsx +9 -1
  223. package/public/manager/src/code/code-composer.css +1 -1
  224. package/public/manager/src/code/code-controller-draft-storage.ts +16 -8
  225. package/public/manager/src/code/code-controller-drafts.ts +33 -6
  226. package/public/manager/src/code/code-controller-runtime.ts +189 -17
  227. package/public/manager/src/code/code-controller-types.ts +19 -1
  228. package/public/manager/src/code/code-recent-workspaces.ts +25 -0
  229. package/public/manager/src/code/code-session-client.ts +19 -1
  230. package/public/manager/src/code/code-session-sidebar.css +38 -0
  231. package/public/manager/src/code/code-session-state.ts +2 -0
  232. package/public/manager/src/code/code-types.ts +47 -0
  233. package/public/manager/src/code/code-workbench.css +36 -1
  234. package/public/manager/src/code/code.css +171 -7
  235. package/public/manager/src/code/session-order.ts +80 -0
  236. package/public/manager/src/code/tool-summary.ts +45 -11
  237. package/public/manager/src/folder-panel/folder-shortcuts.ts +5 -9
  238. package/public/manager/src/hooks/useCommandPalette.ts +3 -7
  239. package/public/manager/src/main.tsx +1 -0
  240. package/public/manager/src/manager-components.css +25 -23
  241. package/public/manager/src/manager-dashboard-reminders.css +2 -2
  242. package/public/manager/src/manager-layout.css +1 -1
  243. package/public/manager/src/manager-shortcut-runner.ts +9 -2
  244. package/public/manager/src/manager-shortcuts.ts +83 -21
  245. package/public/manager/src/panels/desktop-bridge.ts +4 -1
  246. package/public/manager/src/panels/panels.css +9 -0
  247. package/public/manager/src/preview.ts +19 -4
  248. package/public/manager/src/settings/pages/AdvancedExport.tsx +28 -30
  249. package/public/manager/src/settings/pages/Browser.tsx +45 -47
  250. package/public/manager/src/settings/pages/ChannelsDiscord.tsx +51 -26
  251. package/public/manager/src/settings/pages/ChannelsSlack.tsx +59 -17
  252. package/public/manager/src/settings/pages/ChannelsTelegram.tsx +51 -26
  253. package/public/manager/src/settings/pages/Display.tsx +2 -1
  254. package/public/manager/src/settings/pages/Employees.tsx +3 -3
  255. package/public/manager/src/settings/pages/Mcp.tsx +220 -200
  256. package/public/manager/src/settings/pages/Memory.tsx +35 -41
  257. package/public/manager/src/settings/pages/ModelProvider.tsx +23 -98
  258. package/public/manager/src/settings/pages/Network.tsx +5 -4
  259. package/public/manager/src/settings/pages/Permissions.tsx +38 -16
  260. package/public/manager/src/settings/pages/Prompts.tsx +12 -9
  261. package/public/manager/src/settings/pages/SpeechKeys.tsx +132 -139
  262. package/public/manager/src/settings/pages/TelegramHub.tsx +67 -20
  263. package/public/manager/src/settings/pages/components/AvatarCard.tsx +9 -8
  264. package/public/manager/src/settings/pages/components/ChannelSetupEntry.tsx +2 -2
  265. package/public/manager/src/settings/pages/components/EmployeeRow.tsx +6 -12
  266. package/public/manager/src/settings/pages/components/HealthBadge.tsx +11 -6
  267. package/public/manager/src/settings/pages/components/HeartbeatJobRow.tsx +35 -16
  268. package/public/manager/src/settings/pages/components/HeartbeatJobsSection.tsx +11 -11
  269. package/public/manager/src/settings/pages/components/HeartbeatMarkdownSection.tsx +7 -7
  270. package/public/manager/src/settings/pages/components/InlineWarn.tsx +4 -9
  271. package/public/manager/src/settings/pages/components/McpServerCard.tsx +39 -27
  272. package/public/manager/src/settings/pages/components/MemoryRow.tsx +21 -16
  273. package/public/manager/src/settings/pages/components/PerCliRow.tsx +122 -121
  274. package/public/manager/src/settings/pages/components/PiProfileDialog.tsx +9 -6
  275. package/public/manager/src/settings/pages/components/SlackSetup.tsx +8 -8
  276. package/public/manager/src/settings/pages/components/TransportStatusChips.tsx +14 -10
  277. package/public/manager/src/settings/pages/components/agent/AgentEmployeesSection.tsx +26 -26
  278. package/public/manager/src/settings/pages/components/agent/PermissionQuickSection.tsx +5 -5
  279. package/public/manager/src/settings/pages/components/agent/RuntimeEmployeeRow.tsx +14 -11
  280. package/public/manager/src/settings/pages/components/agent/RuntimeHeader.tsx +1 -3
  281. package/public/manager/src/settings/pages/components/agent/agent-meta.ts +0 -2
  282. package/public/manager/src/settings/pages/components/runtime-transport-field.tsx +2 -2
  283. package/public/manager/src/settings/pages/manager/Activity.tsx +6 -2
  284. package/public/manager/src/settings/pages/manager/Display.tsx +3 -8
  285. package/public/manager/src/settings/pages/manager/shared.tsx +44 -33
  286. package/public/manager/src/settings/pages/page-shell.tsx +66 -0
  287. package/public/manager/src/settings-agent.css +185 -36
  288. package/public/manager/src/settings-standalone.tsx +1 -0
  289. package/public/manager/src/settings-surface.css +96 -0
  290. package/public/manager/src/types.ts +1 -0
  291. package/scripts/ci/install-ripgrep.sh +35 -0
  292. package/scripts/collect-fresh-install-evidence.sh +2 -2
  293. package/scripts/install-risk-gate.mjs +2 -3
  294. package/scripts/install-wsl.sh +1 -1
  295. package/scripts/install.ps1 +1 -1
  296. package/scripts/install.sh +1 -1
  297. package/scripts/release-1.6.0.sh +1 -1
  298. package/scripts/release-gates.mjs +48 -13
  299. package/scripts/release-preview.sh +2 -2
  300. package/scripts/restack-pr.sh +88 -0
  301. package/scripts/verify-electron-update-metadata.mjs +1 -1
  302. package/scripts/windows-support-contract.json +1 -1
  303. package/public/dist/assets/Activity-BUrp73Re.js +0 -1
  304. package/public/dist/assets/AdvancedExport-B7h_ZvBu.js +0 -1
  305. package/public/dist/assets/Agent-DnbZJP7b.js +0 -1
  306. package/public/dist/assets/Browser-CiBoK5Qx.js +0 -1
  307. package/public/dist/assets/BrowserPanel-SZAUc9T2.js +0 -3
  308. package/public/dist/assets/ChannelEnablementControl-Dl0uYx38.js +0 -85
  309. package/public/dist/assets/ChannelSetupEntry-DxtOrN01.js +0 -1
  310. package/public/dist/assets/ChannelsDiscord-BEGGwd_3.js +0 -1
  311. package/public/dist/assets/ChannelsSlack-BryC2Ahr.js +0 -1
  312. package/public/dist/assets/ChannelsTelegram-CGSpaImJ.js +0 -1
  313. package/public/dist/assets/CodeCanvas-DpkFi5Au.js +0 -2
  314. package/public/dist/assets/DashboardMeta-Dh-tasbY.js +0 -1
  315. package/public/dist/assets/DesignPanel-NLNK9wCo.js +0 -2
  316. package/public/dist/assets/Display-BHGFX3O0.js +0 -1
  317. package/public/dist/assets/Display-BW-_pKRf.js +0 -1
  318. package/public/dist/assets/DocPanel-BM41cYpS.js +0 -1
  319. package/public/dist/assets/Employees-BuFw6-fT.js +0 -1
  320. package/public/dist/assets/FolderPanel-C-eQT3Yq.js +0 -3
  321. package/public/dist/assets/Heartbeat-CIVtKWNc.js +0 -1
  322. package/public/dist/assets/InlineWarn-DIMbcbzk.js +0 -1
  323. package/public/dist/assets/MarkdownRenderer-DyVx6DxM.js +0 -1
  324. package/public/dist/assets/Mcp-uKjor7vh.js +0 -4
  325. package/public/dist/assets/Memory-BSDIXT0B.js +0 -1
  326. package/public/dist/assets/ModelProvider-DMh2tXUx.js +0 -1
  327. package/public/dist/assets/Network-4Pa6JkhK.js +0 -1
  328. package/public/dist/assets/NotesGraphWorkspace-CgdC-fM6.js +0 -1
  329. package/public/dist/assets/PanelLayoutProvider-mn8sI0h-.js +0 -1
  330. package/public/dist/assets/Permissions-CodIW-Yd.js +0 -1
  331. package/public/dist/assets/Profile-DPqrYA44.js +0 -1
  332. package/public/dist/assets/Prompts-CvAx7QqR.js +0 -1
  333. package/public/dist/assets/SpeechKeys-CVBCqoBn.js +0 -1
  334. package/public/dist/assets/TelegramHub-4zKwpk0P.js +0 -1
  335. package/public/dist/assets/app-ByMfeXp-.js +0 -55
  336. package/public/dist/assets/app-DAJWqs33.css +0 -1
  337. package/public/dist/assets/bgtask-badge-Ch88fnhf.js +0 -1
  338. package/public/dist/assets/error-normalize-CL69Bl7z.js +0 -1
  339. package/public/dist/assets/fields-mlGpofqQ.js +0 -1
  340. package/public/dist/assets/iframe-renderer-Bj1JolO8.js +0 -142
  341. package/public/dist/assets/iframe-renderer-DWnvUSrx.js +0 -1
  342. package/public/dist/assets/jsx-runtime-NZYk81nU.js +0 -1
  343. package/public/dist/assets/manager-Bi_xw7TW.js +0 -5
  344. package/public/dist/assets/manager-BnQHAKk6.css +0 -1
  345. package/public/dist/assets/manager-shortcuts-BDOzd8zm.js +0 -1
  346. package/public/dist/assets/memory-Bk_cb3sw.js +0 -1
  347. package/public/dist/assets/message-history-D2vniu04.js +0 -4
  348. package/public/dist/assets/message-history-GAtF_wbZ.js +0 -1
  349. package/public/dist/assets/page-shell-BeGD0rRQ.js +0 -1
  350. package/public/dist/assets/react-dom-DyUOYpVL.js +0 -1
  351. package/public/dist/assets/settings-W4KG9ivu.js +0 -1
  352. package/public/dist/assets/settings-WVGhtneN.js +0 -1
  353. package/public/dist/assets/settings-client-CmwRsVd1.js +0 -1
  354. package/public/dist/assets/settings-core-CgKGnMA5.js +0 -1
  355. package/public/dist/assets/settings-types-D3cd6SWy.js +0 -1
  356. package/public/dist/assets/shared-CiNP20HB.js +0 -1
  357. package/public/dist/assets/skills-Co7Mr-qP.js +0 -1
  358. package/public/dist/assets/slash-commands-BEr-vmIr.js +0 -1
  359. package/public/dist/assets/ui-TUyJsMUf.js +0 -1
  360. package/public/manager/src/settings/pages/components/EngineSubsection.tsx +0 -25
@@ -7,7 +7,6 @@ import crypto from 'node:crypto';
7
7
  import { join } from 'path';
8
8
  import { spawn } from 'child_process';
9
9
  import { createTextStreamReader, sliceWithoutSplittingSurrogate } from './stream-text.js';
10
- import { userErrorText } from '../messaging/redact.js';
11
10
  import { resolveWindowsLaunchSpec, launchArgv } from '../core/windows-launch-spec.js';
12
11
  import { decideShellFallback } from '../core/windows-shell-fallback.js';
13
12
  /** Cap on retained stderr text used for error classification. */
@@ -20,17 +19,17 @@ import { publish as ssePublish } from '../core/event-bus.js';
20
19
  import { settleOnce } from '../orchestrator/request-registry.js';
21
20
  import { settings, UPLOADS_DIR, detectCli, getProjectDirs } from '../core/config.js';
22
21
  import { stripUndefined } from '../core/strip-undefined.js';
23
- import { clearEmployeeSession, getSession, insertMessage, getRecentMessages, listQueuedMessages, insertQueuedMessage, deleteQueuedMessage, migrateQueuedMessagesV1ToV2, getSessionBucket, clearSessionBucket, setSessionBucketSnapshot, getMaxMessageId, getSteerSalvageAfter, } from '../core/db.js';
22
+ import { getSession, insertMessage, getRecentMessages, listQueuedMessages, insertQueuedMessage, deleteQueuedMessage, migrateQueuedMessagesV1ToV2, getSessionBucket, clearSessionBucket, setSessionBucketSnapshot, getMaxMessageId, getSteerSalvageAfter, } from '../core/db.js';
24
23
  import { buildTaskSnapshot } from '../memory/runtime.js';
25
24
  import { getActiveChatSession, getRemoteBoundSessionId } from '../core/chat-sessions.js';
26
25
  import { currentSessionScope } from '../core/session-context.js';
27
26
  import { getSystemPrompt, regenerateB } from '../prompt/builder.js';
28
27
  import { prependRemoteConversationContext } from '../prompt/conversation-context.js';
29
- import { extractSessionId, extractFromEvent, extractFromAcpUpdate, extractOutputChunk, logEventSummary, flushClaudeBuffers, flushOpenCodeBuffers } from './events.js';
28
+ import { extractSessionId, extractFromEvent, extractOutputChunk, logEventSummary, flushClaudeBuffers, flushOpenCodeBuffers } from './events.js';
30
29
  import { detectSmokeResponse } from './smoke-detector.js';
31
30
  import { saveUpload as _saveUpload, buildMediaPrompt, buildMediaPromptMany } from '../../lib/upload.js';
32
31
  import { resolveMainCli, consumePendingBootstrapPrompt, peekPendingBootstrapPrompt } from '../core/main-session.js';
33
- import { getSessionOwnershipGeneration, isCurrentSessionOwner, persistMainSession, } from './session-persistence.js';
32
+ import { getSessionOwnershipGeneration, isCurrentSessionOwner, } from './session-persistence.js';
34
33
  import { isCompactMarkerRow } from '../core/compact.js';
35
34
  import { isRuntimeSettingsMutationInFlight, waitForRuntimeSettingsIdle } from '../core/runtime-settings-gate.js';
36
35
  import { hasBlockingWorkers, hasPendingWorkerReplays, getActiveWorkers, clearAllWorkers, clearWorkersForScope, cancelWorker } from '../orchestrator/worker-registry.js';
@@ -38,50 +37,40 @@ import { sanitizeWorkerProgressTools } from '../orchestrator/worker-progress.js'
38
37
  import { handleAgentExit, setSpawnAgent, setMainMetaHandler } from './lifecycle-handler.js';
39
38
  import { buildServicePath } from '../core/runtime-path.js';
40
39
  import { formatCliUnavailableMessage, detectCliBinary } from '../core/cli-detect.js';
40
+ import { claudeModelGateMessage } from '../cli/claude-default-model-boot.js';
41
41
  import { LOCAL_SESSION_SCOPE_ACTIVATION, resolveExecutionBinding } from '../orchestrator/scope.js';
42
42
  import { stripInterviewTracker } from '../orchestrator/sanitize.js';
43
- import { beginLiveRun, appendLiveRunText, setLiveRunTraceId, clearLiveRun, replaceLiveRunTools, appendLiveRunTool, getLiveRun } from './live-run-state.js';
43
+ import { beginLiveRun, appendLiveRunText, setLiveRunTraceId, clearLiveRun, replaceLiveRunTools, appendLiveRunTool } from './live-run-state.js';
44
44
  import { setSpawnRef as setMemorySpawnRef, } from './memory-flush-controller.js';
45
45
  import { applyCliEnvDefaults, buildSessionResumeKey, ensureOpencodeAlwaysAllowPermissions, mergeEnvWindowsSafe } from './spawn-env.js';
46
- import { buildPromptForArgs, shouldBuildHistoryBlock, withHistoryPrompt, withSteerContext, PROMPT_HISTORY_MAX_ROWS, PROMPT_HISTORY_MAX_CHARS, appendCursorAcceptedInstruction, buildCursorReplacementPrompt } from './prompt-context.js';
46
+ import { buildPromptForArgs, shouldBuildHistoryBlock, withHistoryPrompt, withSteerContext, PROMPT_HISTORY_MAX_ROWS, PROMPT_HISTORY_MAX_CHARS, } from './prompt-context.js';
47
47
  import { attachWatchdog, DEFAULT_WATCHDOG_ABSOLUTE_HARD_CAP_MS } from './watchdog.js';
48
48
  import { buildOpencodeRuntimeSnapshot, buildOpencodeSpawnAudit, pushOpencodeRawEvent, resolveOpencodeBinary, } from './opencode-diagnostics.js';
49
- import { handoffRuntimeOutcome } from './runtime/outcome.js';
50
- import { AcpRuntimeSession } from './runtime/acp/runtime-session.js';
51
- import { MainReplacementOwnerMismatchError, replaceAcpMainTurn } from './runtime/replace-turn.js';
52
- import { AcpReplacement } from './runtime/acp/replacement.js';
49
+ import { MainReplacementOwnerMismatchError } from './runtime/replace-turn.js';
53
50
  import { beginSteerInput, cancelSteerInputs, cancelAllSteerInputs } from './steer-input-guard.js';
54
- import { runNativeRuntime, NativeRunFailure } from './native-runtime-run.js';
55
51
  import { startClaudeNativeRun } from './claude-runtime-run.js';
56
52
  import { hasClaudeRuns, hasClaudeMainRuns, hasClaudeWorker, cancelClaudeWorker, cancelClaudeScope, cancelAllClaudeRuns } from './runtime/claude-run-controls.js';
57
- import { syncLiveTools } from './events/helpers.js';
58
- import { RuntimeProjection } from './runtime/projection.js';
59
- import { recordRuntimeEvent } from './runtime/events.js';
60
- import { CodexProjection } from './runtime/codex-projection.js';
61
- import { PiProjection } from './runtime/pi-projection.js';
62
- import { PiRawTrace } from './runtime/pi-raw-trace.js';
63
53
  import { createPrintActivity, finishPrintActivity } from './runtime/print-activity.js';
64
54
  import { isNativeAdapterImplemented, isNativeWorkerImplemented, isSwitchableNativeCli, resolveRuntimeTransport, runtimeSessionBucket } from './runtime/selection.js';
65
- import { clearNativeStartFailure, nativeStartFailure, recordNativeStartFailure } from './runtime/start-failure.js';
66
55
  import { asCliEventRecord, discriminate, fieldString } from '../types/cli-events.js';
67
56
  import { isRemoteTarget } from '../messaging/types.js';
68
57
  import { buildRemoteBindingKey } from '../messaging/session-key.js';
69
58
  import { runPinFields, sameRunConversation } from '../messaging/run-pin.js';
70
59
  import { isRetiredCliSelection, retiredRuntimeDiagnostic } from '../types/cli-engine.js';
71
60
  import { runBeforeSpawnChecks } from '../core/policy-hooks.js';
72
- import { appendTraceEvent, createTraceId, finalizeTraceRun, stampTraceTool, startTraceRun, updateTraceToolRow } from '../trace/store.js';
61
+ import { appendTraceEvent, stampTraceTool, startTraceRun } from '../trace/store.js';
73
62
  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';
74
63
  import { detectAgyCapabilities } from './agy-capabilities.js';
75
64
  import { buildAgyBootstrapEnvelope, resolveAgyPromptOrder, } from './agy-bootstrap.js';
76
65
  import { startAgyTranscriptWatcher } from './agy-transcript-watcher.js';
77
- import { appendAssistantRawText, appendAssistantTextSegment, emitAgentTool, normalizeAssistantDisplayText, pushTrace, streamJsonMarksProgress } from './events/helpers.js';
66
+ import { emitAgentTool, normalizeAssistantDisplayText, pushTrace, streamJsonMarksProgress } from './events/helpers.js';
78
67
  import { captureKiroSessionIdAfterExit, finalizeKiroFullText, flushKiroStdoutContext, isKiroPlainTextCli, isKiroStaleSessionOutput, processKiroStdoutChunk, spawnWithKiroSnapshot, } from './kiro-runtime.js';
79
68
  import { resolveCursorModelVariant } from './cursor-runtime.js';
80
- import { bindPiExecutionCancel, normalizePiSettings, openPiRpc } from './pi-runtime.js';
81
69
  import * as piExecutionControls from './pi-runtime.js';
82
- import { PiRuntimeSession } from './runtime/pi-runtime-session.js';
83
- import { piFailureOutcome } from './runtime/pi-turn.js';
84
- import { getEmployeeMcpServers } from './mcp-passthrough.js';
70
+ import { runNativeAcpBackend } from './spawn/backend-native-acp.js';
71
+ import { runCopilotBackend } from './spawn/backend-copilot.js';
72
+ import { runPiBackend } from './spawn/backend-pi.js';
73
+ import { runCodexAppBackend } from './spawn/backend-codex-app.js';
85
74
  // ─── State ───────────────────────────────────────────
86
75
  export const activeProcesses = new Map(); // agentId → child process
87
76
  export function hasActiveAgent(agentId) {
@@ -181,7 +170,6 @@ import { buildSteerStartedEvent } from './spawn/steer-event.js';
181
170
  import { armExitSettle, captureExitSettler, settleCapturedExit, settleExit, waitForExitSettled, } from './spawn/exit-settle.js';
182
171
  import { releaseChildOutputAfterExit } from './spawn/exit-drain.js';
183
172
  import { createNdjsonFramer, MAX_PENDING_LINE_CHARS } from './spawn/line-buffer.js';
184
- import { appendBoundedFullText } from './events/fulltext-bound.js';
185
173
  /** No provider turn was dispatched; preserve the captured cancellation receipt. */
186
174
  function stoppedBeforeStart(reason, traceRunId) {
187
175
  const stopCause = stopCauseFromKillReason(reason);
@@ -951,17 +939,10 @@ const warnedAgyCapabilityFallbacks = new Set();
951
939
  // ─── Upload wrapper ──────────────────────────────────
952
940
  export const saveUpload = (buffer, originalName, options) => _saveUpload(UPLOADS_DIR, Buffer.isBuffer(buffer) ? buffer : Buffer.from(buffer), originalName, options);
953
941
  export { buildMediaPrompt, buildMediaPromptMany };
954
- // ─── Spawn Agent ─────────────────────────────────────
955
- import { AcpClient } from '../cli/acp-client.js';
956
- import { CodexAppClient, CodexSteerError, isRecoverableResumeError } from './codex-app-client.js';
957
- import { acquireCodexAppRuntime, acquirePiRuntime, acquireCursorRuntime, acquireGrokRuntime, } from './runtime-pool.js';
958
- import { grokMainOptions } from './runtime/grok-main.js';
959
- import { acquireCodexAppLane, CodexHostGenerationStaleError, prepareCodexAppHost, } from './codex-host-pool.js';
960
- import { loadCatalogEfforts, resolveCatalogPath, validateModelEffort } from './codex-app-catalog.js';
961
- import { listenCodexAppTurnAdapter, applyCodexAppTextEvent, } from './codex-app-events.js';
962
942
  import { canGuardedAgyResume, resolveAgyNativeResume, shouldEmitHeartbeat, shouldResumeBucketSession } from './spawn/resume.js';
963
943
  export { canGuardedAgyResume, resolveAgyNativeResume, shouldEmitHeartbeat, shouldResumeBucketSession };
964
944
  import { createQueueController, FALLBACK_MAX_RETRIES } from './spawn/queue.js';
945
+ import { stripSkillMentionBlock } from '../core/skill-mentions.js';
965
946
  function cleanupEmployeeTmpDir(cwd, workingDir, label) {
966
947
  if (cwd !== workingDir) {
967
948
  try {
@@ -1499,6 +1480,22 @@ export function spawnAgent(prompt, opts = {}) {
1499
1480
  cleanupEmployeeTmpDir(spawnCwd, settings["workingDir"], agentLabel);
1500
1481
  return { child: null, promise: resultPromise };
1501
1482
  }
1483
+ // ─── Claude model preflight: a model the installed CLI is known not to accept fails
1484
+ // here, for print and native, fresh and resume, instead of as a raw API 400. ───
1485
+ const claudeModelRefusal = cli === 'claude' ? claudeModelGateMessage(detected.path, runtimeModel) : null;
1486
+ if (claudeModelRefusal) {
1487
+ console.error(`[jaw:${agentLabel}] ${claudeModelRefusal}`);
1488
+ if (mainManaged)
1489
+ clearLiveRun(liveScope);
1490
+ broadcast('agent_done', { ...runPin, text: `❌ ${claudeModelRefusal}`, error: true, origin, ...empTag }, isEmployee ? 'internal' : 'public');
1491
+ resolve({ text: '', code: 1 });
1492
+ if (mainManaged) {
1493
+ releaseMainRun(scopeKey, null, ownerGeneration);
1494
+ void processQueue(scopeKey);
1495
+ }
1496
+ cleanupEmployeeTmpDir(spawnCwd, settings["workingDir"], agentLabel);
1497
+ return { child: null, promise: resultPromise };
1498
+ }
1502
1499
  if (cli === 'copilot') {
1503
1500
  console.log(`[jaw:${agentLabel}] Spawning: copilot --acp --model ${model} [${permissions}]`);
1504
1501
  }
@@ -1600,1833 +1597,21 @@ export function spawnAgent(prompt, opts = {}) {
1600
1597
  }
1601
1598
  }
1602
1599
  // ─── Native ACP main: protocol ownership is separate from application settlement. ───
1603
- if (runtimeTransport === 'native' && (cli === 'cursor' || cli === 'grok') && mainManaged) {
1604
- const grok = cli === 'grok';
1605
- const acquireRuntime = grok ? acquireGrokRuntime : acquireCursorRuntime;
1606
- const capturedRun = mainRun;
1607
- const nativeCwd = spawnCwd || process.cwd();
1608
- let traceRunId;
1609
- try {
1610
- traceRunId = startTraceRun({ cli, model: runtimeModel, workingDir: nativeCwd, agentLabel, audience: traceAudience, sessionId: chatSessionId, scopeKey });
1611
- }
1612
- catch {
1613
- traceRunId = createTraceId();
1614
- console.warn(`[runtime:${cli}] trace creation unavailable`);
1615
- }
1616
- const identity = Object.freeze({ runId: traceRunId, sessionId: chatSessionId, scope: scopeKey,
1617
- turnId: traceRunId, audience: traceAudience,
1618
- ...(opts.runtimeParentItemId ? { parentItemId: opts.runtimeParentItemId } : {}) });
1619
- const ctx = { fullText: '', traceLog: [], toolLog: [], seenToolKeys: new Set(),
1620
- hasClaudeStreamEvents: false, sessionId: null, cost: null, turns: null, duration: null, tokens: null,
1621
- stderrBuf: '', runStartedAt: Date.now(), origin,
1622
- ...(opts.requestId ? { requestId: opts.requestId } : {}),
1623
- liveScope: effectiveLiveScope, parentLiveScope: parentLiveScopeForChild, traceRunId, traceAudience };
1624
- const toolMirror = new Map();
1625
- const mirrorLimit = 160;
1626
- let facade = null, ownedLease = null;
1627
- let failedStart = null;
1628
- let nativeStarted = false, runtimeEnded = false, finalizeFailed = false, finalized = false;
1629
- let stopReason = null, queueRequested = false;
1630
- let capturedExit;
1631
- let selectedResult;
1632
- const ownsRun = () => !finalized && activeMainProcesses.get(scopeKey) === capturedRun
1633
- && isCurrentSessionOwner(persistenceOwner, scopeKey);
1634
- const cursorTarget = opts.target ? { ...opts.target } : undefined;
1635
- let acceptedContext = { messages: [], omitted: false };
1636
- const prepareReplacement = (instruction, partialText) => {
1637
- if (!ownsRun())
1638
- throw new Error('cursor_acp_owner_lost');
1639
- return { text: buildCursorReplacementPrompt({
1640
- instruction: prependRemoteConversationContext(instruction, cursorTarget),
1641
- originalRequest: promptForSnapshot, accepted: acceptedContext, partialText, sysPrompt,
1642
- }) };
1643
- };
1644
- const cursorReplaceHook = (instruction, commitInput) => replaceAcpMainTurn(facade, instruction, () => {
1645
- if (!ownsRun())
1646
- throw new Error('cursor_acp_owner_lost');
1647
- const next = appendCursorAcceptedInstruction(acceptedContext, instruction);
1648
- const result = commitInput();
1649
- if (result !== null && (typeof result === 'object' || typeof result === 'function')
1650
- && typeof result.then === 'function') {
1651
- void Promise.resolve(result).catch(() => undefined);
1652
- throw new Error('cursor_acp_async_input_commit');
1653
- }
1654
- acceptedContext = next;
1655
- });
1656
- const resultFor = (outcome) => ({
1657
- text: outcome.finalText?.trim() ?? '', code: outcome.status === 'done' ? 0 : outcome.status === 'stopped' ? 130 : 1,
1658
- runtimeOutcome: outcome, traceRunId,
1659
- });
1660
- // A runtime that never acquired a lease has no facade to ask, so the
1661
- // substring heuristic below could only ever produce the generic sentence
1662
- // for exactly the failures that need naming. The recorded start code is
1663
- // provider-independent by construction, so it decides the wording and is
1664
- // shown alongside it (#658).
1665
- const diagnostic = () => {
1666
- const code = nativeStarted ? undefined : nativeStartFailure(cli)?.code;
1667
- const named = code ? ` (${code})` : '';
1668
- if (grok)
1669
- return `Grok native runtime failed.${named} Check the native model, effort and existing CLI login.`;
1670
- const configFault = code ? code.startsWith('acp_config_') : facade?.lastError?.includes('config');
1671
- return configFault
1672
- ? `Cursor native model or effort is unsupported.${named} Choose an advertised model/effort; Composer models may require an unset effort.`
1673
- : `Cursor native runtime failed.${named} Check the native model, effort and existing CLI login.`;
1674
- };
1675
- const startFailedRuntime = () => {
1676
- if (!failedStart) {
1677
- failedStart = new RuntimeProjection(identity);
1678
- failedStart.start(cli);
1679
- }
1680
- return failedStart;
1681
- };
1682
- const closeFailedTrace = (outcome) => {
1683
- try {
1684
- finalizeTraceRun(traceRunId, outcome.status === 'stopped' ? 'interrupted' : outcome.status, outcome.status === 'error' ? diagnostic() : null, { onlyIfRunning: true });
1685
- }
1686
- catch {
1687
- console.warn(`[runtime:${cli}] failure trace finalization unavailable`);
1688
- }
1689
- };
1690
- const endRuntime = (end) => {
1691
- if (runtimeEnded)
1692
- return;
1693
- runtimeEnded = true;
1694
- if (facade?.claimTurnOutcome(traceRunId)) {
1695
- if (!facade.finalizeTurn(traceRunId, end))
1696
- finalizeFailed = true;
1697
- }
1698
- else if (!nativeStarted) {
1699
- startFailedRuntime().close(end);
1700
- }
1701
- else {
1702
- finalizeFailed = true;
1703
- console.warn(`[runtime:${cli}] missing owned finalizer`);
1704
- }
1705
- };
1706
- const failRuntime = (outcome) => {
1707
- ctx.stallWatchdog?.stop();
1708
- const selected = ctx.runtimeTerminalAttempted && ctx.runtimeOutcome ? ctx.runtimeOutcome : {
1709
- status: stopReason || ctx.stallReason ? 'stopped' : 'error',
1710
- finalText: null, partialText: outcome.partialText,
1711
- };
1712
- const stopCause = selected.status === 'stopped' ? stopCauseFromKillReason(stopReason) : undefined;
1713
- handoffRuntimeOutcome(ctx, selected);
1714
- try {
1715
- // Admit the captured run before compatibility consumers retire it.
1716
- if (!nativeStarted && !runtimeEnded)
1717
- startFailedRuntime();
1718
- if (!ctx.runtimeTerminalAttempted) {
1719
- ctx.runtimeTerminalAttempted = true;
1720
- broadcast('agent_done', { ...runPin, traceRunId, cli,
1721
- text: selected.status === 'stopped' ? '' : `❌ ${diagnostic()}`, error: true,
1722
- runtimeStatus: selected.status, runtimeFinality: selected.finalText === null ? 'absent' : 'present',
1723
- ...(stopCause ? { stopCause } : {}),
1724
- }, traceAudience);
1725
- }
1726
- }
1727
- finally {
1728
- try {
1729
- endRuntime({ kind: 'turn-end', status: selected.status, finalText: selected.finalText,
1730
- ...(selected.status === 'error' ? { error: diagnostic() } : {}) });
1731
- }
1732
- finally {
1733
- closeFailedTrace(selected);
1734
- }
1735
- }
1736
- return selectedResult ?? { ...resultFor(selected), ...(stopCause ? { stopCause } : {}) };
1737
- };
1738
- let nativeRun;
1739
- const cancelHook = (reason) => {
1740
- stopReason ??= reason;
1741
- if (isLifecycleExitSettleReason(reason)) {
1742
- armExitSettle(scopeKey);
1743
- capturedExit ??= captureExitSettler(scopeKey);
1744
- }
1745
- nativeRun.cancel(reason);
1746
- };
1747
- const grokReplaceHook = async (text, commitInput) => {
1748
- if (!ownsRun())
1749
- return { kind: 'race', reason: 'native-owner-lost' };
1750
- const result = await replaceAcpMainTurn(facade, text, commitInput);
1751
- return result.kind === 'unavailable' && !ownsRun() ? { kind: 'race', reason: 'native-owner-lost' } : result;
1752
- };
1753
- const replaceHook = grok ? grokReplaceHook : cursorReplaceHook;
1754
- if (grok)
1755
- capturedRun.replaceTurn = replaceHook;
1756
- capturedRun.starting = true;
1757
- nativeRun = runNativeRuntime({
1758
- turnId: traceRunId, prompt: { text: promptForArgs }, isCurrent: ownsRun,
1759
- acquire: async (signal) => {
1760
- const lease = await acquireRuntime({
1761
- key: { scopeKey, cwd: nativeCwd, model: runtimeModel === 'default' ? '' : runtimeModel, effort, permissions },
1762
- binary: detected.path || (grok ? 'grok' : 'cursor-agent'), env: spawnEnv, promptTimeoutMs: resolvedAgyPrintTimeoutMs,
1763
- persistenceOwner, isCurrentOwner: token => isCurrentSessionOwner(token, scopeKey), canAcquire: ownsRun,
1764
- storedSessionId: resumeSessionId,
1765
- forceNew: forceNew || Boolean(slackToolGrant),
1766
- ...(slackToolGrant ? { lifetime: 'request' } : {}),
1767
- signal,
1768
- });
1769
- facade = new AcpRuntimeSession(lease.session, { provider: cli, deferTurnEnd: true,
1770
- ...(grok ? grokMainOptions : { createReplacement: io => new AcpReplacement(io), prepareReplacement }),
1771
- getTurnContext: () => ({ ...identity, isCurrent: ownsRun }),
1772
- capabilities: { transport: 'native', steer: 'cancel-reprompt', resume: lease.session.agentCapabilities['loadSession'] === true,
1773
- tools: true, toolOutput: true, approvals: true, questions: false, images: false, subagents: false },
1774
- record: (context, body) => {
1775
- if (body.kind === 'turn-start')
1776
- nativeStarted = true;
1777
- return recordRuntimeEvent(context, body);
1778
- },
1779
- });
1780
- ownedLease = { child: lease.session.child, session: facade,
1781
- release: () => lease.release(), retire: reason => lease.retire(reason) };
1782
- return ownedLease;
1783
- },
1784
- ready: lease => {
1785
- if (!ownsRun())
1786
- throw new Error('native_run_owner_lost');
1787
- capturedRun.process = lease.child;
1788
- // This start reached its lease, so the previous start failure is spent.
1789
- clearNativeStartFailure(cli);
1790
- if (!opts._skipInsert)
1791
- insertMessage.run('user', prompt, cli, runtimeModel, nativeCwd, chatSessionId);
1792
- capturedRun.starting = false;
1793
- ctx.sessionId = lease.session.nativeSessionId || null;
1794
- if (capturedRun.cancelPending === cancelHook)
1795
- delete capturedRun.cancelPending;
1796
- capturedRun.cancelTurn = cancelHook;
1797
- capturedRun.replaceTurn = replaceHook;
1798
- beginLiveRun(liveScope, cli);
1799
- setLiveRunTraceId(liveScope, traceRunId);
1800
- const activityIdentity = { runId: traceRunId, sessionId: chatSessionId,
1801
- scope: scopeKey, origin, ...(opts.requestId ? { requestId: opts.requestId } : {}) };
1802
- const onIo = () => {
1803
- if (!ownsRun())
1804
- return;
1805
- try {
1806
- opts.lifecycle?.onActivity?.('native-runtime', activityIdentity);
1807
- }
1808
- catch {
1809
- console.warn(`[runtime:${cli}] liveness observer failed`);
1810
- }
1811
- };
1812
- const dispose = () => {
1813
- ctx.stallWatchdog?.stop();
1814
- if (capturedRun.replaceTurn === replaceHook)
1815
- delete capturedRun.replaceTurn;
1816
- lease.child.stdout?.off('data', onIo);
1817
- lease.child.stderr?.off('data', onIo);
1818
- };
1819
- try {
1820
- lease.child.stdout?.on('data', onIo);
1821
- lease.child.stderr?.on('data', onIo);
1822
- ctx.stallWatchdog = attachWatchdog(lease.child, agentLabel, reason => {
1823
- ctx.stallReason = reason;
1824
- nativeRun.cancel(reason);
1825
- });
1826
- broadcast('agent_status', { running: true, status: 'running', agentId: agentLabel, cli,
1827
- scope: scopeKey, sessionId: chatSessionId, traceRunId,
1828
- ...(opts.requestId ? { requestId: opts.requestId } : {}) }, traceAudience);
1829
- return dispose;
1830
- }
1831
- catch (error) {
1832
- dispose();
1833
- throw error;
1834
- }
1835
- },
1836
- event: (event) => {
1837
- if (event.kind === 'usage') {
1838
- ctx.tokens = { ...(event.inputTokens === undefined ? {} : { input_tokens: event.inputTokens }),
1839
- ...(event.outputTokens === undefined ? {} : { output_tokens: event.outputTokens }),
1840
- ...(event.cachedTokens === undefined ? {} : { cached_input_tokens: event.cachedTokens }) };
1841
- }
1842
- if (event.kind !== 'tool')
1843
- return;
1844
- let tool = toolMirror.get(event.itemId);
1845
- if (!tool && toolMirror.size >= mirrorLimit)
1846
- return;
1847
- const detail = [event.input, event.output, event.detail].filter(value => value !== undefined && value !== '').join('\n');
1848
- if (!tool) {
1849
- tool = { icon: '🔧', label: event.name, toolType: 'tool', status: event.status, detail,
1850
- stepRef: `runtime:${traceRunId}:${event.itemId}` };
1851
- stampTraceTool(tool, ctx, 'tool');
1852
- toolMirror.set(event.itemId, tool);
1853
- }
1854
- else {
1855
- Object.assign(tool, { label: event.name, status: event.status, detail });
1856
- updateTraceToolRow(tool);
1857
- }
1858
- ctx.toolLog = [...toolMirror.values()];
1859
- syncLiveTools(ctx);
1860
- },
1861
- settle: async (lease, outcome, problem) => {
1862
- // Immediate Stop can settle without entering acquire/ready/send.
1863
- if (!nativeStarted && !runtimeEnded)
1864
- startFailedRuntime();
1865
- ctx.stallWatchdog?.stop();
1866
- ctx.fullText = outcome.partialText;
1867
- if (problem)
1868
- ctx.stderrBuf = problem;
1869
- if (lease)
1870
- ctx.sessionId = lease.session.nativeSessionId || null;
1871
- const recordedReason = consumeKillReason(lease?.child.pid);
1872
- const killReason = stopReason || recordedReason;
1873
- const wasKilled = Boolean(killReason), wasSteer = isLifecycleSteerReason(killReason);
1874
- const code = outcome.status === 'done' ? 0 : outcome.status === 'stopped' ? 130 : 1;
1875
- handoffRuntimeOutcome(ctx, outcome);
1876
- try {
1877
- opts.lifecycle?.onExit?.(code);
1878
- }
1879
- catch {
1880
- console.warn(`[runtime:${cli}] exit observer failed`);
1881
- }
1882
- await handleAgentExit({ onRuntimeEnd: endRuntime,
1883
- ctx, code, cli, model: runtimeModel, effectiveProvider, agentLabel, mainManaged, origin,
1884
- killReason,
1885
- resumeKey, prompt, opts, cfg: { ...cfg, effort }, ownerGeneration, persistenceOwner, forceNew, empSid,
1886
- isResume, wasKilled, wasSteer, smokeResult: detectSmokeResponse(outcome.finalText ?? '', ctx.toolLog, code, cli),
1887
- effortDefault: effort, costLine: '', resolve: value => { selectedResult ??= value; },
1888
- activeProcesses, scopeKey, runtimeTransport, scopedBucket: currentBucket, chatSessionId,
1889
- childProcess: lease?.child ?? null, releaseMainRun,
1890
- retryState: queueCtrl.retryStateForScope(scopeKey), fallbackState: queueCtrl.fallbackStateForScope(scopeKey),
1891
- fallbackMaxRetries: FALLBACK_MAX_RETRIES, processQueue: () => { queueRequested = true; },
1892
- });
1893
- if (finalizeFailed && lease)
1894
- await lease.retire(new Error('native_runtime_finalization_failed'));
1895
- return selectedResult ?? resultFor(ctx.runtimeOutcome ?? outcome);
1896
- },
1897
- failed: (error, lease, outcome) => {
1898
- // A null lease means acquire never returned: the runtime failed to
1899
- // start, which is the only class this diagnostic answers (#658).
1900
- // Teardown faults after a claimed answer are a different failure.
1901
- if (!lease)
1902
- recordNativeStartFailure(cli, error);
1903
- return failRuntime(outcome);
1904
- },
1905
- finalized: () => {
1906
- finalized = true;
1907
- try {
1908
- if (capturedRun.cancelPending === cancelHook)
1909
- delete capturedRun.cancelPending;
1910
- if (capturedRun.cancelTurn === cancelHook)
1911
- delete capturedRun.cancelTurn;
1912
- if (capturedRun.replaceTurn === replaceHook)
1913
- delete capturedRun.replaceTurn;
1914
- if (activeMainProcesses.get(scopeKey) === capturedRun) {
1915
- const child = ownedLease?.child ?? null;
1916
- if (releaseMainRun(scopeKey, child, ownerGeneration)) {
1917
- if (getLiveRun(liveScope).traceRunId === traceRunId)
1918
- clearLiveRun(liveScope);
1919
- broadcast('agent_status', { running: false, agentId: agentLabel, cli, scope: scopeKey,
1920
- sessionId: chatSessionId, traceRunId }, traceAudience);
1921
- }
1922
- }
1923
- }
1924
- finally {
1925
- // Projection/listener/cleanup failure must not leave our durable
1926
- // row running or rewrite a lifecycle that already selected a result.
1927
- if (ctx.runtimeOutcome)
1928
- closeFailedTrace(ctx.runtimeOutcome);
1929
- settleCapturedExit(scopeKey, capturedExit);
1930
- // Exceptional settlement can release the slot before lifecycle requests its normal wake.
1931
- if (queueRequested || !activeMainProcesses.has(scopeKey))
1932
- void processQueue(scopeKey);
1933
- }
1934
- },
1935
- });
1936
- capturedRun.cancelPending = cancelHook;
1937
- let resolved = false;
1938
- const resolveOnce = (value) => { if (!resolved) {
1939
- resolved = true;
1940
- resolve(value);
1941
- } };
1942
- nativeRun.done.then(resolveOnce, error => {
1943
- const prior = ctx.runtimeTerminalAttempted && ctx.runtimeOutcome ? ctx.runtimeOutcome
1944
- : error instanceof NativeRunFailure ? { ...error.outcome,
1945
- status: stopReason || ctx.stallReason || error.outcome.status === 'stopped' ? 'stopped' : 'error', finalText: null }
1946
- : { status: 'error', finalText: null, partialText: ctx.fullText };
1947
- resolveOnce(selectedResult ?? resultFor(prior));
1948
- });
1949
- return { child: null, promise: resultPromise };
1950
- }
1600
+ // Backend branches live in ./spawn/backend-*.ts; they read these values, captured here,
1601
+ // and never write back (no spawnAgent binding they read is reassigned after this point).
1602
+ const backendLocals = { agentLabel, bucketSessionId, cfg, chatSessionId, cleanupPiEmployee, cli, codexMultiplexMain, currentBucket, detected, effectiveLiveScope, effectiveProvider, effort, empSid, empTag, forceNew, historyBlock, isEmployee, isResume, liveScope, mainManaged, mainRun, model, opts, origin, ownerGeneration, parentLiveScopeForChild, permissions, persistenceOwner, prompt, promptForArgs, promptForSnapshot, resolve: (value) => resolve(value), resolvedAgyPrintTimeoutMs, resultPromise, resumeKey, resumeSessionId, runPin, runtimeModel, runtimeTransport, scopeKey, slackToolGrant, spawnCwd, spawnEnv, sysPrompt, traceAudience };
1603
+ const backendHost = { CODEX_APP_ACQUIRE_RETRY_BACKOFF_MAX_MS, DEFAULT_CODEX_APP_ACQUIRE_WAIT_MS, DEFAULT_CODEX_APP_TURN_ABS_MS, DEFAULT_CODEX_APP_TURN_IDLE_MS, STDERR_BUF_CAP, activeMainProcesses, activeProcesses, appendParentLiveRunTool, broadcastAgentOutput, buildHistoryBlock, cancelOwnedPiProcess, cleanupEmployeeTmpDir, clearMainLiveRunOnStop, configuredPositiveMs, consumeKillReason, piProfileFingerprintKey, processQueue, queueCtrl, registerActiveProcess, releaseMainRun, stoppedBeforeStart };
1604
+ if (runtimeTransport === 'native' && (cli === 'cursor' || cli === 'grok') && mainManaged)
1605
+ return runNativeAcpBackend(backendLocals, backendHost);
1951
1606
  // ─── Copilot ACP branch ──────────────────────
1952
- if (cli === 'copilot') {
1953
- // Write model + reasoning_effort to ~/.copilot/config.json (CLI flags unsupported)
1954
- try {
1955
- const cfgPath = join(os.homedir(), '.copilot', 'config.json');
1956
- const cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf8'));
1957
- let changed = false;
1958
- // Sync model
1959
- if (model && model !== 'default') {
1960
- if (cfg.model !== model) {
1961
- cfg.model = model;
1962
- changed = true;
1963
- }
1964
- }
1965
- // Sync effort
1966
- if (effort) {
1967
- if (cfg.reasoning_effort !== effort) {
1968
- cfg.reasoning_effort = effort;
1969
- changed = true;
1970
- }
1971
- }
1972
- else if (cfg.reasoning_effort) {
1973
- delete cfg.reasoning_effort;
1974
- changed = true;
1975
- }
1976
- if (changed)
1977
- fs.writeFileSync(cfgPath, JSON.stringify(cfg, null, 2) + '\n');
1978
- }
1979
- catch (e) {
1980
- console.warn('[jaw:copilot] config.json sync failed:', e.message);
1981
- }
1982
- const acp = new AcpClient({ model, workDir: spawnCwd, permissions, env: spawnEnv });
1983
- acp.spawn();
1984
- const child = acp.proc;
1985
- if (!child) {
1986
- throw new Error('Copilot ACP process was not created');
1987
- }
1988
- if (mainManaged)
1989
- mainRun.process = child;
1990
- else
1991
- registerActiveProcess(agentLabel, child);
1992
- if (!opts.internal)
1993
- broadcast('agent_status', { running: true, agentId: agentLabel, cli, ...empTag });
1994
- if (mainManaged && !opts.internal)
1995
- beginLiveRun(liveScope, cli);
1996
- // ─── DIFF-C: ACP error guard — prevent uncaught EventEmitter crash ───
1997
- let acpSettled = false; // guard: error→exit can fire sequentially
1998
- acp.on('error', (err) => {
1999
- if (acpSettled)
2000
- return;
2001
- acpSettled = true;
2002
- cleanupEmployeeTmpDir(spawnCwd, settings["workingDir"], agentLabel);
2003
- opts.lifecycle?.onExit?.(null);
2004
- const msg = `Copilot ACP spawn failed: ${err.message}`;
2005
- console.error(`[acp:error] ${msg}`);
2006
- if (mainManaged) {
2007
- releaseMainRun(scopeKey, child, ownerGeneration);
2008
- clearLiveRun(liveScope);
2009
- broadcast('agent_status', { running: false, agentId: agentLabel });
2010
- }
2011
- else {
2012
- activeProcesses.delete(agentLabel);
2013
- }
2014
- broadcast('agent_done', { ...runPin, text: `❌ ${msg}`, error: true, origin, ...empTag }, isEmployee ? 'internal' : 'public');
2015
- finishPrintActivity(ctx, { kind: 'turn-end', status: 'error', finalText: null, error: msg });
2016
- resolve({ text: '', code: 1 });
2017
- if (mainManaged)
2018
- void processQueue(scopeKey);
2019
- });
2020
- if (mainManaged && !opts.internal && !opts._skipInsert) {
2021
- insertMessage.run('user', prompt, cli, model, settings["workingDir"] || null, chatSessionId);
2022
- }
2023
- if (!opts.internal)
2024
- broadcast('agent_status', { status: 'running', cli, agentId: agentLabel, ...empTag }, traceAudience);
2025
- if (mainManaged && !opts.internal)
2026
- beginLiveRun(liveScope, cli);
2027
- const traceRunId = startTraceRun({ cli, model, workingDir: settings["workingDir"] || null, agentLabel, audience: traceAudience, sessionId: chatSessionId, scopeKey });
2028
- if (mainManaged && !opts.internal)
2029
- setLiveRunTraceId(liveScope, traceRunId);
2030
- const ctx = {
2031
- fullText: '', traceLog: [], toolLog: [], seenToolKeys: new Set(),
2032
- hasClaudeStreamEvents: false, sessionId: null, cost: null,
2033
- turns: null, duration: null, tokens: null, stderrBuf: '',
2034
- thinkingBuf: '',
2035
- runStartedAt: Date.now(),
2036
- ...(opts.requestId ? { requestId: opts.requestId } : {}),
2037
- ...(origin ? { origin } : {}),
2038
- liveScope: effectiveLiveScope,
2039
- parentLiveScope: parentLiveScopeForChild,
2040
- traceRunId,
2041
- traceAudience,
2042
- activityIdentity: { sessionId: chatSessionId, scope: scopeKey },
2043
- };
2044
- ctx.printActivity = createPrintActivity({ runId: traceRunId, sessionId: chatSessionId,
2045
- scope: scopeKey, turnId: traceRunId, audience: traceAudience }, cli);
2046
- // Flush accumulated 💭 thinking buffer as a single merged event
2047
- function flushThinking() {
2048
- if (!ctx.thinkingBuf)
2049
- return;
2050
- const merged = ctx.thinkingBuf.trim();
2051
- if (merged) {
2052
- const singleLine = merged.replace(/\s+/g, ' ').trim();
2053
- const label = singleLine.length > 120 ? `${singleLine.slice(0, 119)}…` : singleLine;
2054
- console.log(` 💭 ${label}`);
2055
- const tool = { icon: '💭', label, toolType: 'thinking', detail: merged };
2056
- stampTraceTool(tool, ctx, 'thinking');
2057
- ctx.toolLog.push(tool);
2058
- if (ctx.liveScope)
2059
- replaceLiveRunTools(ctx.liveScope, ctx.toolLog);
2060
- appendParentLiveRunTool(ctx, tool);
2061
- emitAgentTool(ctx, agentLabel, tool, empTag);
2062
- }
2063
- ctx.thinkingBuf = '';
2064
- }
2065
- // session/update → broadcast mapping
2066
- let replayMode = false; // Phase 17.2: suppress events during loadSession replay
2067
- let lastVisibleBroadcastTs = Date.now();
2068
- let heartbeatSent = false;
2069
- acp.on('session/update', (params) => {
2070
- if (replayMode)
2071
- return; // 리플레이 중 모든 이벤트 무시
2072
- const update = asCliEventRecord(asCliEventRecord(params)["update"]);
2073
- appendTraceEvent({ runId: ctx.traceRunId, source: 'acp_raw', eventType: fieldString(update.sessionUpdate, 'session/update'), raw: params });
2074
- const parsed = extractFromAcpUpdate(params, ctx);
2075
- if (!parsed)
2076
- return;
2077
- if (parsed.tool) {
2078
- const parsedTool = parsed.tool;
2079
- // Buffer 💭 thought chunks → flush when different event arrives
2080
- if (parsedTool.icon === '💭') {
2081
- ctx.printActivity?.reasoning(parsedTool.detail || parsedTool.label, 'append');
2082
- ctx.thinkingBuf += parsedTool.detail || parsedTool.label;
2083
- return;
2084
- }
2085
- // Non-💭 tool → flush any pending thinking first
2086
- flushThinking();
2087
- // [I3] Include stepRef + status in dedupe key to allow repeated same-name tool calls
2088
- const key = `${parsedTool.icon}:${parsedTool.label}:${parsedTool.stepRef || ''}:${parsedTool.status || ''}`;
2089
- if (!ctx.seenToolKeys.has(key)) {
2090
- ctx.seenToolKeys.add(key);
2091
- stampTraceTool(parsedTool, ctx, parsedTool.toolType || 'tool');
2092
- ctx.toolLog.push(parsedTool);
2093
- if (ctx.liveScope)
2094
- replaceLiveRunTools(ctx.liveScope, ctx.toolLog);
2095
- appendParentLiveRunTool(ctx, parsedTool);
2096
- emitAgentTool(ctx, agentLabel, parsedTool, empTag);
2097
- // Reset heartbeat gate on actually visible broadcast (not 💭)
2098
- lastVisibleBroadcastTs = Date.now();
2099
- heartbeatSent = false;
2100
- }
2101
- }
2102
- if (parsed.text) {
2103
- flushThinking();
2104
- // NARRATION-BOUNDARY-01: a changed ACP messageId means a NEW
2105
- // assistant message, so what accumulated was progress narration
2106
- // rather than part of this answer. External channels deliver text
2107
- // derived from ctx.fullText; the live UI keeps the narration via
2108
- // the agent_output broadcast below. Chunks without a messageId
2109
- // carry no boundary signal and simply accumulate.
2110
- if (parsed.messageId && ctx.acpAssistantMessageId !== undefined
2111
- && ctx.acpAssistantMessageId !== parsed.messageId) {
2112
- ctx.printActivity?.nextMessage();
2113
- ctx.fullText = '';
2114
- ctx.outputTextStarted = false;
2115
- }
2116
- if (parsed.messageId)
2117
- ctx.acpAssistantMessageId = parsed.messageId;
2118
- ctx.printActivity?.message(parsed.text, 'append', 'unknown');
2119
- const segment = appendAssistantTextSegment(ctx, parsed.text);
2120
- if (segment) {
2121
- broadcastAgentOutput(ctx, agentLabel, cli, segment, empTag, traceAudience);
2122
- lastVisibleBroadcastTs = Date.now();
2123
- heartbeatSent = false;
2124
- }
2125
- }
2126
- opts.lifecycle?.onActivity?.('acp');
2127
- });
2128
- // [P2-3.14] session/cancelled → route through extractFromAcpUpdate for UI notification
2129
- acp.on('session/cancelled', (params) => {
2130
- appendTraceEvent({ runId: ctx.traceRunId, source: 'acp_raw', eventType: 'session/cancelled', raw: params });
2131
- const parsed = extractFromAcpUpdate({
2132
- update: { sessionUpdate: 'session_cancelled', ...(params || {}) },
2133
- });
2134
- if (parsed?.tool) {
2135
- stampTraceTool(parsed.tool, ctx, parsed.tool.toolType || 'tool');
2136
- ctx.toolLog.push(parsed.tool);
2137
- if (ctx.liveScope)
2138
- replaceLiveRunTools(ctx.liveScope, ctx.toolLog);
2139
- appendParentLiveRunTool(ctx, parsed.tool);
2140
- emitAgentTool(ctx, agentLabel, parsed.tool, empTag);
2141
- }
2142
- });
2143
- // [P2-3.15] session/request_permission → audit record in toolLog
2144
- acp.on('session/request_permission', (params) => {
2145
- appendTraceEvent({ runId: ctx.traceRunId, source: 'acp_raw', eventType: 'session/request_permission', raw: params });
2146
- const parsed = extractFromAcpUpdate({
2147
- update: { sessionUpdate: 'request_permission', ...(params || {}) },
2148
- });
2149
- if (parsed?.tool) {
2150
- stampTraceTool(parsed.tool, ctx, parsed.tool.toolType || 'tool');
2151
- ctx.toolLog.push(parsed.tool);
2152
- if (ctx.liveScope)
2153
- replaceLiveRunTools(ctx.liveScope, ctx.toolLog);
2154
- appendParentLiveRunTool(ctx, parsed.tool);
2155
- emitAgentTool(ctx, agentLabel, parsed.tool, empTag);
2156
- }
2157
- });
2158
- // stderr_activity → stderrBuf accumulation + conditional heartbeat
2159
- acp.on('stderr_activity', (text) => {
2160
- appendTraceEvent({ runId: ctx.traceRunId, source: 'stderr', eventType: 'stderr_activity', raw: text });
2161
- // Accumulate stderr for diagnostics (capped)
2162
- if (ctx.stderrBuf.length < 4000) {
2163
- ctx.stderrBuf += text + '\n';
2164
- }
2165
- opts.lifecycle?.onActivity?.('stderr');
2166
- // Conditional heartbeat: visible progress absent for N seconds
2167
- if (shouldEmitHeartbeat(lastVisibleBroadcastTs, heartbeatSent)) {
2168
- heartbeatSent = true;
2169
- const elapsed = Math.round((Date.now() - lastVisibleBroadcastTs) / 1000);
2170
- console.log(` ⏳ agent active (no visible event for ${elapsed}s)`);
2171
- emitAgentTool(ctx, agentLabel, {
2172
- icon: '⏳',
2173
- label: 'working... (no visible progress)',
2174
- }, empTag);
2175
- }
2176
- });
2177
- // Run ACP flow
2178
- let promptCompleted = false;
2179
- (async () => {
2180
- try {
2181
- const initResult = await acp.initialize();
2182
- if (process.env["DEBUG"])
2183
- console.log('[acp:init]', JSON.stringify(initResult).slice(0, 200));
2184
- replayMode = true; // Phase 17.2: mute during session load
2185
- let loadSessionOk = false;
2186
- if (isResume && resumeSessionId) {
2187
- try {
2188
- await acp.loadSession(resumeSessionId);
2189
- loadSessionOk = true;
2190
- console.log(`[acp:session] loadSession OK: ${resumeSessionId.slice(0, 12)}...`);
2191
- }
2192
- catch (loadErr) {
2193
- console.warn(`[acp:session] loadSession FAILED: ${loadErr.message} — falling back to createSession`);
2194
- if (empSid && opts.agentId) {
2195
- clearEmployeeSession.run(opts.agentId);
2196
- console.warn(`[acp:session] cleared stale employee resume for ${opts.agentId}`);
2197
- }
2198
- await acp.createSession(spawnCwd, getEmployeeMcpServers());
2199
- }
2200
- }
2201
- else {
2202
- await acp.createSession(spawnCwd, getEmployeeMcpServers());
2203
- }
2204
- replayMode = false; // Phase 17.2: unmute after session load
2205
- ctx.sessionId = acp.sessionId;
2206
- // Reset accumulated text from loadSession replay (ACP replays full history)
2207
- ctx.fullText = '';
2208
- ctx.toolLog = [];
2209
- ctx.seenToolKeys.clear();
2210
- ctx.thinkingBuf = ''; // Phase 17.2: clear replay thinking too
2211
- if (mainManaged && !opts.internal) {
2212
- beginLiveRun(liveScope, cli);
2213
- if (ctx.traceRunId)
2214
- setLiveRunTraceId(liveScope, ctx.traceRunId);
2215
- }
2216
- // If loadSession failed (or not resuming), inject history into prompt
2217
- const needsHistoryFallback = isResume && !loadSessionOk;
2218
- const fallbackHistory = needsHistoryFallback && !opts._skipHistory ? buildHistoryBlock(prompt, settings["workingDir"], chatSessionId) : '';
2219
- const acpPrompt = needsHistoryFallback
2220
- ? withHistoryPrompt(prompt, fallbackHistory)
2221
- : (isResume ? prompt : withHistoryPrompt(prompt, historyBlock));
2222
- const acpPromptWithSteer = withSteerContext(acpPrompt, opts.steerContext);
2223
- const { promise: promptPromise } = acp.prompt(acpPromptWithSteer);
2224
- const promptResult = await promptPromise;
2225
- promptCompleted = true;
2226
- if (process.env["DEBUG"])
2227
- console.log('[acp:prompt:result]', JSON.stringify(promptResult).slice(0, 200));
2228
- // Save session BEFORE shutdown — acp.shutdown() causes SIGTERM (code=null),
2229
- // which skips the exit handler's code===0 gate, losing session continuity.
2230
- const persistedAcpSessionId = ctx.sessionId;
2231
- if (persistedAcpSessionId && persistMainSession(stripUndefined({
2232
- persistenceOwner,
2233
- scopeKey,
2234
- forceNew,
2235
- employeeSessionId: empSid,
2236
- sessionId: persistedAcpSessionId,
2237
- isFallback: opts._isFallback,
2238
- cli,
2239
- model,
2240
- resumeKey,
2241
- effort: cfg.effort || '',
2242
- skipSessionPersist: opts._skipSessionPersist === true,
2243
- // Without this the save falls back to the bare, unscoped bucket, and a
2244
- // successful turn in another session writes its vendor id into the row
2245
- // the default session resumes from. The exit handler below passes the
2246
- // same value; this path runs first and must not disagree with it.
2247
- runtimeTransport,
2248
- scopedBucket: currentBucket,
2249
- }))) {
2250
- console.log(`[jaw:session] saved ${cli} session=${persistedAcpSessionId.slice(0, 12)}... (pre-shutdown)`);
2251
- }
2252
- await acp.shutdown();
2253
- }
2254
- catch (err) {
2255
- console.error(`[acp:error] ${err.message}`);
2256
- if (ctx.stderrBuf.length < 4000)
2257
- ctx.stderrBuf += err.message;
2258
- acp.kill();
2259
- }
2260
- })();
2261
- acp.on('exit', ({ code, signal }) => {
2262
- if (acpSettled)
2263
- return; // error handler already resolved
2264
- acpSettled = true;
2265
- cleanupEmployeeTmpDir(spawnCwd, settings["workingDir"], agentLabel);
2266
- opts.lifecycle?.onExit?.(code ?? null);
2267
- // [I2] Consume per-process kill reason
2268
- const acpKillReason = consumeKillReason(acp.proc?.pid);
2269
- if (code !== 0 && !acpKillReason) {
2270
- console.warn(`[acp:unexpected-exit] code=${code} signal=${signal} sessionId=${ctx.sessionId || 'none'}`);
2271
- }
2272
- const wasKilled = !!acpKillReason;
2273
- const wasSteer = isLifecycleSteerReason(acpKillReason);
2274
- flushThinking(); // Flush any remaining thinking buffer
2275
- const smokeResult = detectSmokeResponse(ctx.fullText, ctx.toolLog, code, cli);
2276
- const acpCode = promptCompleted ? 0 : (code ?? 1);
2277
- // Delegated to lifecycle-handler.ts → handleAgentExit:
2278
- // - smoke continuation (guarded by !wasSteer)
2279
- // - output: ⏹️ [interrupted] prefix (wasSteer && mainManaged && !opts.internal)
2280
- // - error: code !== 0 && !wasKilled → classifyExitError
2281
- // - trace: if (traceText) traceText = `⏹️ [interrupted]…`
2282
- handleAgentExit({
2283
- ctx, code: acpCode, childExitCode: code, cli, model, agentLabel, mainManaged, origin,
2284
- killReason: acpKillReason,
2285
- onRuntimeEnd: end => ctx.printActivity?.finish(end),
2286
- resumeKey,
2287
- prompt, opts, cfg, ownerGeneration, persistenceOwner, forceNew, empSid,
2288
- isResume, wasKilled, wasSteer, smokeResult,
2289
- effortDefault: '', costLine: '',
2290
- resolve: resolve,
2291
- activeProcesses,
2292
- scopeKey,
2293
- runtimeTransport,
2294
- scopedBucket: currentBucket,
2295
- chatSessionId,
2296
- childProcess: child,
2297
- releaseMainRun,
2298
- retryState: queueCtrl.retryStateForScope(scopeKey),
2299
- fallbackState: queueCtrl.fallbackStateForScope(scopeKey),
2300
- fallbackMaxRetries: FALLBACK_MAX_RETRIES,
2301
- processQueue,
2302
- }).catch((err) => {
2303
- console.error('[jaw:lifecycle] handleAgentExit failed (ACP):', err.message);
2304
- }).finally(() => settleExit(scopeKey));
2305
- });
2306
- return { child, promise: resultPromise };
2307
- }
1607
+ if (cli === 'copilot')
1608
+ return runCopilotBackend(backendLocals, backendHost);
2308
1609
  // ─── Pi RPC branch ─────────────────────────────
2309
- if (cli === 'pi') {
2310
- let piExit = captureExitSettler(scopeKey);
2311
- const settlePiExit = () => { settleCapturedExit(scopeKey, piExit); };
2312
- const pi = normalizePiSettings(settings["pi"]);
2313
- const profileId = cfg.provider || pi.defaultProfileId;
2314
- const profile = pi.profiles.find((entry) => entry.id === profileId) || pi.profiles[0];
2315
- if (!profile) {
2316
- throw new Error('Pi profile is not configured');
2317
- }
2318
- const piSessionId = isResume && bucketSessionId ? bucketSessionId : '';
2319
- console.log(`[jaw:pi] isResume=${isResume}, bucketSessionId=${bucketSessionId || 'none'}, piSessionId=${piSessionId || 'new'}`);
2320
- const piPrompt = withSteerContext(piSessionId ? prompt : withHistoryPrompt(prompt, historyBlock), opts.steerContext);
2321
- const traceRunId = startTraceRun({ cli, model: runtimeModel, workingDir: settings["workingDir"] || null, agentLabel, audience: traceAudience, sessionId: chatSessionId, scopeKey });
2322
- const ctx = {
2323
- fullText: '',
2324
- traceLog: [],
2325
- toolLog: [],
2326
- seenToolKeys: new Set(),
2327
- hasClaudeStreamEvents: false,
2328
- runStartedAt: Date.now(),
2329
- ...(opts.requestId ? { requestId: opts.requestId } : {}),
2330
- ...(origin ? { origin } : {}),
2331
- sessionId: null,
2332
- cost: null,
2333
- turns: null,
2334
- duration: null,
2335
- tokens: null,
2336
- stderrBuf: '',
2337
- hasActiveSubAgent: false,
2338
- showReasoning: settings["showReasoning"] === true,
2339
- outputTextStarted: false,
2340
- effectiveProvider: profile.id,
2341
- thinkingBuf: '',
2342
- liveOutputText: '',
2343
- liveScope: effectiveLiveScope,
2344
- parentLiveScope: parentLiveScopeForChild,
2345
- traceRunId,
2346
- traceAudience,
2347
- activityIdentity: { sessionId: chatSessionId, scope: scopeKey },
2348
- };
2349
- const activity = new RuntimeProjection({
2350
- runId: traceRunId, sessionId: chatSessionId, scope: scopeKey,
2351
- turnId: traceRunId, audience: traceAudience,
2352
- ...(opts.runtimeParentItemId ? { parentItemId: opts.runtimeParentItemId } : {}),
2353
- });
2354
- const piProjection = new PiProjection(activity);
2355
- const rawTrace = new PiRawTrace(traceRunId, appendTraceEvent, () => activity.report('persistence'));
2356
- activity.start('pi');
2357
- const onPiRawRecord = (raw) => {
2358
- rawTrace.record(raw);
2359
- piProjection.observeRecord(raw);
2360
- };
2361
- function flushPiThinking() {
2362
- if (!ctx.thinkingBuf)
2363
- return;
2364
- const merged = ctx.thinkingBuf.trim();
2365
- if (merged) {
2366
- const singleLine = merged.replace(/\s+/g, ' ').trim();
2367
- const label = singleLine.length > 120 ? `${singleLine.slice(0, 119)}…` : singleLine;
2368
- const tool = stripUndefined({ icon: '💭', label, toolType: 'thinking', detail: merged });
2369
- stampTraceTool(tool, ctx, 'thinking');
2370
- ctx.toolLog.push(tool);
2371
- if (ctx.liveScope)
2372
- replaceLiveRunTools(ctx.liveScope, ctx.toolLog);
2373
- appendParentLiveRunTool(ctx, tool);
2374
- emitAgentTool(ctx, agentLabel, tool, empTag);
2375
- }
2376
- ctx.thinkingBuf = '';
2377
- }
2378
- const piToolDiscipline = [
2379
- '[Pi Tool Discipline]',
2380
- 'Your available tools are strictly lowercase: read, bash, edit, write, grep, find, ls.',
2381
- 'Capitalized variants (Read, Bash, Edit, Write, Grep, Find, Ls) do NOT exist and will fail.',
2382
- ].join('\n');
2383
- const piSysPrompt = sysPrompt ? `${sysPrompt}\n\n${piToolDiscipline}` : piToolDiscipline;
2384
- const onPiEvent = (event) => {
2385
- piProjection.observe(event);
2386
- try {
2387
- opts.lifecycle?.onActivity?.('pi-rpc');
2388
- }
2389
- catch {
2390
- console.warn('[jaw:pi] activity observer failed');
2391
- }
2392
- if (event.kind === 'thinking') {
2393
- ctx.thinkingBuf = (ctx.thinkingBuf || '') + event.text;
2394
- return;
2395
- }
2396
- if (event.kind === 'text') {
2397
- flushPiThinking();
2398
- const delta = String(event.text || '');
2399
- if (!delta)
2400
- return;
2401
- {
2402
- // D3: bound fullText — see events/fulltext-bound.ts.
2403
- const bounded = appendBoundedFullText(ctx.fullText, delta);
2404
- ctx.fullText = bounded.text;
2405
- if (bounded.truncated)
2406
- ctx.fullTextTruncated = true;
2407
- }
2408
- const displayDelta = normalizeAssistantDisplayText(delta);
2409
- if (ctx.liveOutputText !== undefined) {
2410
- // Bound this too: it is promoted into fullText at close.
2411
- const live = appendBoundedFullText(ctx.liveOutputText, displayDelta);
2412
- ctx.liveOutputText = live.text;
2413
- if (live.truncated)
2414
- ctx.fullTextTruncated = true;
2415
- }
2416
- if (!ctx.outputTextStarted)
2417
- ctx.outputTextStarted = true;
2418
- broadcastAgentOutput(ctx, agentLabel, cli, displayDelta, empTag, traceAudience);
2419
- return;
2420
- }
2421
- if (event.kind === 'tool') {
2422
- flushPiThinking();
2423
- const tool = stripUndefined({ icon: '🔧', label: event.label, status: event.status, detail: event.detail, toolType: 'tool' });
2424
- stampTraceTool(tool, ctx, 'tool');
2425
- ctx.toolLog.push(tool);
2426
- if (ctx.liveScope)
2427
- replaceLiveRunTools(ctx.liveScope, ctx.toolLog);
2428
- appendParentLiveRunTool(ctx, tool);
2429
- emitAgentTool(ctx, agentLabel, tool, empTag);
2430
- return;
2431
- }
2432
- if (event.kind === 'session')
2433
- ctx.sessionId = event.sessionId;
2434
- };
2435
- let piFacade = null;
2436
- let piTurnChild = null;
2437
- let piSendStarted = false;
2438
- const piTurnContext = () => ({
2439
- runId: traceRunId, sessionId: chatSessionId, scope: scopeKey,
2440
- turnId: traceRunId, audience: traceAudience,
2441
- isCurrent: () => isCurrentSessionOwner(persistenceOwner, scopeKey)
2442
- && (mainManaged
2443
- ? activeMainProcesses.get(scopeKey) === mainRun
2444
- : piTurnChild !== null && activeProcesses.get(agentLabel) === piTurnChild),
2445
- });
2446
- const appendPiStderr = (stderr) => {
2447
- if (!stderr || ctx.stderrBuf.length >= STDERR_BUF_CAP)
2448
- return;
2449
- ctx.stderrBuf = sliceWithoutSplittingSurrogate(ctx.stderrBuf + stderr, STDERR_BUF_CAP);
2450
- };
2451
- const onPiFailure = (error, outcome) => {
2452
- appendPiStderr(error.message);
2453
- if (outcome.status !== 'error')
2454
- return;
2455
- const diagnostic = sliceWithoutSplittingSurrogate(userErrorText(error), STDERR_BUF_CAP);
2456
- if (diagnostic.trim() && !ctx.runtimeDiagnostic?.trim())
2457
- ctx.runtimeDiagnostic = diagnostic;
2458
- };
2459
- const onPiStderr = (stderr) => appendPiStderr(stderr);
2460
- const mapPiSend = (child, send) => send.then((outcome) => ({
2461
- text: outcome.finalText ?? outcome.partialText ?? '',
2462
- code: typeof child.exitCode === 'number' ? child.exitCode
2463
- : outcome.status === 'error' ? 1 : 0,
2464
- sessionId: ctx.sessionId ?? piFacade?.nativeSessionId ?? null,
2465
- runtimeOutcome: outcome,
2466
- }));
2467
- const endPiRuntime = (end) => {
2468
- if (piFacade?.claimTurnOutcome(traceRunId)) {
2469
- if (!piFacade.finalizeTurn(traceRunId, end)) {
2470
- console.warn('[jaw:pi] owned finalizer rejected the terminal');
2471
- activity.close(end);
2472
- }
2473
- return;
2474
- }
2475
- if (piSendStarted)
2476
- console.warn('[jaw:pi] missing owned finalizer');
2477
- activity.close(end);
2478
- };
2479
- const runPiTurn = (child, lease, directCleanup, start) => {
2480
- let leaseCancel = null;
2481
- let cleanupDone = false, queueRequested = false;
2482
- let selectedResult;
2483
- const recordResult = (result) => {
2484
- if (selectedResult !== undefined)
2485
- return;
2486
- selectedResult = result;
2487
- if (cleanupDone)
2488
- resolve(result);
2489
- };
2490
- const requestQueue = () => {
2491
- if (cleanupDone)
2492
- void processQueue(scopeKey);
2493
- else
2494
- queueRequested = true;
2495
- };
2496
- const requestCancel = () => {
2497
- if (cleanupDone)
2498
- return Promise.resolve();
2499
- if (!lease) {
2500
- if (cancelOwnedPiProcess(child))
2501
- return Promise.resolve();
2502
- ownProcess(child).terminate('cancel');
2503
- return Promise.resolve();
2504
- }
2505
- leaseCancel ??= lease.cancel();
2506
- return leaseCancel;
2507
- };
2508
- const cancelHook = (reason) => {
2509
- if (cleanupDone)
2510
- return;
2511
- if (isLifecycleExitSettleReason(reason))
2512
- piExit = captureExitSettler(scopeKey);
2513
- void requestCancel();
2514
- };
2515
- if (lease && mainRun)
2516
- mainRun.cancelTurn = cancelHook;
2517
- let piWatchdog;
2518
- let watchdogStopped = false;
2519
- const stopWatchdog = () => { if (!watchdogStopped) {
2520
- watchdogStopped = true;
2521
- piWatchdog?.stop();
2522
- } };
2523
- let setupError;
2524
- try {
2525
- piWatchdog = attachWatchdog(child, agentLabel, (reason) => {
2526
- console.log(`[jaw:watchdog] cancelling ${agentLabel} (pi) — ${reason}`);
2527
- ctx.stallReason = reason;
2528
- void requestCancel();
2529
- });
2530
- ctx.stallWatchdog = piWatchdog;
2531
- if (mainManaged)
2532
- mainRun.process = child;
2533
- else
2534
- registerActiveProcess(agentLabel, child);
2535
- if (!opts.internal)
2536
- broadcast('agent_status', { running: true, agentId: agentLabel, cli, provider: profile.id, ...empTag });
2537
- if (mainManaged && !opts.internal) {
2538
- beginLiveRun(liveScope, cli);
2539
- setLiveRunTraceId(liveScope, traceRunId);
2540
- }
2541
- if (mainManaged && !opts.internal && !opts._skipInsert) {
2542
- insertMessage.run('user', prompt, cli, runtimeModel, settings["workingDir"] || null, chatSessionId);
2543
- }
2544
- if (!opts.internal)
2545
- broadcast('agent_status', { status: 'running', cli, agentId: agentLabel, provider: profile.id, ...empTag }, traceAudience);
2546
- }
2547
- catch (error) {
2548
- setupError = error;
2549
- try {
2550
- void requestCancel().catch(() => { console.warn('[jaw:pi] setup cancellation failed'); });
2551
- }
2552
- catch {
2553
- console.warn('[jaw:pi] setup cancellation failed');
2554
- }
2555
- }
2556
- const releaseLease = async () => {
2557
- try {
2558
- if (leaseCancel)
2559
- await leaseCancel;
2560
- }
2561
- finally {
2562
- if (mainRun?.cancelTurn === cancelHook)
2563
- delete mainRun.cancelTurn;
2564
- consumeKillReason(child.pid);
2565
- if (lease)
2566
- lease.release();
2567
- else {
2568
- let receipt;
2569
- try {
2570
- receipt = await directCleanup ?? undefined;
2571
- }
2572
- catch { /* Missing/rejected evidence never authorizes deletion. */ }
2573
- if (receipt?.cwdDisposition === 'removable')
2574
- cleanupPiEmployee();
2575
- else
2576
- console.warn('[jaw:pi] retaining employee cwd: physical cleanup unconfirmed', spawnCwd);
2577
- }
2578
- }
2579
- };
2580
- const done = start();
2581
- done.then(async (result) => {
2582
- if (setupError !== undefined)
2583
- throw setupError;
2584
- if (result.runtimeOutcome !== undefined)
2585
- handoffRuntimeOutcome(ctx, result.runtimeOutcome);
2586
- const killReason = consumeKillReason(child.pid);
2587
- stopWatchdog();
2588
- flushPiThinking();
2589
- if (result.sessionId)
2590
- ctx.sessionId = result.sessionId;
2591
- if (!ctx.fullText && result.text)
2592
- ctx.fullText = result.text;
2593
- try {
2594
- opts.lifecycle?.onExit?.(result.code);
2595
- }
2596
- catch {
2597
- console.warn('[jaw:pi] exit observer failed');
2598
- }
2599
- const wasKilled = !!killReason;
2600
- const wasSteer = isLifecycleSteerReason(killReason);
2601
- const smokeResult = detectSmokeResponse(ctx.fullText, ctx.toolLog, result.code, cli);
2602
- return handleAgentExit({
2603
- onRuntimeEnd: endPiRuntime,
2604
- ctx, code: result.code, cli, model: runtimeModel, effectiveProvider: profile.id, agentLabel, mainManaged, origin,
2605
- killReason,
2606
- resumeKey,
2607
- prompt, opts, cfg, ownerGeneration, persistenceOwner, forceNew, empSid,
2608
- isResume: false, wasKilled, wasSteer, smokeResult,
2609
- effortDefault: 'medium', costLine: '',
2610
- resolve: recordResult,
2611
- activeProcesses,
2612
- scopeKey,
2613
- runtimeTransport,
2614
- scopedBucket: currentBucket,
2615
- chatSessionId,
2616
- childProcess: child,
2617
- releaseMainRun: (scope, process, generation) => activeMainProcesses.get(scope) === mainRun
2618
- && releaseMainRun(scope, process, generation),
2619
- retryState: queueCtrl.retryStateForScope(scopeKey),
2620
- fallbackState: queueCtrl.fallbackStateForScope(scopeKey),
2621
- fallbackMaxRetries: FALLBACK_MAX_RETRIES,
2622
- processQueue: requestQueue,
2623
- });
2624
- }, async (err) => {
2625
- if (setupError !== undefined)
2626
- throw setupError;
2627
- const failedOutcome = piFailureOutcome(err);
2628
- if (failedOutcome !== undefined)
2629
- handoffRuntimeOutcome(ctx, failedOutcome);
2630
- const killReason = consumeKillReason(child.pid);
2631
- const wasKilled = !!killReason;
2632
- const wasSteer = isLifecycleSteerReason(killReason);
2633
- stopWatchdog();
2634
- appendPiStderr(err.message);
2635
- console.error('[jaw:pi] runtime failed:', userErrorText(err));
2636
- return handleAgentExit({
2637
- onRuntimeEnd: endPiRuntime,
2638
- ctx, code: 1, cli, model: runtimeModel, effectiveProvider: profile.id, agentLabel, mainManaged, origin,
2639
- killReason,
2640
- resumeKey,
2641
- prompt, opts, cfg, ownerGeneration, persistenceOwner, forceNew, empSid,
2642
- isResume: false, wasKilled, wasSteer, smokeResult: detectSmokeResponse('', [], 1, cli),
2643
- effortDefault: 'medium', costLine: '',
2644
- resolve: recordResult,
2645
- activeProcesses,
2646
- scopeKey,
2647
- runtimeTransport,
2648
- scopedBucket: currentBucket,
2649
- chatSessionId,
2650
- childProcess: child,
2651
- releaseMainRun: (scope, process, generation) => activeMainProcesses.get(scope) === mainRun
2652
- && releaseMainRun(scope, process, generation),
2653
- retryState: queueCtrl.retryStateForScope(scopeKey),
2654
- fallbackState: queueCtrl.fallbackStateForScope(scopeKey),
2655
- fallbackMaxRetries: FALLBACK_MAX_RETRIES,
2656
- processQueue: requestQueue,
2657
- });
2658
- }).catch((handleErr) => {
2659
- endPiRuntime({ kind: 'turn-end', status: 'error', finalText: null, error: 'Lifecycle failed' });
2660
- console.error('[jaw:lifecycle] handleAgentExit failed (Pi):', handleErr.message);
2661
- try {
2662
- finalizeTraceRun(traceRunId, 'error', 'Lifecycle failed', { onlyIfRunning: true });
2663
- }
2664
- catch {
2665
- console.warn('[runtime] Pi lifecycle trace finalization failed');
2666
- }
2667
- if (mainManaged && activeMainProcesses.get(scopeKey) === mainRun) {
2668
- if (releaseMainRun(scopeKey, mainRun.process, ownerGeneration) && !opts.internal)
2669
- requestQueue();
2670
- if (getLiveRun(liveScope).traceRunId === traceRunId)
2671
- clearLiveRun(liveScope);
2672
- }
2673
- else if (!mainManaged && activeProcesses.get(agentLabel) === child)
2674
- activeProcesses.delete(agentLabel);
2675
- recordResult({ text: ctx.runtimeOutcome?.finalText ?? '', code: 1,
2676
- ...(ctx.runtimeOutcome === undefined ? {} : { runtimeOutcome: { ...ctx.runtimeOutcome } }) });
2677
- }).finally(async () => {
2678
- try {
2679
- stopWatchdog();
2680
- await releaseLease();
2681
- }
2682
- catch {
2683
- console.warn('[jaw:pi] runtime cleanup failed');
2684
- recordResult({ text: '', code: 1 });
2685
- }
2686
- finally {
2687
- cleanupDone = true;
2688
- settlePiExit();
2689
- if (queueRequested)
2690
- void processQueue(scopeKey);
2691
- if (selectedResult !== undefined)
2692
- resolve(selectedResult);
2693
- }
2694
- });
2695
- };
2696
- if (opts.agentId) {
2697
- let opened;
2698
- try {
2699
- opened = openPiRpc(profile, pi, {
2700
- model: runtimeModel,
2701
- ...(piSessionId ? { sessionId: piSessionId } : {}),
2702
- effort, cwd: spawnCwd, env: spawnEnv,
2703
- onEvent: onPiEvent, onRawRecord: onPiRawRecord,
2704
- });
2705
- }
2706
- catch (error) {
2707
- activity.close({ kind: 'turn-end', status: 'error', finalText: null, error: 'Pi process creation failed' });
2708
- finalizeTraceRun(traceRunId, 'error', 'Pi process creation failed');
2709
- cleanupPiEmployee();
2710
- throw error;
2711
- }
2712
- piTurnChild = opened.child;
2713
- piFacade = new PiRuntimeSession(opened, {
2714
- lifetime: 'oneshot',
2715
- provider: 'pi',
2716
- deferTurnEnd: true,
2717
- projection: activity,
2718
- getTurnContext: piTurnContext,
2719
- ...(effort ? { effort } : {}),
2720
- onPiEvent,
2721
- onRawRecord: onPiRawRecord,
2722
- onFailure: onPiFailure,
2723
- onStderr: onPiStderr,
2724
- });
2725
- bindPiExecutionCancel(opened.child, () => { void piFacade.cancel(); });
2726
- runPiTurn(opened.child, null, opened.cleanup, () => {
2727
- piSendStarted = true;
2728
- return mapPiSend(opened.child, piFacade.send({ text: `${piSysPrompt}\n\n${piPrompt}` }, () => { }));
2729
- });
2730
- return { child: opened.child, promise: resultPromise };
2731
- }
2732
- const profileFp = crypto.createHmac('sha256', piProfileFingerprintKey)
2733
- .update(profile.apiKey || '')
2734
- .digest('hex')
2735
- .slice(0, 12);
2736
- mainRun.starting = true;
2737
- let piAcquireStopReason;
2738
- const cancelPiAcquire = (reason) => {
2739
- piAcquireStopReason ??= reason;
2740
- clearMainLiveRunOnStop(scopeKey, reason);
2741
- };
2742
- const finishPiAcquire = () => {
2743
- mainRun.starting = false;
2744
- if (mainRun.cancelPending === cancelPiAcquire)
2745
- delete mainRun.cancelPending;
2746
- };
2747
- const abandonPiAcquire = () => {
2748
- activity.close({ kind: 'turn-end', status: 'stopped', finalText: null });
2749
- try {
2750
- finalizeTraceRun(traceRunId, 'interrupted');
2751
- }
2752
- catch {
2753
- console.warn('[runtime] Pi cancelled acquisition trace finalization failed');
2754
- }
2755
- if (activeMainProcesses.get(scopeKey) === mainRun) {
2756
- if (getLiveRun(liveScope).traceRunId === traceRunId)
2757
- clearLiveRun(liveScope);
2758
- releaseMainRun(scopeKey, null, ownerGeneration);
2759
- }
2760
- settlePiExit();
2761
- resolve(stoppedBeforeStart(piAcquireStopReason, traceRunId));
2762
- };
2763
- mainRun.cancelPending = cancelPiAcquire;
2764
- void acquirePiRuntime({
2765
- key: {
2766
- scopeKey,
2767
- cwd: spawnCwd,
2768
- profileId: profile.id,
2769
- fullEndpoint: profile.endpoint,
2770
- apiKind: profile.apiKind,
2771
- model: runtimeModel,
2772
- effort,
2773
- profileFp,
2774
- },
2775
- piSettings: pi,
2776
- env: spawnEnv,
2777
- storedSessionId: piSessionId || null,
2778
- instructions: piSysPrompt,
2779
- forceNew: forceNew || Boolean(slackToolGrant),
2780
- }).then((lease) => {
2781
- finishPiAcquire();
2782
- if (piAcquireStopReason !== undefined || activeMainProcesses.get(scopeKey) !== mainRun || !isCurrentSessionOwner(persistenceOwner, scopeKey)) {
2783
- lease.release();
2784
- abandonPiAcquire();
2785
- return;
2786
- }
2787
- ctx.sessionId = lease.session.sessionId;
2788
- console.log(`[jaw:pi:pool] reused=${lease.reused} sessionId=${lease.session.sessionId || 'new'}`);
2789
- piTurnChild = lease.session.child;
2790
- piFacade = new PiRuntimeSession(lease.session, {
2791
- lifetime: 'pooled',
2792
- provider: 'pi',
2793
- deferTurnEnd: true,
2794
- projection: activity,
2795
- getTurnContext: piTurnContext,
2796
- ...(effort ? { effort } : {}),
2797
- onPiEvent,
2798
- onRawRecord: onPiRawRecord,
2799
- onFailure: onPiFailure,
2800
- onStderr: onPiStderr,
2801
- });
2802
- runPiTurn(lease.session.child, lease, null, () => {
2803
- piSendStarted = true;
2804
- return mapPiSend(lease.session.child, piFacade.send({ text: piPrompt }, () => { }));
2805
- });
2806
- }).catch((err) => {
2807
- finishPiAcquire();
2808
- if (piAcquireStopReason !== undefined) {
2809
- abandonPiAcquire();
2810
- return;
2811
- }
2812
- console.error(`[jaw:pi:pool] acquire failed: ${err.message}`);
2813
- activity.close({ kind: 'turn-end', status: 'error', finalText: null, error: 'Pi acquisition failed' });
2814
- try {
2815
- finalizeTraceRun(traceRunId, 'error', 'Pi acquisition failed');
2816
- }
2817
- catch {
2818
- console.warn('[runtime] Pi acquisition trace finalization failed');
2819
- }
2820
- const ownsRun = activeMainProcesses.get(scopeKey) === mainRun;
2821
- if (ownsRun) {
2822
- clearLiveRun(liveScope);
2823
- try {
2824
- broadcast('agent_status', { running: false, agentId: agentLabel });
2825
- broadcast('agent_done', { ...runPin, text: `❌ Pi RPC acquire failed: ${err.message}`, error: true, origin }, 'public');
2826
- }
2827
- catch {
2828
- console.warn('[jaw:pi] acquisition diagnostic delivery failed');
2829
- }
2830
- releaseMainRun(scopeKey, null, ownerGeneration);
2831
- }
2832
- resolve({ text: '', code: 1 });
2833
- if (ownsRun) {
2834
- settlePiExit();
2835
- void processQueue(scopeKey);
2836
- }
2837
- });
2838
- return { child: null, promise: resultPromise };
2839
- }
1610
+ if (cli === 'pi')
1611
+ return runPiBackend(backendLocals, backendHost);
2840
1612
  // ─── Codex AppServer branch ────────────────────
2841
- if (cli === 'codex-app') {
2842
- const catalogPath = resolveCatalogPath();
2843
- if (catalogPath) {
2844
- const verdict = validateModelEffort(model, effort, loadCatalogEfforts(catalogPath));
2845
- if (!verdict.ok) {
2846
- throw new Error(`[codex-app] ${verdict.error}`);
2847
- }
2848
- }
2849
- if (mainManaged && !opts.internal && !opts._skipInsert) {
2850
- insertMessage.run('user', prompt, cli, model, settings["workingDir"] || null, chatSessionId);
2851
- }
2852
- if (!opts.internal)
2853
- broadcast('agent_status', { status: 'running', cli, agentId: agentLabel, ...empTag }, traceAudience);
2854
- const traceRunId = startTraceRun({ cli, model, workingDir: settings["workingDir"] || null, agentLabel, audience: traceAudience, sessionId: chatSessionId, scopeKey });
2855
- if (mainManaged && !opts.internal)
2856
- setLiveRunTraceId(liveScope, traceRunId);
2857
- const ctx = {
2858
- fullText: '', traceLog: [], toolLog: [], seenToolKeys: new Set(),
2859
- hasClaudeStreamEvents: false, sessionId: null, cost: null,
2860
- turns: null, duration: null, tokens: null, stderrBuf: '',
2861
- thinkingBuf: '',
2862
- runStartedAt: Date.now(),
2863
- ...(opts.requestId ? { requestId: opts.requestId } : {}),
2864
- ...(origin ? { origin } : {}),
2865
- liveScope: effectiveLiveScope,
2866
- parentLiveScope: parentLiveScopeForChild,
2867
- traceRunId,
2868
- traceAudience,
2869
- activityIdentity: { sessionId: chatSessionId, scope: scopeKey },
2870
- };
2871
- const activity = new RuntimeProjection({
2872
- runId: traceRunId, sessionId: chatSessionId, scope: scopeKey,
2873
- turnId: traceRunId, audience: traceAudience,
2874
- ...(opts.runtimeParentItemId ? { parentItemId: opts.runtimeParentItemId } : {}),
2875
- });
2876
- const codexProjection = new CodexProjection(activity);
2877
- activity.start('codex-app');
2878
- function flushCodexAppThinking() {
2879
- if (!ctx.thinkingBuf)
2880
- return;
2881
- const merged = ctx.thinkingBuf.trim();
2882
- if (merged) {
2883
- const singleLine = merged.replace(/\s+/g, ' ').trim();
2884
- const label = singleLine.length > 120 ? `${singleLine.slice(0, 119)}…` : singleLine;
2885
- console.log(` 💭 ${label}`);
2886
- const tool = { icon: '💭', label, toolType: 'thinking', detail: merged };
2887
- stampTraceTool(tool, ctx, 'thinking');
2888
- ctx.toolLog.push(tool);
2889
- if (ctx.liveScope)
2890
- replaceLiveRunTools(ctx.liveScope, ctx.toolLog);
2891
- appendParentLiveRunTool(ctx, tool);
2892
- emitAgentTool(ctx, agentLabel, tool, empTag);
2893
- }
2894
- ctx.thinkingBuf = '';
2895
- }
2896
- let lastVisibleBroadcastTs = Date.now();
2897
- let heartbeatSent = false;
2898
- let turnCompleted = false;
2899
- let turnReportedFailure = false;
2900
- let markCodexProgress = () => { };
2901
- let settleTurn;
2902
- let rejectTurn;
2903
- const turnDone = new Promise((resolveTurn, rejectTurnPromise) => {
2904
- settleTurn = resolveTurn;
2905
- rejectTurn = rejectTurnPromise;
2906
- });
2907
- const consumeCodexAppEvent = (method, parsed) => {
2908
- if (!parsed) {
2909
- if (method === 'turn/completed')
2910
- settleTurn();
2911
- return;
2912
- }
2913
- if (parsed.flushThinking) {
2914
- flushCodexAppThinking();
2915
- }
2916
- if (parsed.tool) {
2917
- const parsedTool = parsed.tool;
2918
- if (parsedTool.icon === '💭') {
2919
- ctx.thinkingBuf += parsedTool.detail || parsedTool.label;
2920
- return;
2921
- }
2922
- flushCodexAppThinking();
2923
- const key = `${parsedTool.icon}:${parsedTool.label}:${parsedTool.stepRef || ''}:${parsedTool.status || ''}`;
2924
- if (!ctx.seenToolKeys.has(key)) {
2925
- ctx.seenToolKeys.add(key);
2926
- stampTraceTool(parsedTool, ctx, parsedTool.toolType || 'tool');
2927
- ctx.toolLog.push(parsedTool);
2928
- if (ctx.liveScope)
2929
- replaceLiveRunTools(ctx.liveScope, ctx.toolLog);
2930
- appendParentLiveRunTool(ctx, parsedTool);
2931
- emitAgentTool(ctx, agentLabel, parsedTool, empTag);
2932
- lastVisibleBroadcastTs = Date.now();
2933
- heartbeatSent = false;
2934
- }
2935
- }
2936
- // Sticky channel/item bookkeeping and the durable-vs-live decision live
2937
- // in applyCodexAppTextEvent so they can be tested without a live runtime.
2938
- if (parsed.text)
2939
- flushCodexAppThinking();
2940
- const textDecision = applyCodexAppTextEvent(ctx, parsed);
2941
- if (textDecision.durable) {
2942
- // codex-app streams item/agentMessage/delta at TOKEN granularity;
2943
- // the segment formatter would inject "\n- " between unjoined
2944
- // tokens ("이"+"지만" → "이\n- 지만"). Raw-append like the plain
2945
- // `claude` text_delta path (events/index.ts) instead.
2946
- const segment = appendAssistantRawText(ctx, textDecision.durable);
2947
- if (segment) {
2948
- broadcastAgentOutput(ctx, agentLabel, cli, segment, empTag, traceAudience);
2949
- lastVisibleBroadcastTs = Date.now();
2950
- heartbeatSent = false;
2951
- }
2952
- }
2953
- else if (textDecision.live) {
2954
- // Commentary is a transient progress update, not part of the durable
2955
- // response. Broadcast it for live UI preview but keep it out of
2956
- // fullText — that way agent_done (and therefore Slack/Telegram/
2957
- // Discord delivery) contains only the final answer.
2958
- broadcastAgentOutput(ctx, agentLabel, cli, textDecision.live, empTag, traceAudience);
2959
- }
2960
- if (parsed.sessionId && !ctx.sessionId) {
2961
- ctx.sessionId = parsed.sessionId;
2962
- }
2963
- if (parsed.tokens) {
2964
- ctx.tokens = parsed.tokens;
2965
- }
2966
- if (parsed.turnStatus && parsed.turnStatus !== 'completed') {
2967
- console.warn(`[codex-app:turn] final status: ${parsed.turnStatus}`);
2968
- turnReportedFailure = true;
2969
- }
2970
- opts.lifecycle?.onActivity?.('codex-app');
2971
- if (method === 'turn/completed')
2972
- settleTurn();
2973
- };
2974
- const handleStderr = (text) => {
2975
- appendTraceEvent({ runId: ctx.traceRunId, source: 'stderr', eventType: 'stderr', raw: text });
2976
- if (ctx.stderrBuf.length < 4000) {
2977
- ctx.stderrBuf += text + '\n';
2978
- }
2979
- opts.lifecycle?.onActivity?.('stderr');
2980
- if (shouldEmitHeartbeat(lastVisibleBroadcastTs, heartbeatSent)) {
2981
- heartbeatSent = true;
2982
- const elapsed = Math.round((Date.now() - lastVisibleBroadcastTs) / 1000);
2983
- console.log(` ⏳ agent active (no visible event for ${elapsed}s)`);
2984
- emitAgentTool(ctx, agentLabel, {
2985
- icon: '⏳',
2986
- label: 'working... (no visible progress)',
2987
- }, empTag);
2988
- }
2989
- };
2990
- const effectiveFastMode = cfg.fastMode ?? settings["perCli"]?.["codex"]?.fastMode ?? false;
2991
- const runCodexAppTurn = async (appClient, lease, laneScope) => {
2992
- const child = appClient.proc;
2993
- if (!child)
2994
- throw new Error('Codex AppServer process was not created');
2995
- if (mainManaged)
2996
- mainRun.process = child;
2997
- else
2998
- registerActiveProcess(agentLabel, child);
2999
- if (!opts.internal)
3000
- broadcast('agent_status', { running: true, agentId: agentLabel, cli, ...empTag });
3001
- const processExit = { value: null };
3002
- // The one documented cross-runtime exception. codex-app keeps a 300s idle
3003
- // bound where the shared watchdog defaults to 90s, because an appserver turn
3004
- // reports sparse STRUCTURED progress instead of a continuous stream and
3005
- // nothing here counts stdout as liveness. settings.agentTimeout deliberately
3006
- // does not narrow it either: a global idleMs written for print CLIs would
3007
- // cut the default runtime's bound by two thirds. The env overrides remain
3008
- // the supported knob and are mapped into the watchdog config below.
3009
- const idleMs = configuredPositiveMs(process.env["CODEX_APP_TURN_IDLE_MS"], DEFAULT_CODEX_APP_TURN_IDLE_MS);
3010
- const absoluteMs = configuredPositiveMs(process.env["CODEX_APP_TURN_ABS_MS"], DEFAULT_CODEX_APP_TURN_ABS_MS);
3011
- let watchdogCancel = null;
3012
- let leaseCancel = null;
3013
- const requestLeaseCancel = () => {
3014
- if (!lease) {
3015
- appClient.kill();
3016
- return Promise.resolve();
3017
- }
3018
- leaseCancel ??= lease.cancel().catch((err) => {
3019
- console.warn('[codex-app:turn] cancel failed:', err.message);
3020
- });
3021
- return leaseCancel;
3022
- };
3023
- const cancelHook = (_reason) => { void requestLeaseCancel(); };
3024
- if (lease && mainRun)
3025
- mainRun.cancelTurn = cancelHook;
3026
- // Same-turn steer (turn/steer) for the duration of THIS turn only:
3027
- // installed and torn down with cancelHook so capability reads never
3028
- // outlive the steerable window.
3029
- const steerHook = async (text) => {
3030
- try {
3031
- await appClient.steerTurn(laneScope, text, { clientUserMessageId: crypto.randomUUID() });
3032
- return 'steered';
3033
- }
3034
- catch (err) {
3035
- if (err instanceof CodexSteerError) {
3036
- // review/compact turns reject steer; a mismatched/finished
3037
- // turn raced us. Both are queue-fallback territory.
3038
- return err.code === 'not-steerable' ? 'rejected' : 'unavailable';
3039
- }
3040
- throw err;
3041
- }
3042
- };
3043
- if (mainRun)
3044
- mainRun.steerTurnInBand = steerHook;
3045
- // The same watchdog every other runtime uses. The bespoke timer pair this
3046
- // replaces never set `ctx.stallReason`, and that field is the ONLY trigger
3047
- // for the #405 truncation notice (`error-classifier.ts:149-159`). So on the
3048
- // DEFAULT runtime a timeout surfaced as a generic error and the #405 fix
3049
- // never applied to the path most turns actually take.
3050
- const turnWatchdog = attachWatchdog(child, agentLabel, reason => {
3051
- if (watchdogCancel)
3052
- return;
3053
- console.warn(`[codex-app:turn] watchdog stall (${reason}, idleMs=${idleMs}, absoluteMs=${absoluteMs})`);
3054
- ctx.stallReason = reason;
3055
- watchdogCancel = requestLeaseCancel();
3056
- rejectTurn(new Error(`Codex AppServer turn watchdog timeout: ${reason}`));
3057
- }, {
3058
- // This child's stdout IS the JSON-RPC stream and readline already owns
3059
- // it, so counting raw traffic as liveness would keep a wedged turn
3060
- // looking alive forever. Progress comes from the turn adapter instead.
3061
- observeStdio: false,
3062
- // The two bespoke timers map onto the watchdog's two real gates:
3063
- // markProgress slides absoluteDeadline to now+absoluteMs, giving
3064
- // "no progress for idleMs", capped at startedAt+absoluteHardCapMs,
3065
- // giving "total run beyond absoluteMs". firstProgressMs bounds a turn
3066
- // that never reports at all.
3067
- firstProgressMs: idleMs, idleMs, absoluteMs: idleMs, absoluteHardCapMs: absoluteMs,
3068
- });
3069
- ctx.stallWatchdog = turnWatchdog;
3070
- markCodexProgress = () => { turnWatchdog.markProgress(); };
3071
- const listener = listenCodexAppTurnAdapter(appClient, lease, laneScope, ctx, {
3072
- onProgress: () => { markCodexProgress(); },
3073
- onRawNotification: (method, params) => {
3074
- if (method === 'turn/completed' || method === 'turn/started' || method === 'error') {
3075
- console.log(`[codex-app:notify] ${method}`);
3076
- }
3077
- appendTraceEvent({ runId: ctx.traceRunId, source: 'codex_app_raw', eventType: method, raw: params });
3078
- },
3079
- onDiagnosticNotification: (entry) => {
3080
- appendTraceEvent({
3081
- runId: ctx.traceRunId,
3082
- source: 'codex_app_raw',
3083
- eventType: 'unrouted-notification',
3084
- raw: entry,
3085
- });
3086
- },
3087
- onEvent: consumeCodexAppEvent,
3088
- onProjectionNotification: (method, params, parsed) => {
3089
- codexProjection.observe(method, params, parsed, ctx.codexAppActiveChannel || '');
3090
- },
3091
- onStderr: handleStderr,
3092
- onExit: (code, signal) => {
3093
- processExit.value = { code, signal };
3094
- rejectTurn(new Error(`Codex AppServer exited (code=${code}, signal=${signal})`));
3095
- },
3096
- onError: rejectTurn,
3097
- onInterruptFailed: (err) => {
3098
- console.warn(`[codex-app:interrupt] ${err.message}`);
3099
- },
3100
- });
3101
- try {
3102
- if (lease) {
3103
- ctx.sessionId = lease.threadId;
3104
- console.log(`[codex-app:pool] thread=${lease.threadId.slice(0, 12)}... reused=${lease.reused} resumed=${lease.resumedThread}`);
3105
- }
3106
- else {
3107
- const initResult = await appClient.initialize();
3108
- if (process.env["DEBUG"])
3109
- console.log('[codex-app:init]', JSON.stringify(initResult).slice(0, 200));
3110
- const threadOptions = {
3111
- model,
3112
- effort,
3113
- cwd: spawnCwd,
3114
- fastMode: effectiveFastMode,
3115
- instructions: sysPrompt,
3116
- };
3117
- if (isResume && resumeSessionId) {
3118
- try {
3119
- await appClient.resumeThread(laneScope, resumeSessionId, threadOptions);
3120
- console.log(`[codex-app:session] resumeThread OK: ${resumeSessionId.slice(0, 12)}...`);
3121
- }
3122
- catch (resumeErr) {
3123
- const message = resumeErr.message || '';
3124
- if (!isRecoverableResumeError(message))
3125
- throw resumeErr;
3126
- console.warn(`[codex-app:session] resumeThread FAILED (recoverable): ${message} — starting new thread`);
3127
- if (empSid && opts.agentId)
3128
- clearEmployeeSession.run(opts.agentId);
3129
- await appClient.startThread(laneScope, threadOptions);
3130
- }
3131
- }
3132
- else {
3133
- await appClient.startThread(laneScope, threadOptions);
3134
- }
3135
- ctx.sessionId = appClient.getThreadId(laneScope) ?? '';
3136
- }
3137
- const shouldPrependHistory = lease
3138
- ? !(lease.resumedThread || lease.reused)
3139
- : !(isResume && Boolean(resumeSessionId));
3140
- const codexAppPrompt = (shouldPrependHistory && historyBlock)
3141
- ? `${historyBlock}\n\n[User Message]\n${prompt}`
3142
- : prompt;
3143
- const codexAppPromptWithSteer = withSteerContext(codexAppPrompt, opts.steerContext);
3144
- const startTurn = appClient.startTurn(laneScope, codexAppPromptWithSteer);
3145
- await Promise.race([startTurn, turnDone]);
3146
- await turnDone;
3147
- turnCompleted = !turnReportedFailure;
3148
- flushCodexAppThinking();
3149
- const persistedThreadId = lease?.threadId ?? appClient.getThreadId(laneScope);
3150
- if (persistedThreadId && persistMainSession(stripUndefined({
3151
- persistenceOwner,
3152
- scopeKey,
3153
- forceNew,
3154
- employeeSessionId: empSid,
3155
- sessionId: persistedThreadId,
3156
- isFallback: opts._isFallback,
3157
- cli,
3158
- model,
3159
- resumeKey,
3160
- effort: cfg.effort || '',
3161
- skipSessionPersist: opts._skipSessionPersist === true,
3162
- ...(lease?.bucketKey ? { codexAppBucket: lease.bucketKey } : {}),
3163
- // With multiplex off there is no lease bucket key, and without this the
3164
- // save lands on the bare `codex-app` row that belongs to the default
3165
- // session. codex-app is the default runtime, so that is the common case.
3166
- runtimeTransport,
3167
- scopedBucket: currentBucket,
3168
- }))) {
3169
- console.log(`[jaw:session] saved ${cli} session=${persistedThreadId.slice(0, 12)}... (pre-shutdown)`);
3170
- }
3171
- }
3172
- catch (err) {
3173
- console.error(`[codex-app:error] ${err.message}`);
3174
- if (ctx.stderrBuf.length < 4000)
3175
- ctx.stderrBuf += err.message;
3176
- if (!lease)
3177
- appClient.kill();
3178
- }
3179
- finally {
3180
- turnWatchdog.stop();
3181
- markCodexProgress = () => { };
3182
- if (watchdogCancel)
3183
- await watchdogCancel;
3184
- if (mainRun?.cancelTurn === cancelHook)
3185
- delete mainRun.cancelTurn;
3186
- if (mainRun?.steerTurnInBand === steerHook)
3187
- delete mainRun.steerTurnInBand;
3188
- listener.dispose();
3189
- if (lease)
3190
- lease.release();
3191
- else {
3192
- await appClient.closeGracefully();
3193
- appClient.cleanup();
3194
- cleanupEmployeeTmpDir(spawnCwd, settings["workingDir"], agentLabel);
3195
- }
3196
- }
3197
- // A turn that never completed is a failure even when the process it
3198
- // was running on exited cleanly. Trusting the child's status here
3199
- // reports success for a turn that produced nothing, which is exactly
3200
- // what happens when a shared host is closed mid-turn.
3201
- const exitCode = turnCompleted ? 0 : (processExit.value?.code || 1);
3202
- opts.lifecycle?.onExit?.(exitCode);
3203
- const killReason = consumeKillReason(child.pid);
3204
- if (processExit.value && processExit.value.code !== 0 && !killReason) {
3205
- console.warn(`[codex-app:unexpected-exit] code=${processExit.value.code} signal=${processExit.value.signal} threadId=${ctx.sessionId || 'none'}`);
3206
- }
3207
- const wasKilled = !!killReason;
3208
- const wasSteer = isLifecycleSteerReason(killReason);
3209
- flushCodexAppThinking();
3210
- const smokeResult = detectSmokeResponse(ctx.fullText, ctx.toolLog, exitCode, cli);
3211
- await handleAgentExit({
3212
- onRuntimeEnd: (end) => { activity.close(end); },
3213
- ctx, code: exitCode, cli, model, agentLabel, mainManaged, origin,
3214
- killReason,
3215
- resumeKey,
3216
- prompt, opts, cfg, ownerGeneration, persistenceOwner, forceNew, empSid,
3217
- isResume, wasKilled, wasSteer, smokeResult,
3218
- effortDefault: '', costLine: '',
3219
- resolve: resolve,
3220
- activeProcesses,
3221
- scopeKey,
3222
- runtimeTransport,
3223
- scopedBucket: currentBucket,
3224
- chatSessionId,
3225
- ...(lease?.bucketKey ? { codexAppBucket: lease.bucketKey } : {}),
3226
- childProcess: child,
3227
- releaseMainRun,
3228
- retryState: queueCtrl.retryStateForScope(scopeKey),
3229
- fallbackState: queueCtrl.fallbackStateForScope(scopeKey),
3230
- fallbackMaxRetries: FALLBACK_MAX_RETRIES,
3231
- processQueue,
3232
- }).catch((err) => {
3233
- activity.close({ kind: 'turn-end', status: 'error', finalText: null, error: 'Lifecycle failed' });
3234
- console.error('[jaw:lifecycle] handleAgentExit failed (codex-app):', err.message);
3235
- }).finally(() => settleExit(scopeKey));
3236
- };
3237
- if (opts.agentId) {
3238
- const employeeLaneScope = `employee:${opts.agentId}`;
3239
- const appClient = new CodexAppClient({
3240
- binary: detected.path || 'codex', workDir: spawnCwd, env: spawnEnv,
3241
- });
3242
- let child;
3243
- try {
3244
- appClient.spawn();
3245
- if (!appClient.proc)
3246
- throw new Error('Codex AppServer process was not created');
3247
- child = appClient.proc;
3248
- }
3249
- catch (error) {
3250
- activity.close({ kind: 'turn-end', status: 'error', finalText: null, error: 'Codex process creation failed' });
3251
- finalizeTraceRun(traceRunId, 'error', 'Codex process creation failed');
3252
- cleanupEmployeeTmpDir(spawnCwd, settings["workingDir"], agentLabel);
3253
- throw error;
3254
- }
3255
- void runCodexAppTurn(appClient, null, employeeLaneScope);
3256
- return { child, promise: resultPromise };
3257
- }
3258
- mainRun.starting = true;
3259
- let codexAcquireStopReason;
3260
- const cancelCodexAcquire = (reason) => { codexAcquireStopReason ??= reason; };
3261
- const releaseCodexAcquireHook = () => {
3262
- if (mainRun.cancelPending === cancelCodexAcquire)
3263
- delete mainRun.cancelPending;
3264
- };
3265
- mainRun.cancelPending = cancelCodexAcquire;
3266
- const acquireCodexAppForTurn = async () => {
3267
- const acquireWasCancelled = () => codexAcquireStopReason !== undefined || activeMainProcesses.get(scopeKey) !== mainRun;
3268
- try {
3269
- if (!codexMultiplexMain) {
3270
- const lease = await acquireCodexAppRuntime({
3271
- binary: detected.path || 'codex', env: spawnEnv,
3272
- route: 'legacy',
3273
- key: {
3274
- scopeKey,
3275
- cwd: spawnCwd, model, effort, fastMode: effectiveFastMode,
3276
- },
3277
- storedThreadId: resumeSessionId || null,
3278
- instructions: sysPrompt,
3279
- forceNew: forceNew || Boolean(slackToolGrant),
3280
- });
3281
- return { kind: 'lease', lease };
3282
- }
3283
- const waitMs = configuredPositiveMs(process.env["CODEX_APP_ACQUIRE_WAIT_MS"], DEFAULT_CODEX_APP_ACQUIRE_WAIT_MS);
3284
- const deadlineAt = Date.now() + waitMs;
3285
- let lastStaleError = null;
3286
- let staleAttempts = 0;
3287
- const deadlineError = (stage) => lastStaleError
3288
- ?? new Error(`Codex App ${stage} timed out after ${waitMs}ms`);
3289
- const awaitWithinDeadline = async (stage, pending, onLateValue) => {
3290
- let deadlineWon = false;
3291
- let timeout;
3292
- void pending.then((value) => {
3293
- if (deadlineWon)
3294
- onLateValue?.(value);
3295
- }, () => { });
3296
- const remainingMs = deadlineAt - Date.now();
3297
- if (remainingMs <= 0) {
3298
- deadlineWon = true;
3299
- throw deadlineError(stage);
3300
- }
3301
- const deadline = new Promise((_resolveDeadline, rejectDeadline) => {
3302
- timeout = setTimeout(() => {
3303
- deadlineWon = true;
3304
- rejectDeadline(deadlineError(stage));
3305
- }, remainingMs);
3306
- });
3307
- try {
3308
- return await Promise.race([pending, deadline]);
3309
- }
3310
- finally {
3311
- if (timeout)
3312
- clearTimeout(timeout);
3313
- }
3314
- };
3315
- for (;;) {
3316
- if (acquireWasCancelled())
3317
- return { kind: 'cancelled', reason: codexAcquireStopReason };
3318
- // Check the budget before spawning more work. awaitWithinDeadline()
3319
- // only measures what remains once the promise already exists, so a
3320
- // backoff that consumed the last of the budget would still get to
3321
- // start one more prepare.
3322
- if (deadlineAt - Date.now() <= 0)
3323
- throw lastStaleError ?? deadlineError('prepare');
3324
- try {
3325
- const prepared = await awaitWithinDeadline('prepare', prepareCodexAppHost({
3326
- binary: detected.path || 'codex', cwd: spawnCwd,
3327
- fastMode: effectiveFastMode, env: spawnEnv, model, effort,
3328
- }));
3329
- if (acquireWasCancelled())
3330
- return { kind: 'cancelled', reason: codexAcquireStopReason };
3331
- const lease = await awaitWithinDeadline('acquire', acquireCodexAppLane(prepared, {
3332
- scopeKey,
3333
- bucketKey: currentBucket,
3334
- storedThreadId: resumeSessionId || null,
3335
- instructions: sysPrompt,
3336
- forceNew: forceNew || Boolean(slackToolGrant),
3337
- waitMs: deadlineAt - Date.now(),
3338
- }), (lateLease) => { lateLease.release(); });
3339
- if (acquireWasCancelled()) {
3340
- lease.release();
3341
- return { kind: 'cancelled', reason: codexAcquireStopReason };
3342
- }
3343
- return { kind: 'lease', lease };
3344
- }
3345
- catch (err) {
3346
- if (!(err instanceof CodexHostGenerationStaleError))
3347
- throw err;
3348
- lastStaleError = err;
3349
- if (acquireWasCancelled())
3350
- return { kind: 'cancelled', reason: codexAcquireStopReason };
3351
- const remainingMs = deadlineAt - Date.now();
3352
- if (remainingMs <= 0)
3353
- throw lastStaleError;
3354
- staleAttempts += 1;
3355
- const backoffMs = Math.min(remainingMs, CODEX_APP_ACQUIRE_RETRY_BACKOFF_MAX_MS, 25 * staleAttempts);
3356
- await new Promise((done) => { setTimeout(done, backoffMs); });
3357
- }
3358
- }
3359
- }
3360
- finally {
3361
- // The next promise continuation still owns the acquired lease.
3362
- // Keep its cancellation hook until it hands off or abandons it.
3363
- mainRun.starting = false;
3364
- }
3365
- };
3366
- // A run that never started a turn owns nothing but its own map slot.
3367
- // releaseMainRun() matches on (process, ownerGeneration), and a pending
3368
- // run has process=null while sharing the global generation with whatever
3369
- // replaced it, so calling it here would delete the replacement's entry.
3370
- // Compare the captured object instead and only drop our own slot.
3371
- const abandonTurn = (lease) => {
3372
- lease?.release();
3373
- activity.close({ kind: 'turn-end', status: 'stopped', finalText: null });
3374
- finalizeTraceRun(traceRunId, 'interrupted');
3375
- if (!activeMainProcesses.has(scopeKey) || activeMainProcesses.get(scopeKey) === mainRun) {
3376
- const currentLive = getLiveRun(liveScope);
3377
- const ownsLive = currentLive.traceRunId === traceRunId;
3378
- if (ownsLive)
3379
- clearLiveRun(liveScope);
3380
- if (ownsLive || (!currentLive.running && currentLive.traceRunId === undefined)) {
3381
- broadcast('agent_status', { running: false, agentId: agentLabel });
3382
- }
3383
- }
3384
- resolve(stoppedBeforeStart(codexAcquireStopReason, traceRunId));
3385
- if (activeMainProcesses.get(scopeKey) === mainRun)
3386
- activeMainProcesses.delete(scopeKey);
3387
- void processQueue(scopeKey);
3388
- };
3389
- void acquireCodexAppForTurn().then(async (outcome) => {
3390
- releaseCodexAcquireHook();
3391
- if (outcome.kind === 'cancelled') {
3392
- abandonTurn(null);
3393
- return;
3394
- }
3395
- const lease = outcome.lease;
3396
- if (codexAcquireStopReason !== undefined || activeMainProcesses.get(scopeKey) !== mainRun) {
3397
- abandonTurn(lease);
3398
- return;
3399
- }
3400
- await runCodexAppTurn(lease.client, lease, lease.laneScope);
3401
- }).catch((err) => {
3402
- releaseCodexAcquireHook();
3403
- if (codexAcquireStopReason !== undefined) {
3404
- abandonTurn(null);
3405
- return;
3406
- }
3407
- console.error(`[codex-app:pool] acquire failed: ${err.message}`);
3408
- activity.close({ kind: 'turn-end', status: 'error', finalText: null, error: 'Codex acquisition failed' });
3409
- try {
3410
- finalizeTraceRun(traceRunId, 'error', 'Codex acquisition failed');
3411
- }
3412
- catch {
3413
- console.warn('[runtime] Codex acquisition trace finalization failed');
3414
- }
3415
- const ownsRun = activeMainProcesses.get(scopeKey) === mainRun;
3416
- if (ownsRun) {
3417
- clearLiveRun(liveScope);
3418
- broadcast('agent_status', { running: false, agentId: agentLabel });
3419
- broadcast('agent_done', { ...runPin, text: `❌ Codex AppServer acquire failed: ${err.message}`, error: true, origin }, 'public');
3420
- releaseMainRun(scopeKey, null, ownerGeneration);
3421
- }
3422
- resolve({ text: '', code: 1 });
3423
- if (ownsRun) {
3424
- settleExit(scopeKey);
3425
- void processQueue(scopeKey);
3426
- }
3427
- });
3428
- return { child: null, promise: resultPromise };
3429
- }
1613
+ if (cli === 'codex-app')
1614
+ return runCodexAppBackend(backendLocals, backendHost);
3430
1615
  // ─── Standard CLI branch (claude/codex/opencode) ──────
3431
1616
  const spawnCommand = cli === 'opencode' && process.platform !== 'win32'
3432
1617
  ? (resolvedOpencodeBinary || detected.path || cli)
@@ -3568,7 +1753,8 @@ export function spawnAgent(prompt, opts = {}) {
3568
1753
  void processQueue(scopeKey);
3569
1754
  });
3570
1755
  if (mainManaged && !opts.internal && !opts._skipInsert) {
3571
- insertMessage.run('user', prompt, cli, runtimeModel, settings["workingDir"] || null, chatSessionId);
1756
+ // The runtime gets the inline-skill block; the chat row keeps what was written.
1757
+ insertMessage.run('user', stripSkillMentionBlock(prompt), cli, runtimeModel, settings["workingDir"] || null, chatSessionId);
3572
1758
  }
3573
1759
  if (cli === 'claude') {
3574
1760
  child.stdin.write(withSteerContext(isResume ? prompt : withHistoryPrompt(prompt, historyBlock), opts.steerContext));