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
@@ -36,13 +36,17 @@ CREATE TABLE IF NOT EXISTS code_sessions (
36
36
  native_cursor TEXT, native_started INTEGER NOT NULL DEFAULT 0,
37
37
  native_policy_json TEXT, capabilities_json TEXT NOT NULL,
38
38
  epoch INTEGER NOT NULL DEFAULT 0, sequence INTEGER NOT NULL DEFAULT 0,
39
- revision INTEGER NOT NULL DEFAULT 0, created_at INTEGER NOT NULL, last_used_at INTEGER NOT NULL
39
+ revision INTEGER NOT NULL DEFAULT 0, created_at INTEGER NOT NULL, last_used_at INTEGER NOT NULL,
40
+ last_turn_completed_at INTEGER, last_visited_at INTEGER, thinking INTEGER,
41
+ replay_floor_sequence INTEGER NOT NULL DEFAULT 0, history_generation INTEGER NOT NULL DEFAULT 0,
42
+ rollback_since INTEGER
40
43
  );
41
44
  CREATE TABLE IF NOT EXISTS code_turns (
42
45
  session_id TEXT NOT NULL, turn_id TEXT NOT NULL, client_turn_key TEXT NOT NULL,
43
46
  prompt_hash TEXT NOT NULL, status TEXT NOT NULL, accepted_sequence INTEGER NOT NULL,
44
47
  event_bytes INTEGER NOT NULL DEFAULT 0, control_event_bytes INTEGER NOT NULL DEFAULT 0,
45
48
  settlement_bytes INTEGER NOT NULL DEFAULT 0,
49
+ native_prompt_uuid TEXT, removed_generation INTEGER,
46
50
  PRIMARY KEY(session_id, turn_id), UNIQUE(session_id, client_turn_key)
47
51
  );
48
52
  CREATE TABLE IF NOT EXISTS code_events (
@@ -54,6 +58,13 @@ CREATE TABLE IF NOT EXISTS code_items (
54
58
  item_json TEXT NOT NULL, PRIMARY KEY(session_id, item_id)
55
59
  );
56
60
  CREATE INDEX IF NOT EXISTS idx_code_items_order ON code_items(session_id, first_sequence);
61
+ CREATE TABLE IF NOT EXISTS code_steers (
62
+ session_id TEXT NOT NULL, turn_id TEXT NOT NULL, client_turn_key TEXT NOT NULL,
63
+ prompt_hash TEXT NOT NULL, status TEXT NOT NULL, accepted_sequence INTEGER, native_uuid TEXT,
64
+ PRIMARY KEY(session_id, client_turn_key)
65
+ );
66
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_code_steers_one_per_turn ON code_steers(session_id, turn_id)
67
+ WHERE status IN ('reserved', 'committed', 'unknown');
57
68
  `;
58
69
  export class CodeStoreError extends Error {
59
70
  code;
@@ -65,11 +76,29 @@ export class CodeStoreError extends Error {
65
76
  this.name = 'CodeStoreError';
66
77
  }
67
78
  }
79
+ /**
80
+ * The first kept user row whose turn still has a native prompt boundary, as a correlated
81
+ * subquery on `code_sessions.session_id`. User rows are admitted in turn order, so the earliest
82
+ * boundary turn (walked on idx_code_turns_boundary) that still has its row owns the minimum.
83
+ */
84
+ const ROLLBACK_SINCE_SQL = `(SELECT i.first_sequence FROM code_turns t
85
+ JOIN code_items i ON i.session_id = t.session_id AND i.item_id = t.turn_id || ':user'
86
+ WHERE t.session_id = code_sessions.session_id AND t.native_prompt_uuid IS NOT NULL AND t.removed_generation IS NULL
87
+ ORDER BY t.accepted_sequence LIMIT 1)`;
88
+ // `rollback_since` is stored: it is refreshed only where boundaries or user rows change, so a
89
+ // session read on the streaming path never walks the session's turns.
68
90
  const SESSION_COLUMNS = `session_id, provider, cwd, title, model, effort, permission_mode,
69
91
  status, active_turn_id, archived_at, error_json, native_cursor, native_started,
70
- native_policy_json, capabilities_json, epoch, sequence, revision, created_at, last_used_at`;
71
- const TURN_COLUMNS = 'turn_id, client_turn_key, prompt_hash, status, accepted_sequence';
72
- const isBusy = (status) => status === 'starting' || status === 'streaming' || status === 'stopping';
92
+ native_policy_json, capabilities_json, epoch, sequence, revision, created_at, last_used_at,
93
+ last_turn_completed_at, last_visited_at, thinking, replay_floor_sequence, history_generation, rollback_since`;
94
+ const TURN_COLUMNS = 'turn_id, client_turn_key, prompt_hash, status, accepted_sequence, removed_generation';
95
+ const STEER_COLUMNS = 'turn_id, client_turn_key, prompt_hash, status, accepted_sequence, native_uuid';
96
+ const promptHash = (text) => createHash('sha256').update(text).digest('hex');
97
+ function steerItem(turnId, clientTurnKey, text, at) {
98
+ return { itemId: `${turnId}:steer:${clientTurnKey}`, turnId, kind: 'user_message', status: 'done',
99
+ text, clientTurnKey, createdAt: at, updatedAt: at };
100
+ }
101
+ export const isBusy = (status) => status === 'starting' || status === 'streaming' || status === 'stopping';
73
102
  function mapCapabilities(value) {
74
103
  return {
75
104
  resume: value.resume, interrupt: value.interrupt, permissions: value.permissions,
@@ -144,14 +173,18 @@ export function toCodeSessionInfo(record) {
144
173
  const reason = record.archivedAt !== null ? 'archived'
145
174
  : !record.capabilities.resume ? 'unsupported'
146
175
  : record.nativeCursor ? null : record.nativeStarted ? 'resume_unavailable' : 'not_started';
176
+ const rollback = record.provider !== 'claude' ? 'unsupported' : reason ?? (record.rollbackSince === null ? 'no_boundary' : null);
147
177
  return {
148
178
  sessionId: record.sessionId, provider: record.provider, cwd: record.cwd, title: record.title,
149
179
  model: record.model, effort: record.effort, permissionMode: record.permissionMode,
150
180
  status: record.status, turnId: record.turnId, archivedAt: record.archivedAt,
151
181
  error: mapError(record.error), resume: { available: reason === null, reason },
182
+ rollback: { available: rollback === null, reason: rollback, sinceSequence: record.rollbackSince },
183
+ historyGeneration: record.historyGeneration,
152
184
  capabilities: mapCapabilities(record.capabilities), epoch: record.epoch,
153
185
  sequence: record.sequence, revision: record.revision, createdAt: record.createdAt,
154
- lastUsedAt: record.lastUsedAt,
186
+ lastUsedAt: record.lastUsedAt, lastTurnCompletedAt: record.lastTurnCompletedAt,
187
+ lastVisitedAt: record.lastVisitedAt, thinking: record.thinking,
155
188
  };
156
189
  }
157
190
  function rowToRecord(row) {
@@ -165,11 +198,17 @@ function rowToRecord(row) {
165
198
  capabilities: JSON.parse(row.capabilities_json),
166
199
  epoch: row.epoch, sequence: row.sequence, revision: row.revision,
167
200
  createdAt: row.created_at, lastUsedAt: row.last_used_at,
201
+ lastTurnCompletedAt: row.last_turn_completed_at, lastVisitedAt: row.last_visited_at,
202
+ // Claude thinking defaults on, including rows written before the column existed.
203
+ thinking: row.provider === 'claude' ? row.thinking !== 0 : null,
204
+ replayFloorSequence: row.replay_floor_sequence, historyGeneration: row.history_generation,
205
+ rollbackSince: row.rollback_since,
168
206
  };
169
207
  }
208
+ /** A turn a rollback removed is no longer current: its receipt reads cancelled. */
170
209
  function receipt(row) {
171
210
  return { turnId: row.turn_id, clientTurnKey: row.client_turn_key,
172
- sequence: row.accepted_sequence, status: row.status };
211
+ sequence: row.accepted_sequence, status: row.removed_generation === null ? row.status : 'cancelled' };
173
212
  }
174
213
  function pageLimit(value, max) {
175
214
  if (value === undefined)
@@ -197,6 +236,8 @@ export class CodeStore {
197
236
  this.database.exec(CREATE_CODE_SCHEMA_SQL);
198
237
  this.ensureSessionListIndex();
199
238
  this.ensureBudgetColumns();
239
+ this.ensureActivityColumns();
240
+ this.ensureRollbackColumns();
200
241
  }
201
242
  /** Rebuild the list index only when it does not already match the page key. */
202
243
  ensureSessionListIndex() {
@@ -209,11 +250,63 @@ export class CodeStore {
209
250
  this.database.exec(`CREATE INDEX idx_code_sessions_list ON code_sessions(${SESSION_LIST_INDEX_COLUMNS})`);
210
251
  })();
211
252
  }
253
+ /** Adds the sidebar activity clocks and the thinking switch to databases created before them; all stay NULL. */
254
+ ensureActivityColumns() {
255
+ const names = new Set(this.database.prepare('PRAGMA table_info(code_sessions)').all()
256
+ .map(column => column.name));
257
+ if (names.has('last_turn_completed_at') && names.has('last_visited_at') && names.has('thinking'))
258
+ return;
259
+ this.database.transaction(() => {
260
+ if (!names.has('last_turn_completed_at'))
261
+ this.database.exec('ALTER TABLE code_sessions ADD COLUMN last_turn_completed_at INTEGER');
262
+ if (!names.has('last_visited_at'))
263
+ this.database.exec('ALTER TABLE code_sessions ADD COLUMN last_visited_at INTEGER');
264
+ if (!names.has('thinking'))
265
+ this.database.exec('ALTER TABLE code_sessions ADD COLUMN thinking INTEGER');
266
+ }).immediate();
267
+ }
268
+ /**
269
+ * Adds the rollback columns to databases created before them. Existing turns keep a
270
+ * NULL prompt boundary, so they are never rollback targets; floors and generations start at 0.
271
+ * A stored rollback start that did not exist yet is computed once for every session.
272
+ */
273
+ ensureRollbackColumns() {
274
+ const columns = (table) => new Set(this.database.prepare(`PRAGMA table_info(${table})`).all()
275
+ .map(column => column.name));
276
+ const sessions = columns('code_sessions'), turns = columns('code_turns');
277
+ const indexed = !!this.database.prepare("SELECT 1 FROM sqlite_master WHERE type = 'index' AND name = 'idx_code_turns_boundary'").get();
278
+ if (indexed && sessions.has('rollback_since') && sessions.has('replay_floor_sequence') && sessions.has('history_generation')
279
+ && turns.has('native_prompt_uuid') && turns.has('removed_generation'))
280
+ return;
281
+ this.database.transaction(() => {
282
+ if (!sessions.has('replay_floor_sequence'))
283
+ this.database.exec('ALTER TABLE code_sessions ADD COLUMN replay_floor_sequence INTEGER NOT NULL DEFAULT 0');
284
+ if (!sessions.has('history_generation'))
285
+ this.database.exec('ALTER TABLE code_sessions ADD COLUMN history_generation INTEGER NOT NULL DEFAULT 0');
286
+ if (!turns.has('native_prompt_uuid'))
287
+ this.database.exec('ALTER TABLE code_turns ADD COLUMN native_prompt_uuid TEXT');
288
+ if (!turns.has('removed_generation'))
289
+ this.database.exec('ALTER TABLE code_turns ADD COLUMN removed_generation INTEGER');
290
+ this.database.exec(`CREATE INDEX IF NOT EXISTS idx_code_turns_boundary ON code_turns(session_id, accepted_sequence)
291
+ WHERE native_prompt_uuid IS NOT NULL AND removed_generation IS NULL`);
292
+ if (!sessions.has('rollback_since')) {
293
+ this.database.exec('ALTER TABLE code_sessions ADD COLUMN rollback_since INTEGER');
294
+ this.database.exec(`UPDATE code_sessions SET rollback_since = ${ROLLBACK_SINCE_SQL}`);
295
+ }
296
+ }).immediate();
297
+ }
298
+ /** Adds the per-turn byte budgets; an up-to-date database returns before taking the write lock. */
212
299
  ensureBudgetColumns() {
300
+ const budgetColumns = ['event_bytes', 'control_event_bytes', 'settlement_bytes'];
301
+ const present = () => new Set(this.database.prepare('PRAGMA table_info(code_turns)').all()
302
+ .map(column => column.name));
303
+ const before = present();
304
+ if (budgetColumns.every(column => before.has(column)))
305
+ return;
213
306
  this.database.transaction(() => {
214
- const columns = this.database.prepare('PRAGMA table_info(code_turns)').all();
215
- const names = new Set(columns.map(column => column.name));
216
- for (const column of ['event_bytes', 'control_event_bytes', 'settlement_bytes']) {
307
+ // Read again under the lock: another process may have migrated in between.
308
+ const names = present();
309
+ for (const column of budgetColumns) {
217
310
  if (!names.has(column))
218
311
  this.database.exec(`ALTER TABLE code_turns ADD COLUMN ${column} INTEGER NOT NULL DEFAULT 0`);
219
312
  }
@@ -252,6 +345,14 @@ export class CodeStore {
252
345
  throw new CodeStoreError('session_not_found', 'Code session not found', 404);
253
346
  return record;
254
347
  }
348
+ /**
349
+ * Refresh the stored rollback start after boundaries or user rows change, before the session
350
+ * event that saves it. Every writer of either calls this.
351
+ */
352
+ readRollbackSince(sessionId) {
353
+ return this.database.prepare(`SELECT ${ROLLBACK_SINCE_SQL} AS since FROM code_sessions WHERE session_id = ?`)
354
+ .get(sessionId)?.since ?? null;
355
+ }
255
356
  read(sessionId) {
256
357
  const record = this.readRecord(sessionId);
257
358
  return record ? toCodeSessionInfo(record) : null;
@@ -353,9 +454,13 @@ export class CodeStore {
353
454
  }
354
455
  const size = pageLimit(limit, CODE_EVENT_PAGE_MAX);
355
456
  return this.database.transaction(() => {
356
- const throughSequence = this.requireRecord(sessionId).sequence;
457
+ const record = this.requireRecord(sessionId);
458
+ const throughSequence = record.sequence;
357
459
  if (afterSequence > throughSequence)
358
460
  throw new CodeStoreError('invalid_sequence', 'Sequence exceeds session watermark', 409);
461
+ // Events before a rollback describe items it removed; only a new snapshot is consistent.
462
+ if (afterSequence < record.replayFloorSequence)
463
+ throw new CodeStoreError('invalid_sequence', 'Conversation was rolled back; take a new snapshot', 409);
359
464
  const events = [];
360
465
  let bytes = jsonBytes({ events, nextSequence: throughSequence, throughSequence, hasMore: false });
361
466
  if (bytes > this.limits.maxReplayPageBytes)
@@ -437,8 +542,10 @@ export class CodeStore {
437
542
  save(record) {
438
543
  this.database.prepare(`UPDATE code_sessions SET title = ?, model = ?, effort = ?, permission_mode = ?,
439
544
  status = ?, active_turn_id = ?, archived_at = ?, error_json = ?, native_cursor = ?, native_started = ?,
440
- native_policy_json = ?, epoch = ?, sequence = ?, revision = ?, last_used_at = ? WHERE session_id = ?`)
441
- .run(record.title, record.model, record.effort, record.permissionMode, record.status, record.turnId, record.archivedAt, record.error === null ? null : JSON.stringify(mapError(record.error)), record.nativeCursor, Number(record.nativeStarted), record.nativePolicy === null ? null : JSON.stringify(record.nativePolicy), record.epoch, record.sequence, record.revision, record.lastUsedAt, record.sessionId);
545
+ native_policy_json = ?, epoch = ?, sequence = ?, revision = ?, last_used_at = ?,
546
+ last_turn_completed_at = ?, last_visited_at = ?, thinking = ?, replay_floor_sequence = ?,
547
+ history_generation = ?, rollback_since = ? WHERE session_id = ?`)
548
+ .run(record.title, record.model, record.effort, record.permissionMode, record.status, record.turnId, record.archivedAt, record.error === null ? null : JSON.stringify(mapError(record.error)), record.nativeCursor, Number(record.nativeStarted), record.nativePolicy === null ? null : JSON.stringify(record.nativePolicy), record.epoch, record.sequence, record.revision, record.lastUsedAt, record.lastTurnCompletedAt, record.lastVisitedAt, record.thinking === null ? null : Number(record.thinking), record.replayFloorSequence, record.historyGeneration, record.rollbackSince, record.sessionId);
442
549
  }
443
550
  persistEvent(record, event, mode, settlementDelta = 0, budgetTurnId = record.turnId) {
444
551
  const encoded = JSON.stringify(event);
@@ -467,7 +574,8 @@ export class CodeStore {
467
574
  this.save(record);
468
575
  return event;
469
576
  }
470
- event(record, item, mode = 'ordinary', budgetTurnId = record.turnId) {
577
+ /** `reserve` holds extra settlement bytes for a frame this item may need at settlement. */
578
+ event(record, item, mode = 'ordinary', budgetTurnId = record.turnId, reserve = 0) {
471
579
  record.sequence += 1;
472
580
  let retainedItem;
473
581
  let previousItem;
@@ -491,7 +599,7 @@ export class CodeStore {
491
599
  sequence: record.sequence, epoch: record.epoch,
492
600
  ...(update ? { update } : retainedItem ? { item: retainedItem } : { session: toCodeSessionInfo(record) }),
493
601
  };
494
- this.persistEvent(record, event, mode, settlementCost(record.sessionId, retainedItem) - settlementCost(record.sessionId, previousItem), budgetTurnId);
602
+ this.persistEvent(record, event, mode, settlementCost(record.sessionId, retainedItem) - settlementCost(record.sessionId, previousItem) + reserve, budgetTurnId);
495
603
  if (item)
496
604
  this.database.prepare(`INSERT INTO code_items (session_id, item_id, first_sequence, item_json)
497
605
  VALUES (?, ?, ?, ?) ON CONFLICT(session_id, item_id) DO UPDATE SET item_json = excluded.item_json`)
@@ -504,8 +612,8 @@ export class CodeStore {
504
612
  const sessionId = input.sessionId ?? this.newId();
505
613
  this.database.prepare(`INSERT INTO code_sessions
506
614
  (session_id, provider, cwd, title, model, effort, permission_mode, status,
507
- capabilities_json, created_at, last_used_at) VALUES (?, ?, ?, ?, ?, ?, ?, 'idle', ?, ?, ?)`)
508
- .run(sessionId, input.provider, input.cwd, input.title ?? null, input.model, input.effort, input.permissionMode, JSON.stringify(mapCapabilities(input.capabilities)), now, now);
615
+ capabilities_json, created_at, last_used_at, thinking) VALUES (?, ?, ?, ?, ?, ?, ?, 'idle', ?, ?, ?, ?)`)
616
+ .run(sessionId, input.provider, input.cwd, input.title ?? null, input.model, input.effort, input.permissionMode, JSON.stringify(mapCapabilities(input.capabilities)), now, now, input.thinking === undefined ? null : Number(input.thinking));
509
617
  const record = this.requireRecord(sessionId);
510
618
  const events = [this.event(record)];
511
619
  return { session: toCodeSessionInfo(record), events };
@@ -521,21 +629,139 @@ export class CodeStore {
521
629
  readTurn(sessionId, clientTurnKey) {
522
630
  const row = this.database.prepare(`SELECT ${TURN_COLUMNS} FROM code_turns WHERE session_id = ? AND client_turn_key = ?`)
523
631
  .get(sessionId, clientTurnKey);
632
+ if (!row)
633
+ this.refuseSteerKey(sessionId, clientTurnKey);
524
634
  return row ? receipt(row) : null;
525
635
  }
636
+ /** One client key names one message: a prompt key is never a follow-up key, or the reverse. */
637
+ refuseSteerKey(sessionId, clientTurnKey) {
638
+ if (this.database.prepare('SELECT 1 FROM code_steers WHERE session_id = ? AND client_turn_key = ?').get(sessionId, clientTurnKey)) {
639
+ throw new CodeStoreError('turn_key_conflict', 'Client turn key was used for a follow-up', 409);
640
+ }
641
+ }
642
+ steerRow(sessionId, clientTurnKey) {
643
+ return this.database.prepare(`SELECT ${STEER_COLUMNS} FROM code_steers WHERE session_id = ? AND client_turn_key = ?`)
644
+ .get(sessionId, clientTurnKey);
645
+ }
646
+ /** A follow-up receipt reports its owning turn as a prompt receipt does: a turn a rollback removed reads cancelled. */
647
+ turnStatus(sessionId, turnId) {
648
+ const turn = this.database.prepare('SELECT status, removed_generation FROM code_turns WHERE session_id = ? AND turn_id = ?')
649
+ .get(sessionId, turnId);
650
+ return turn.removed_generation === null ? turn.status : 'cancelled';
651
+ }
652
+ /** The follow-up key state machine; null means a new key that may be reserved. */
653
+ steerKey(sessionId, clientTurnKey, text) {
654
+ if (!text.trim() || !clientTurnKey.trim())
655
+ throw new CodeStoreError('invalid_prompt', 'Text and client turn key are required', 400);
656
+ this.requireRecord(sessionId);
657
+ if (this.database.prepare('SELECT 1 FROM code_turns WHERE session_id = ? AND client_turn_key = ?').get(sessionId, clientTurnKey)) {
658
+ throw new CodeStoreError('turn_key_conflict', 'Client turn key was used for a prompt', 409);
659
+ }
660
+ const row = this.steerRow(sessionId, clientTurnKey);
661
+ if (!row)
662
+ return null;
663
+ if (row.prompt_hash !== promptHash(text))
664
+ throw new CodeStoreError('turn_key_conflict', 'Client turn key was used for different content', 409);
665
+ if (row.status === 'reserved')
666
+ throw new CodeStoreError('steer_in_flight', 'This follow-up is still being delivered', 409);
667
+ if (row.status === 'rejected')
668
+ throw new CodeStoreError('steer_key_spent', 'This follow-up was refused; send it again as a new message', 409);
669
+ if (row.status === 'unknown')
670
+ throw new CodeStoreError('steer_outcome_unknown', 'Follow-up delivery could not be confirmed', 503);
671
+ return { turnId: row.turn_id, clientTurnKey, sequence: row.accepted_sequence, status: this.turnStatus(sessionId, row.turn_id) };
672
+ }
673
+ /** The committed receipt of a follow-up key, whatever its turn has become since. */
674
+ readSteer(sessionId, input) {
675
+ return this.database.transaction(() => this.steerKey(sessionId, input.clientTurnKey, input.text))();
676
+ }
677
+ /**
678
+ * Take the running turn's one follow-up slot before any native offer. No events: the
679
+ * message joins the transcript only through commitSteer, after the runtime accepted it.
680
+ */
681
+ reserveSteer(input) {
682
+ return this.write(() => {
683
+ const replay = this.steerKey(input.sessionId, input.clientTurnKey, input.text);
684
+ if (replay)
685
+ return { reservationId: input.clientTurnKey, receipt: replay };
686
+ const record = this.requireRecord(input.sessionId);
687
+ if (record.epoch !== input.epoch || record.turnId !== input.turnId) {
688
+ throw new CodeStoreError('stale_owner', 'Code turn ownership has changed', 409);
689
+ }
690
+ if (record.archivedAt !== null || record.status !== 'streaming' || record.turnId === null) {
691
+ throw new CodeStoreError('session_not_steerable', 'This turn does not take a follow-up right now', 409);
692
+ }
693
+ const turnId = record.turnId;
694
+ if (this.database.prepare(`SELECT 1 FROM code_steers WHERE session_id = ? AND turn_id = ?
695
+ AND status IN ('reserved', 'committed', 'unknown')`).get(input.sessionId, turnId)) {
696
+ throw new CodeStoreError('steer_queue_full', 'The running turn already has its follow-up', 409);
697
+ }
698
+ // Refuse before the native offer what commitSteer could not store, including the reserve for
699
+ // the settlement frame that may re-mark this item; digits a later sequence adds are covered.
700
+ const item = steerItem(turnId, input.clientTurnKey, input.text, this.now());
701
+ const bytes = jsonBytes({ topic: 'code', event: 'code_item', sessionId: record.sessionId, sequence: record.sequence + 1,
702
+ epoch: record.epoch, item: mapItem(item, record.sequence + 1) }) + 32;
703
+ const budget = this.database.prepare(`SELECT event_bytes, control_event_bytes, settlement_bytes FROM code_turns
704
+ WHERE session_id = ? AND turn_id = ?`).get(record.sessionId, turnId);
705
+ if (bytes > this.limits.maxEventBytes
706
+ || budget.event_bytes - budget.control_event_bytes + bytes > this.limits.maxTurnEventBytes
707
+ || budget.control_event_bytes + budget.settlement_bytes + settlementCost(record.sessionId, { ...item, status: 'pending' })
708
+ + CODE_SETTLEMENT_TAIL_BYTES > CODE_TERMINAL_RESERVE_BYTES) {
709
+ throw new CodeStoreError('transcript_limit', 'Code turn event byte limit reached', 409);
710
+ }
711
+ this.database.prepare(`INSERT INTO code_steers (session_id, turn_id, client_turn_key, prompt_hash, status)
712
+ VALUES (?, ?, ?, ?, 'reserved')`).run(input.sessionId, turnId, input.clientTurnKey, promptHash(input.text));
713
+ return { reservationId: input.clientTurnKey, receipt: null };
714
+ });
715
+ }
716
+ /** The runtime accepted the follow-up: append it to the running turn and spend the reservation. */
717
+ commitSteer(sessionId, reservationId, text, nativeId) {
718
+ return this.write(() => {
719
+ const row = this.steerRow(sessionId, reservationId);
720
+ const record = this.requireRecord(sessionId);
721
+ if (!row || row.status !== 'reserved' || row.prompt_hash !== promptHash(text) || record.turnId !== row.turn_id
722
+ || record.archivedAt !== null || record.status !== 'streaming') {
723
+ throw new CodeStoreError('stale_owner', 'Follow-up reservation is no longer open', 409);
724
+ }
725
+ const item = steerItem(row.turn_id, reservationId, text, this.now());
726
+ const events = [this.event(record, item, 'ordinary', row.turn_id, settlementCost(sessionId, { ...item, status: 'pending' }))];
727
+ this.database.prepare(`UPDATE code_steers SET status = 'committed', accepted_sequence = ?, native_uuid = ?
728
+ WHERE session_id = ? AND client_turn_key = ?`).run(record.sequence, nativeId, sessionId, reservationId);
729
+ return { session: toCodeSessionInfo(record), events,
730
+ receipt: { turnId: row.turn_id, clientTurnKey: reservationId, sequence: record.sequence, status: this.turnStatus(sessionId, row.turn_id) } };
731
+ });
732
+ }
733
+ /**
734
+ * A definitive refusal before native acceptance: the key is spent, the turn's slot is free again.
735
+ * A turn that settled while the offer was in flight left the row `unknown`; the in-flight call
736
+ * that then learns the runtime refused the offer is the one owner that can make it definitive.
737
+ */
738
+ rejectSteer(sessionId, reservationId) {
739
+ this.write(() => {
740
+ this.database.prepare(`UPDATE code_steers SET status = 'rejected' WHERE session_id = ? AND client_turn_key = ?
741
+ AND status IN ('reserved', 'unknown')`).run(sessionId, reservationId);
742
+ });
743
+ }
744
+ /** Native input left the process without a durable record: the slot stays spent and the key answers unknown. */
745
+ markSteerUnknown(sessionId, reservationId) {
746
+ this.write(() => {
747
+ this.database.prepare(`UPDATE code_steers SET status = 'unknown' WHERE session_id = ? AND client_turn_key = ?
748
+ AND status <> 'committed'`).run(sessionId, reservationId);
749
+ });
750
+ }
526
751
  admitTurn(input) {
527
752
  return this.write(() => {
528
753
  if (!input.text.trim() || !input.clientTurnKey.trim())
529
754
  throw new CodeStoreError('invalid_prompt', 'Text and client turn key are required', 400);
530
755
  const record = this.requireRecord(input.sessionId);
531
- const promptHash = createHash('sha256').update(input.text).digest('hex');
756
+ const hash = promptHash(input.text);
532
757
  const previous = this.database.prepare(`SELECT ${TURN_COLUMNS} FROM code_turns WHERE session_id = ? AND client_turn_key = ?`)
533
758
  .get(input.sessionId, input.clientTurnKey);
534
759
  if (previous) {
535
- if (previous.prompt_hash !== promptHash)
760
+ if (previous.prompt_hash !== hash)
536
761
  throw new CodeStoreError('turn_key_conflict', 'Client turn key was used for different content', 409);
537
- return { session: toCodeSessionInfo(record), events: [], receipt: receipt(previous), duplicate: true };
762
+ return { session: toCodeSessionInfo(record), events: [], receipt: receipt(previous), duplicate: true, promptUuid: null };
538
763
  }
764
+ this.refuseSteerKey(input.sessionId, input.clientTurnKey);
539
765
  if (record.archivedAt !== null)
540
766
  throw new CodeStoreError('session_archived', 'Code session is archived', 409);
541
767
  if (input.expectedRevision !== undefined && input.expectedRevision !== record.revision) {
@@ -556,17 +782,20 @@ export class CodeStore {
556
782
  record.title = initialTitle(input.text) || null;
557
783
  record.nativePolicy = { model: record.model, effort: record.effort, permissionMode: record.permissionMode };
558
784
  const acceptedSequence = record.sequence + 3;
785
+ // The private native input identity; the provider sends the prompt under it.
786
+ const promptUuid = record.provider === 'claude' ? randomUUID() : null;
559
787
  this.database.prepare(`INSERT INTO code_turns
560
- (session_id, turn_id, client_turn_key, prompt_hash, status, accepted_sequence)
561
- VALUES (?, ?, ?, ?, 'accepted', ?)`)
562
- .run(input.sessionId, turnId, input.clientTurnKey, promptHash, acceptedSequence);
788
+ (session_id, turn_id, client_turn_key, prompt_hash, status, accepted_sequence, native_prompt_uuid)
789
+ VALUES (?, ?, ?, ?, 'accepted', ?, ?)`)
790
+ .run(input.sessionId, turnId, input.clientTurnKey, hash, acceptedSequence, promptUuid);
563
791
  const events = [
564
792
  this.event(record, { itemId: `${turnId}:user`, turnId, kind: 'user_message', status: 'done',
565
793
  text: input.text, clientTurnKey: input.clientTurnKey, createdAt: now, updatedAt: now }),
566
794
  this.event(record, { itemId: `${turnId}:started`, turnId, kind: 'turn_started', status: 'running', createdAt: now, updatedAt: now }),
567
- this.event(record),
568
795
  ];
569
- return { session: toCodeSessionInfo(record), events, duplicate: false,
796
+ record.rollbackSince = this.readRollbackSince(input.sessionId);
797
+ events.push(this.event(record));
798
+ return { session: toCodeSessionInfo(record), events, duplicate: false, promptUuid,
570
799
  receipt: { turnId, clientTurnKey: input.clientTurnKey, sequence: acceptedSequence, status: 'accepted' } };
571
800
  });
572
801
  }
@@ -591,6 +820,15 @@ export class CodeStore {
591
820
  return { session: toCodeSessionInfo(record), events };
592
821
  });
593
822
  }
823
+ /** The Manager opened this session. A read receipt: the metadata revision does not move. */
824
+ markVisited(sessionId) {
825
+ return this.write(() => {
826
+ const record = this.requireRecord(sessionId);
827
+ record.lastVisitedAt = this.now();
828
+ const events = [this.event(record)];
829
+ return { session: toCodeSessionInfo(record), events };
830
+ });
831
+ }
594
832
  /** Reserve an explicit resume without inventing a prompt or consuming a client key. */
595
833
  beginAttach(sessionId, expectedRevision) {
596
834
  return this.write(() => {
@@ -642,14 +880,22 @@ export class CodeStore {
642
880
  throw new CodeStoreError('stale_owner', 'Native cursor owner is no longer writable', 409);
643
881
  if (cursor !== null && !cursor.trim())
644
882
  throw new CodeStoreError('invalid_cursor', 'Native cursor must be nonempty', 400);
883
+ // Boundaries belong to the native session that recorded them. A replaced identity
884
+ // retires them; the first identity, and the active turn now sent to it, keep theirs.
885
+ if (cursor !== null && record.nativeCursor !== null && cursor !== record.nativeCursor) {
886
+ this.database.prepare('UPDATE code_turns SET native_prompt_uuid = NULL WHERE session_id = ? AND turn_id IS NOT ?')
887
+ .run(record.sessionId, record.turnId);
888
+ }
645
889
  record.nativeStarted = true;
646
890
  if (cursor !== null)
647
891
  record.nativeCursor = cursor;
892
+ record.rollbackSince = this.readRollbackSince(record.sessionId);
648
893
  const events = [this.event(record)];
649
894
  return { session: toCodeSessionInfo(record), events };
650
895
  });
651
896
  }
652
- finish(record, result) {
897
+ /** `lostRuntime`: a restart settles the turn, and nothing can confirm what its runtime consumed. */
898
+ finish(record, result, lostRuntime = false) {
653
899
  const turn = this.database.prepare(`SELECT ${TURN_COLUMNS} FROM code_turns WHERE session_id = ? AND turn_id = ?`)
654
900
  .get(record.sessionId, record.turnId);
655
901
  if (!turn)
@@ -684,16 +930,49 @@ export class CodeStore {
684
930
  .run(status, now, record.sessionId, item.item_id);
685
931
  after = item.first_sequence;
686
932
  }
933
+ // A follow-up no native result consumed may never have reached Claude; say so on its item.
934
+ // After a restart every committed follow-up of the turn reads that way.
935
+ const undelivered = new Set(result.undeliveredFollowUps ?? []);
936
+ const steers = this.database.prepare(`SELECT client_turn_key, native_uuid FROM code_steers
937
+ WHERE session_id = ? AND turn_id = ? AND status = 'committed'`).all(record.sessionId, turnId);
938
+ for (const steer of steers) {
939
+ const itemId = `${turnId}:steer:${steer.client_turn_key}`;
940
+ const projected = this.database.prepare('SELECT first_sequence FROM code_items WHERE session_id = ? AND item_id = ?')
941
+ .get(record.sessionId, itemId);
942
+ const unconfirmed = lostRuntime || (steer.native_uuid !== null && undelivered.has(steer.native_uuid));
943
+ if (!unconfirmed || !projected)
944
+ continue;
945
+ record.sequence += 1;
946
+ events.push(this.persistEvent(record, {
947
+ topic: 'code', event: 'code_item_update', sessionId: record.sessionId, sequence: record.sequence, epoch: record.epoch,
948
+ update: { itemId, turnId, firstSequence: projected.first_sequence, phase: 'unknown', updatedAt: now },
949
+ }, 'settlement', -settlementCost(record.sessionId, { itemId, turnId, status: 'pending' }), turnId));
950
+ this.database.prepare(`UPDATE code_items SET item_json = json_set(item_json, '$.phase', 'unknown', '$.updatedAt', ?)
951
+ WHERE session_id = ? AND item_id = ?`).run(now, record.sessionId, itemId);
952
+ }
953
+ // A reservation still open here may already have been offered to the runtime: its key answers
954
+ // unknown (never "send it again"), and the turn's slot stays spent.
955
+ this.database.prepare(`UPDATE code_steers SET status = 'unknown' WHERE session_id = ? AND turn_id = ? AND status = 'reserved'`)
956
+ .run(record.sessionId, turnId);
687
957
  const kind = result.status === 'completed' ? 'turn_completed' : result.status === 'failed' ? 'turn_failed' : 'turn_cancelled';
688
958
  events.push(this.event(record, { itemId: `${record.turnId}:terminal`, turnId: record.turnId, kind,
689
959
  status: result.status === 'completed' ? 'done' : result.status === 'failed' ? 'error' : 'cancelled',
690
960
  ...(error ? { text: error.message } : {}), createdAt: now, updatedAt: now }, 'settlement', turnId));
691
961
  this.database.prepare('UPDATE code_turns SET status = ? WHERE session_id = ? AND turn_id = ?')
692
962
  .run(result.status, record.sessionId, record.turnId);
963
+ // A prompt that never reached the native runtime is absent from its history.
964
+ if (result.dispatched === false) {
965
+ this.database.prepare('UPDATE code_turns SET native_prompt_uuid = NULL WHERE session_id = ? AND turn_id = ?')
966
+ .run(record.sessionId, record.turnId);
967
+ record.rollbackSince = this.readRollbackSince(record.sessionId);
968
+ }
693
969
  record.turnId = null;
694
970
  record.status = result.status === 'failed' ? 'failed' : 'idle';
695
971
  record.error = error;
696
972
  record.lastUsedAt = now;
973
+ // The unread clock: a user-stopped turn is one they were watching, so it does not count.
974
+ if (result.status !== 'cancelled')
975
+ record.lastTurnCompletedAt = now;
697
976
  events.push(this.event(record, undefined, 'settlement', turnId));
698
977
  return { session: toCodeSessionInfo(record), events, receipt: { ...receipt(turn), status: result.status } };
699
978
  }
@@ -718,7 +997,8 @@ export class CodeStore {
718
997
  const record = this.requireRecord(sessionId);
719
998
  if (record.revision !== patch.expectedRevision)
720
999
  throw new CodeStoreError('revision_conflict', 'Code metadata changed', 409);
721
- const policyChange = patch.model !== undefined || patch.effort !== undefined || patch.permissionMode !== undefined;
1000
+ const policyChange = patch.model !== undefined || patch.effort !== undefined || patch.permissionMode !== undefined
1001
+ || patch.thinking !== undefined;
722
1002
  if (isBusy(record.status) && (policyChange || patch.archived !== undefined)) {
723
1003
  throw new CodeStoreError('session_busy', 'Active Code sessions cannot change policy or archive', 409);
724
1004
  }
@@ -730,6 +1010,8 @@ export class CodeStore {
730
1010
  record.effort = patch.effort;
731
1011
  if (patch.permissionMode !== undefined)
732
1012
  record.permissionMode = patch.permissionMode;
1013
+ if (patch.thinking !== undefined)
1014
+ record.thinking = patch.thinking;
733
1015
  if (patch.archived !== undefined)
734
1016
  record.archivedAt = patch.archived ? record.archivedAt ?? this.now() : null;
735
1017
  // Invalidates idle runtime callbacks after reconfiguration or archive.
@@ -741,6 +1023,109 @@ export class CodeStore {
741
1023
  return { session: toCodeSessionInfo(record), events };
742
1024
  });
743
1025
  }
1026
+ /** Kept and later turns around a settled `${turnId}:user` target; removed turns are not part of the conversation. */
1027
+ rollbackTurns(record, upToItemId) {
1028
+ const item = this.database.prepare(`SELECT json_extract(item_json, '$.kind') AS kind, json_extract(item_json, '$.turnId') AS turn_id
1029
+ FROM code_items WHERE session_id = ? AND item_id = ?`).get(record.sessionId, upToItemId);
1030
+ const turnId = item?.turn_id;
1031
+ if (!item || item.kind !== 'user_message' || typeof turnId !== 'string' || `${turnId}:user` !== upToItemId) {
1032
+ throw new CodeStoreError('rollback_target_not_found', 'Rollback target is not a turn in this conversation', 404);
1033
+ }
1034
+ const turns = this.database.prepare(`SELECT turn_id, status, accepted_sequence, native_prompt_uuid FROM code_turns
1035
+ WHERE session_id = ? AND removed_generation IS NULL ORDER BY accepted_sequence`).all(record.sessionId);
1036
+ const index = turns.findIndex(turn => turn.turn_id === turnId);
1037
+ const target = turns[index];
1038
+ if (!target)
1039
+ throw new CodeStoreError('rollback_target_not_found', 'Rollback target is not a turn in this conversation', 404);
1040
+ if (target.status !== 'completed' && target.status !== 'failed' && target.status !== 'cancelled') {
1041
+ throw new CodeStoreError('session_busy', 'The rollback target has not settled', 409);
1042
+ }
1043
+ return { kept: turns.slice(0, index + 1), later: turns.slice(index + 1) };
1044
+ }
1045
+ /**
1046
+ * Validate a rollback to the item's turn without writing anything. The target must carry a
1047
+ * boundary; later turns are passed as they are, boundary or not, and the provider proves
1048
+ * where the fork ends (see `forkClaudeHistory`) before any fork.
1049
+ */
1050
+ readRollbackPlan(sessionId, upToItemId) {
1051
+ return this.database.transaction(() => {
1052
+ const record = this.requireRecord(sessionId);
1053
+ if (record.archivedAt !== null)
1054
+ throw new CodeStoreError('session_archived', 'Code session is archived', 409);
1055
+ if (record.provider !== 'claude' || !record.nativeCursor)
1056
+ throw new CodeStoreError('rollback_unavailable', 'Conversation history is unavailable', 409);
1057
+ const { kept, later } = this.rollbackTurns(record, upToItemId);
1058
+ const target = kept.at(-1);
1059
+ if (!later.length)
1060
+ throw new CodeStoreError('rollback_noop', 'The target is already the latest turn', 409);
1061
+ if (target.native_prompt_uuid === null) {
1062
+ throw new CodeStoreError('rollback_boundary_unavailable', 'This turn has no recorded conversation boundary', 409);
1063
+ }
1064
+ const turn = (row) => ({ turnId: row.turn_id, promptUuid: row.native_prompt_uuid });
1065
+ return { target: { turnId: target.turn_id, promptUuid: target.native_prompt_uuid }, kept: kept.map(turn),
1066
+ later: later.map(turn), revision: record.revision, epoch: record.epoch, nativeCursor: record.nativeCursor,
1067
+ title: record.title };
1068
+ })();
1069
+ }
1070
+ /**
1071
+ * Commit a verified fork in one transaction, compare-and-swap on the revision, epoch and
1072
+ * native cursor the plan was read at. Later items and their events leave; their turn rows
1073
+ * stay so client keys keep answering, marked removed. Replay below the new floor fails with
1074
+ * `invalid_sequence`, and the history generation tells live readers to take a snapshot.
1075
+ * Follow-up rows (`code_steers`) stay as they are: their keys remain spent and their native
1076
+ * ids are not remapped (only a running turn reads them). The follow-up items of removed turns
1077
+ * leave with the other later items, and their receipts read cancelled like the turns'.
1078
+ */
1079
+ commitRollback(input) {
1080
+ return this.write(() => {
1081
+ const record = this.requireRecord(input.sessionId);
1082
+ if (record.revision !== input.expectedRevision || record.epoch !== input.expectedEpoch
1083
+ || record.nativeCursor !== input.expectedCursor || record.turnId !== null || isBusy(record.status)
1084
+ || record.archivedAt !== null || record.provider !== 'claude') {
1085
+ throw new CodeStoreError('revision_conflict', 'Code session changed during rollback', 409);
1086
+ }
1087
+ if (!input.forkCursor.trim() || input.forkCursor === input.expectedCursor) {
1088
+ throw new CodeStoreError('invalid_cursor', 'Rollback fork must be a new native session', 400);
1089
+ }
1090
+ const { kept, later } = this.rollbackTurns(record, input.upToItemId);
1091
+ if (!later.length)
1092
+ throw new CodeStoreError('rollback_noop', 'The target is already the latest turn', 409);
1093
+ const keptIds = new Set(kept.map(turn => turn.turn_id));
1094
+ if (!input.remapped.every(turn => keptIds.has(turn.turnId) && !!turn.promptUuid.trim())
1095
+ || !input.cleared.every(turnId => keptIds.has(turnId))) {
1096
+ throw new CodeStoreError('rollback_unavailable', 'Rollback boundaries do not match the kept turns', 409);
1097
+ }
1098
+ const generation = record.historyGeneration + 1;
1099
+ const after = kept.at(-1).accepted_sequence;
1100
+ const cut = this.database.prepare(`SELECT MIN(first_sequence) AS cut FROM code_items WHERE session_id = ?
1101
+ AND json_extract(item_json, '$.turnId') IN (SELECT turn_id FROM code_turns WHERE session_id = ?
1102
+ AND removed_generation IS NULL AND accepted_sequence > ?)`)
1103
+ .get(record.sessionId, record.sessionId, after).cut;
1104
+ if (cut !== null) {
1105
+ this.database.prepare('DELETE FROM code_items WHERE session_id = ? AND first_sequence >= ?').run(record.sessionId, cut);
1106
+ this.database.prepare('DELETE FROM code_events WHERE session_id = ? AND sequence >= ?').run(record.sessionId, cut);
1107
+ }
1108
+ this.database.prepare(`UPDATE code_turns SET removed_generation = ? WHERE session_id = ?
1109
+ AND removed_generation IS NULL AND accepted_sequence > ?`).run(generation, record.sessionId, after);
1110
+ // Every source boundary is retired; only the ones found again in the fork return.
1111
+ this.database.prepare('UPDATE code_turns SET native_prompt_uuid = NULL WHERE session_id = ?').run(record.sessionId);
1112
+ const remap = this.database.prepare('UPDATE code_turns SET native_prompt_uuid = ? WHERE session_id = ? AND turn_id = ?');
1113
+ for (const turn of input.remapped)
1114
+ remap.run(turn.promptUuid, record.sessionId, turn.turnId);
1115
+ record.historyGeneration = generation;
1116
+ record.nativeCursor = input.forkCursor;
1117
+ record.nativeStarted = true;
1118
+ record.revision += 1;
1119
+ record.epoch += 1;
1120
+ record.status = 'idle';
1121
+ record.error = null;
1122
+ record.lastUsedAt = this.now();
1123
+ record.rollbackSince = this.readRollbackSince(record.sessionId);
1124
+ record.replayFloorSequence = record.sequence + 1;
1125
+ const events = [this.event(record)];
1126
+ return { session: toCodeSessionInfo(record), events };
1127
+ });
1128
+ }
744
1129
  /** Call once before the manager admits work. Never replays a native prompt. */
745
1130
  recoverInterrupted() {
746
1131
  return this.write(() => {
@@ -751,8 +1136,11 @@ export class CodeStore {
751
1136
  const record = rowToRecord(row);
752
1137
  record.epoch += 1;
753
1138
  const error = { code: 'orphaned_turn', message: 'Code turn interrupted by server restart', at: this.now(), recoverable: true };
754
- if (record.turnId !== null)
755
- events.push(...this.finish(record, { status: 'failed', error }).events);
1139
+ // A turn is marked streaming before its prompt is handed to the runtime, so one still
1140
+ // starting never sent it: its boundary cannot be in native history.
1141
+ if (record.turnId !== null) {
1142
+ events.push(...this.finish(record, { status: 'failed', error, ...(row.status === 'starting' ? { dispatched: false } : {}) }, true).events);
1143
+ }
756
1144
  else {
757
1145
  record.status = 'failed';
758
1146
  record.error = error;