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
@@ -6,10 +6,11 @@ import type { CodeControllerModel, CodeControllerOptions, CodeSessionFilter, Cod
6
6
  import { CodeClientError, codeBaseOrigin, createCodeSessionClient, type CodeGitInfo } from './code-session-client';
7
7
  import { emptyCodeSession, reduceCodeSession, codeSessionBusy, type CodeSessionState } from './code-session-state';
8
8
  import {
9
- acknowledgeCodeSend, catalogSelection, codeDraftBook, createCodeDraft, persistCodeDraftBook, sessionSelection,
9
+ acknowledgeCodeSend, acknowledgeCodeSteer, catalogSelection, codeDraftBook, createCodeDraft, persistCodeDraftBook, sessionSelection,
10
10
  type CodeDraft, type CodeDraftBook,
11
11
  } from './code-controller-drafts';
12
12
  import { withPendingUserItem } from './pending-user-item';
13
+ import { codeCanResume, codeCanRollback, codeResendCopy, type CodeResendReason } from './code-types';
13
14
 
14
15
  const MAX_DETAILS = 6;
15
16
  const MAX_INDEX = 1000;
@@ -18,6 +19,14 @@ const MAX_CATCHUP_PAGES = 32;
18
19
  const INDEX_DEBOUNCE_MS = 150;
19
20
  const message = (error: unknown) => error instanceof CodeClientError ? error.message : 'Connection lost. Refresh to check the current state.';
20
21
  const rejected = (error: unknown) => error instanceof CodeClientError && error.status >= 400 && error.status < 500;
22
+ // A lost response may still be committed, so its message can arrive; a `steer_outcome_unknown` answer
23
+ // means the server could not record it, so it never will.
24
+ const FOLLOW_UP_UNCONFIRMED = 'Follow-up delivery not confirmed. It was not resent; it appears in the conversation if Claude received it.';
25
+ const FOLLOW_UP_UNRECORDED = 'Follow-up delivery not confirmed. Claude may have received it, but it will not appear in the conversation. It was not resent.';
26
+ // A rollback posts the revision and epoch the user chose the row at; PATCH's settings copy does not fit its conflict.
27
+ const ROLLBACK_CONFLICT = 'The conversation changed since you chose this point. Review it and try again.';
28
+ // A rollback seen from elsewhere, for a send whose key nothing ties to a removed turn.
29
+ const ROLLED_BACK_ELSEWHERE = 'The conversation was rolled back elsewhere; this message was not sent.';
21
30
  const newer = (incoming: CodeSessionInfo, current?: CodeSessionInfo | null) => !current
22
31
  || incoming.epoch > current.epoch || (incoming.epoch === current.epoch && (incoming.sequence > current.sequence
23
32
  || (incoming.sequence === current.sequence && incoming.revision >= current.revision)));
@@ -89,6 +98,7 @@ export class CodeController {
89
98
  private gitError: string | null = null;
90
99
  private gitGeneration = 0;
91
100
  private pickerGeneration = 0;
101
+ private workspacePicking = false;
92
102
  private indexGeneration = 0;
93
103
  private catalogGeneration = 0;
94
104
  private lifetime = 0;
@@ -213,7 +223,14 @@ export class CodeController {
213
223
  }
214
224
  private accept(incoming: CodeSessionInfo): void {
215
225
  if (!this.active) return;
216
- if (!newer(incoming, this.info(incoming.sessionId))) return;
226
+ const before = this.info(incoming.sessionId);
227
+ if (!newer(incoming, before)) return;
228
+ // A turn that finishes while its session is open was seen: receipt it so the
229
+ // sidebar does not mark it unread. The receipt's own echo leaves the clock alone.
230
+ if (incoming.sessionId === this.book.selectedId && before
231
+ && (incoming.lastTurnCompletedAt ?? 0) > (before.lastTurnCompletedAt ?? 0)) {
232
+ void this.markVisited(incoming.sessionId);
233
+ }
217
234
  // The ranked record carries identity and lifecycle only. Attached fields are
218
235
  // dropped by copy, never by mutation: readIndex hands the same row to
219
236
  // observe(), and update() hands over details.session, which the reducer may
@@ -225,6 +242,8 @@ export class CodeController {
225
242
  if (victim) { this.summaries.delete(victim); this.attention.delete(victim); }
226
243
  }
227
244
  const draft = this.book.sessions.get(session.sessionId);
245
+ // Once the server reports the turn, its own status carries the working state.
246
+ if (draft?.awaitingTurn && codeSessionBusy(session)) draft.awaitingTurn = null;
228
247
  if (draft?.stopTarget && (session.turnId !== draft.stopTarget.turnId || session.epoch !== draft.stopTarget.epoch
229
248
  || !codeSessionBusy(session))) {
230
249
  draft.stopTarget = null;
@@ -253,13 +272,36 @@ export class CodeController {
253
272
  }
254
273
  }
255
274
  private update(id: string, state: CodeSessionState): void {
275
+ // A trimmed or reloaded detail holds no session of its own; the index copy, read before
276
+ // accept() below can replace it, is then the last generation this page saw.
277
+ const before = this.details.get(id)?.session ?? this.summaries.get(id);
256
278
  this.details.set(id, state);
257
279
  if (state.session) this.accept(state.session);
258
280
  const draft = this.book.sessions.get(id);
281
+ if (draft && state.session && state.hydrated && !state.needsSnapshot) {
282
+ const generation = state.session.historyGeneration ?? 0;
283
+ if (before && generation > (before.historyGeneration ?? 0)) this.rolledBack(draft, state);
284
+ // Also without that copy: once any rollback happened, a follow-up whose turn is gone
285
+ // can no longer appear, so its unconfirmed notice goes.
286
+ const steer = draft.steer;
287
+ if (generation > 0 && steer?.state === 'unknown' && !state.items.some(item => item.turnId === steer.turnId)) draft.steer = null;
288
+ }
259
289
  if (draft?.retry && state.items.some(item => item.kind === 'user_message' && item.clientTurnKey === draft.retry!.key)) {
260
290
  if (deadSend(state, draft.retry.key)) this.requireNewKey(draft);
261
291
  else acknowledgeCodeSend(draft, draft.retry.key);
262
292
  }
293
+ if (draft?.steer && state.items.some(item => item.kind === 'user_message' && item.clientTurnKey === draft.steer!.key)) {
294
+ acknowledgeCodeSteer(draft, draft.steer.key);
295
+ }
296
+ if (draft?.awaitingTurn) {
297
+ // Only this send's own turn ending clears the marker: a stale read receipt or
298
+ // an older sequence must not switch a fresh send's spinner off.
299
+ const turnId = state.items.find(item => item.kind === 'user_message' && item.clientTurnKey === draft.awaitingTurn)?.turnId;
300
+ if (turnId && state.items.some(item => item.turnId === turnId
301
+ && (item.kind === 'turn_completed' || item.kind === 'turn_failed' || item.kind === 'turn_cancelled'))) {
302
+ draft.awaitingTurn = null;
303
+ }
304
+ }
263
305
  if (draft && state.synced) {
264
306
  const current = new Set(state.permissions.map(p => p.permissionId));
265
307
  for (const key of Object.keys(draft.permissionOperations)) {
@@ -276,10 +318,40 @@ export class CodeController {
276
318
  * can actually be admitted, and it also stops the settled turn's own
277
319
  * `user_message` from matching, so this fires once rather than on every read.
278
320
  */
279
- private requireNewKey(draft: CodeDraft): void {
321
+ private requireNewKey(draft: CodeDraft, reason?: CodeResendReason): void {
280
322
  if (!draft.retry) return;
281
- draft.retry = { ...draft.retry, key: crypto.randomUUID(), resend: true };
282
- draft.operation = { kind: 'unknown-send', error: 'The original attempt ended on the server without running. The message was not resent; Retry will submit it as a new message.' };
323
+ const { resendReason: _previous, ...retry } = draft.retry;
324
+ draft.retry = { ...retry, key: crypto.randomUUID(), resend: true, ...(reason ? { resendReason: reason } : {}) };
325
+ draft.operation = { kind: 'unknown-send', error: codeResendCopy(reason).error };
326
+ }
327
+ /**
328
+ * A rollback removed turns, possibly the one this draft is waiting on. A send whose own
329
+ * row is gone no longer has a turn to wait for. An unconfirmed send keeps its key: seeing
330
+ * its row would have settled it, so nothing here ties the key to a removed turn. A same-key
331
+ * retry is then admitted once, or answers `cancelled` and submit() names the rollback. A
332
+ * key already retired says it was not sent. (An unconfirmed follow-up of a removed turn is
333
+ * dropped by update(); one still in flight settles on its own answer.)
334
+ */
335
+ private rolledBack(draft: CodeDraft, state: CodeSessionState): void {
336
+ const kept = (key: string) => state.items.some(item => item.kind === 'user_message' && item.clientTurnKey === key);
337
+ if (draft.awaitingTurn && !kept(draft.awaitingTurn)) draft.awaitingTurn = null;
338
+ if (draft.retry && !draft.retry.resend && !kept(draft.retry.key)
339
+ && (draft.operation.kind === 'idle' || draft.operation.kind === 'unknown-send')) {
340
+ draft.operation = { kind: 'unknown-send', error: ROLLED_BACK_ELSEWHERE };
341
+ }
342
+ }
343
+ /**
344
+ * A spent key answers `cancelled` both for a turn that was stopped and for one a rollback
345
+ * removed. Every turn this detail has read past (its cursor is at or after the receipt), in
346
+ * a window that reaches back to it, still has items unless a rollback deleted them, so the
347
+ * turn's absence there says which. The HTTP receipt is all a reloaded page has: the
348
+ * transcript that held the turn is gone with the rollback.
349
+ */
350
+ private removedByRollback(id: string, receipt: CodePromptReceipt): boolean {
351
+ const state = this.details.get(id);
352
+ return receipt.status === 'cancelled' && !!state?.hydrated && (state.session?.historyGeneration ?? 0) > 0
353
+ && receipt.sequence <= state.cursor && (!state.hasOlder || (state.beforeSequence ?? Infinity) <= receipt.sequence)
354
+ && !state.items.some(item => item.turnId === receipt.turnId);
283
355
  }
284
356
  private makeModel(): CodeControllerModel {
285
357
  const id = this.book.selectedId;
@@ -295,28 +367,39 @@ export class CodeController {
295
367
  const session = canonical ? publish(canonical, detail) : canonical;
296
368
  const operation = draft.operation;
297
369
  const persistenceWarning = this.book.storageWarning ?? this.book.recoveryWarning;
298
- const pending = ['creating', 'sending', 'stopping', 'resuming', 'patching'].includes(operation.kind) && !operation.error;
370
+ const pending = ['creating', 'sending', 'stopping', 'resuming', 'patching', 'rolling-back'].includes(operation.kind) && !operation.error;
299
371
  const synced = id === null ? !!this.catalog : !!detail?.synced && (!session || session.sequence <= detail.cursor) && draft.requiredSequence <= (detail?.cursor ?? 0);
372
+ const followUp = !!session && synced && session.provider === 'claude' && session.status === 'streaming'
373
+ && !!session.turnId && session.archivedAt === null;
374
+ // The turn's one follow-up is already in the transcript.
375
+ const followUpSent = followUp && !!session && (detail?.items ?? []).some(item => item.kind === 'user_message'
376
+ && item.itemId.startsWith(`${session.turnId}:steer:`));
377
+ const unconfirmed = draft.steer?.state !== 'unknown' ? null : draft.steer.unrecorded ? FOLLOW_UP_UNRECORDED : FOLLOW_UP_UNCONFIRMED;
300
378
  return {
301
379
  catalog: this.catalog, sessions: this.rows.map(id => {
302
380
  const row = this.info(id), detail = this.details.get(id);
303
381
  return row ? publish(row, detail) : row;
304
382
  }).filter((row): row is CodeSessionInfo => !!row),
305
383
  selectedId: id, session, items: withPendingUserItem(draft, detail?.items ?? []),
384
+ hasUnsentDraft: !!this.book.fresh.input.trim() || this.book.fresh.createUnknown || !!this.book.fresh.retry,
306
385
  permissions: detail?.permissions ?? [],
307
386
  input: draft.input, selection: session ? sessionSelection(session) : draft.selection,
308
387
  gitInfo: this.gitInfo, loading: this.indexLoading || !!detail?.hydrating || (!!id && !detail?.hydrated && !detail?.error),
309
- pending, busy: codeSessionBusy(session), synced, transport: this.transport,
310
- error: [operation.error ?? detail?.error ?? this.indexError ?? this.catalogError ?? this.gitError ?? session?.error?.message, persistenceWarning].filter(Boolean).join(' ') || null,
388
+ pending, busy: codeSessionBusy(session), working: !!draft.awaitingTurn || codeSessionBusy(session),
389
+ workingIds: new Set(this.rows.filter(rowId => {
390
+ const row = this.info(rowId);
391
+ return !!this.book.sessions.get(rowId)?.awaitingTurn || (!!row && codeSessionBusy(row));
392
+ })), followUp, followUpSent, steering: draft.steer?.state === 'sending', synced, transport: this.transport, workspacePicking: this.workspacePicking,
393
+ error: [operation.error ?? unconfirmed ?? detail?.error ?? this.indexError ?? this.catalogError ?? this.gitError ?? session?.error?.message, persistenceWarning].filter(Boolean).join(' ') || null,
311
394
  operation: { ...operation, error: operation.error && persistenceWarning ? `${operation.error} ${persistenceWarning}` : operation.error }, retryText: draft.retry?.text ?? null,
312
395
  canRetrySameSend: !!id && operation.kind === 'unknown-send' && !!draft.retry && synced && session?.archivedAt === null,
313
- resendRequired: !!draft.retry?.resend,
396
+ resendRequired: !!draft.retry?.resend, resendReason: draft.retry?.resend ? draft.retry.resendReason ?? null : null,
314
397
  permissionOperations: { ...draft.permissionOperations }, hasMoreSessions: this.moreSessions,
315
398
  hasOlderHistory: detail?.hasOlder ?? false, filter: this.filter,
316
399
  creationUnknown: id === null && draft.createUnknown, startAnotherSession: this.startAnotherSession,
317
400
  newSession: this.newSession, selectSession: this.selectSession, setInput: this.setInput,
318
401
  setSelection: this.setSelection, pickWorkspace: this.pickWorkspace, send: this.send,
319
- retrySameSend: this.retrySameSend, stop: this.stop, resume: this.resume, rename: this.rename,
402
+ retrySameSend: this.retrySameSend, stop: this.stop, resume: this.resume, rollbackSession: this.rollbackSession, rename: this.rename,
320
403
  archive: this.archive, answer: this.answer, refresh: this.refresh, loadMoreSessions: this.loadMoreSessions,
321
404
  loadOlderHistory: this.loadOlderHistory, setFilter: this.setFilter, clearError: this.clearError,
322
405
  };
@@ -445,6 +528,10 @@ export class CodeController {
445
528
  if (event.session?.sessionId === event.sessionId) this.accept(event.session);
446
529
  if (event.event === 'code_session' || event.item?.kind === 'permission_request' || event.update?.status) this.scheduleIndex();
447
530
  const draft = this.book.sessions.get(event.sessionId);
531
+ if (event.item?.kind === 'user_message' && event.item.clientTurnKey && draft?.steer?.key === event.item.clientTurnKey) {
532
+ draft.requiredSequence = Math.max(draft.requiredSequence, event.sequence);
533
+ acknowledgeCodeSteer(draft, event.item.clientTurnKey);
534
+ }
448
535
  if (event.item?.kind === 'user_message' && event.item.clientTurnKey && draft?.retry?.key === event.item.clientTurnKey) {
449
536
  draft.requiredSequence = Math.max(draft.requiredSequence, event.sequence);
450
537
  if (deadSend(this.details.get(event.sessionId), event.item.clientTurnKey)) this.requireNewKey(draft);
@@ -492,12 +579,21 @@ export class CodeController {
492
579
  this.gitInfo = null;
493
580
  this.notify();
494
581
  await this.sync(id, true);
582
+ if (this.book.selectedId === id) void this.markVisited(id);
495
583
  if (this.book.selectedId === id) await this.readGit();
496
584
  };
585
+ /** Read receipt for the unread marker. A failure only leaves the marker on; selection never waits for it. */
586
+ private async markVisited(id: string): Promise<void> {
587
+ try {
588
+ this.accept(await this.client.visitSession(id));
589
+ this.notify();
590
+ } catch { /* the next open retries */ }
591
+ }
497
592
  setInput = (text: string): void => { const draft = this.draft(); draft.input = text; draft.edit++; this.notify(); };
498
593
  clearError = (): void => {
499
594
  const draft = this.draft();
500
595
  if (draft.operation.kind === 'idle') draft.operation = { kind: 'idle', error: null };
596
+ if (draft.steer?.state === 'unknown') draft.steer = null;
501
597
  this.indexError = null;
502
598
  this.book.recoveryWarning = null;
503
599
  this.notify();
@@ -521,8 +617,10 @@ export class CodeController {
521
617
  }
522
618
  pickWorkspace = async (): Promise<void> => {
523
619
  const draft = this.book.fresh;
524
- if (this.book.selectedId !== null || draft.operation.kind !== 'idle' || draft.createUnknown) return;
620
+ if (this.book.selectedId !== null || draft.operation.kind !== 'idle' || draft.createUnknown || this.workspacePicking) return;
525
621
  const navigation = this.book.navigation, selectionEdit = draft.selectionEdit, generation = ++this.pickerGeneration;
622
+ this.workspacePicking = true;
623
+ this.notify();
526
624
  try {
527
625
  const result = await this.client.pickWorkspace();
528
626
  if (!this.active || generation !== this.pickerGeneration || navigation !== this.book.navigation
@@ -534,6 +632,9 @@ export class CodeController {
534
632
  } catch (error) {
535
633
  if (this.active && generation === this.pickerGeneration && navigation === this.book.navigation
536
634
  && selectionEdit === draft.selectionEdit && draft === this.book.fresh && draft.operation.kind === 'idle') { draft.operation.error = message(error); this.notify(); }
635
+ } finally {
636
+ this.workspacePicking = false;
637
+ this.notify();
537
638
  }
538
639
  };
539
640
  setSelection = async (patch: Partial<CodeCreateSessionRequest>): Promise<void> => {
@@ -557,7 +658,8 @@ export class CodeController {
557
658
  }
558
659
  await this.patch(id, { model: patch.model ?? session.model,
559
660
  effort: patch.effort === undefined ? session.effort : patch.effort,
560
- permissionMode: patch.permissionMode ?? session.permissionMode });
661
+ permissionMode: patch.permissionMode ?? session.permissionMode,
662
+ ...(session.provider === 'claude' ? { thinking: patch.thinking ?? session.thinking ?? true } : {}) });
561
663
  return;
562
664
  }
563
665
  if (id) return;
@@ -605,8 +707,24 @@ export class CodeController {
605
707
  send = async (): Promise<void> => {
606
708
  let id = this.book.selectedId;
607
709
  let draft = this.draft(id);
608
- if (draft.operation.kind !== 'idle' || draft.createUnknown || !draft.input.trim()) return;
609
- const session = this.info(id);
710
+ if (draft.operation.kind !== 'idle' || draft.createUnknown || !draft.input.trim() || draft.steer?.state === 'sending') return;
711
+ let session = this.info(id);
712
+ // A running Claude turn takes the text as its follow-up; an idle session starts a new turn.
713
+ if (id && session && this.model.followUp) {
714
+ if (!this.model.followUpSent) await this.steer(id, draft, session);
715
+ return;
716
+ }
717
+ if (draft.steer?.state === 'unknown') draft.steer = null;
718
+ // t3code continues a session on the next send; a suspended (or recoverably
719
+ // failed) session attaches first instead of asking for a separate Resume.
720
+ if (id && session && codeCanResume(session) && this.model.synced) {
721
+ draft.operation = { kind: 'resuming', error: null }; this.notify();
722
+ try { this.accept(await this.client.attachSession(id)); draft.operation = { kind: 'idle', error: null }; }
723
+ catch (error) { draft.operation = { kind: 'idle', error: message(error) }; this.notify(); this.scheduleIndex(); return; }
724
+ this.notify(); this.scheduleIndex();
725
+ if (this.active) await this.sync(id, true);
726
+ session = this.info(id);
727
+ }
610
728
  if (id && (!session || session.status !== 'idle' || session.archivedAt !== null || !this.model.synced)) {
611
729
  draft.operation.error = 'Wait for the session to be idle and synchronized before sending.'; this.notify(); return;
612
730
  }
@@ -648,7 +766,9 @@ export class CodeController {
648
766
  private async submit(id: string, draft: CodeDraft): Promise<void> {
649
767
  const attempt = draft.retry;
650
768
  if (!attempt) return;
651
- draft.operation = { kind: 'sending', error: null }; this.notify();
769
+ draft.operation = { kind: 'sending', error: null };
770
+ draft.awaitingTurn = attempt.key;
771
+ this.notify();
652
772
  try {
653
773
  const receipt = await this.client.sendPrompt(id, { text: attempt.text, clientTurnKey: attempt.key });
654
774
  if (receipt.clientTurnKey !== attempt.key) throw new Error('Mismatched prompt receipt');
@@ -657,14 +777,51 @@ export class CodeController {
657
777
  if (state) this.details.set(id, reduceCodeSession(state, { type: 'stale' }));
658
778
  // A duplicate receipt for a spent key is a report about a turn that is
659
779
  // already over, not an admission of this one.
660
- if (spent(receipt.status)) this.requireNewKey(draft);
780
+ if (spent(receipt.status)) {
781
+ this.requireNewKey(draft, this.removedByRollback(id, receipt) ? 'rolled-back' : undefined);
782
+ draft.awaitingTurn = null;
783
+ }
661
784
  else acknowledgeCodeSend(draft, attempt.key);
662
785
  } catch (error) {
663
786
  // The committed user event may already have acknowledged a lost HTTP response.
664
787
  if (draft.retry?.key === attempt.key) {
665
788
  draft.operation = { kind: rejected(error) ? 'idle' : 'unknown-send', error: rejected(error) ? message(error)
666
789
  : 'Message acceptance is unknown. Refresh, or explicitly retry the original message with the same key.' };
667
- if (rejected(error)) draft.retry = null;
790
+ if (rejected(error)) { draft.retry = null; draft.awaitingTurn = null; }
791
+ }
792
+ }
793
+ this.notify(); this.scheduleIndex();
794
+ if (this.active) await this.sync(id, true);
795
+ }
796
+ /**
797
+ * Post one follow-up for the captured turn. Only a matching receipt or the follow-up's own
798
+ * `user_message` clears the input; a refusal keeps it; an unconfirmed outcome is never resent.
799
+ * The send's operation is left alone, so a Stop in progress keeps its state.
800
+ */
801
+ private async steer(id: string, draft: CodeDraft, session: CodeSessionInfo): Promise<void> {
802
+ if (!session.turnId || (draft.stopTarget && draft.stopTarget.outcome !== 'retryable')) return;
803
+ const attempt: NonNullable<CodeDraft['steer']> = { key: crypto.randomUUID(), text: draft.input, edit: draft.edit,
804
+ turnId: session.turnId, epoch: session.epoch, state: 'sending' };
805
+ draft.steer = attempt;
806
+ if (draft.operation.error) draft.operation = { ...draft.operation, error: null };
807
+ this.notify();
808
+ try {
809
+ const receipt = await this.client.steerSession(id, { text: attempt.text, clientTurnKey: attempt.key,
810
+ turnId: attempt.turnId, epoch: attempt.epoch });
811
+ if (receipt.clientTurnKey !== attempt.key) throw new Error('Mismatched follow-up receipt');
812
+ draft.requiredSequence = Math.max(draft.requiredSequence, receipt.sequence);
813
+ const state = this.details.get(id);
814
+ if (state) this.details.set(id, reduceCodeSession(state, { type: 'stale' }));
815
+ acknowledgeCodeSteer(draft, attempt.key);
816
+ } catch (error) {
817
+ if (draft.steer === attempt) {
818
+ if (!rejected(error)) {
819
+ attempt.state = 'unknown';
820
+ attempt.unrecorded = error instanceof CodeClientError && error.code === 'steer_outcome_unknown';
821
+ } else {
822
+ draft.steer = null;
823
+ if (draft.operation.kind === 'idle') draft.operation = { kind: 'idle', error: message(error) };
824
+ }
668
825
  }
669
826
  }
670
827
  this.notify(); this.scheduleIndex();
@@ -729,6 +886,21 @@ export class CodeController {
729
886
  this.notify(); this.scheduleIndex();
730
887
  if (this.active) await this.sync(id, true);
731
888
  };
889
+ rollbackSession = async (itemId: string): Promise<void> => {
890
+ const id = this.book.selectedId, session = this.info(id), draft = this.draft(id);
891
+ // A follow-up still in flight settles first: its answer may name the turn this would remove.
892
+ if (!id || !session || !codeCanRollback(session, this.model.synced, draft.operation.kind === 'idle' && draft.steer?.state !== 'sending')) return;
893
+ draft.operation = { kind: 'rolling-back', error: null }; this.notify();
894
+ try {
895
+ this.accept(await this.client.rollbackSession(id, { expectedRevision: session.revision, expectedEpoch: session.epoch, upToItemId: itemId }));
896
+ draft.operation = { kind: 'idle', error: null };
897
+ } catch (error) {
898
+ if (error instanceof CodeClientError && error.session?.sessionId === id) { this.observe(error.session); this.accept(error.session); }
899
+ draft.operation = { kind: 'idle', error: error instanceof CodeClientError && error.code === 'revision_conflict' ? ROLLBACK_CONFLICT : message(error) };
900
+ }
901
+ this.notify(); this.scheduleIndex();
902
+ if (this.active) await this.sync(id, true);
903
+ };
732
904
  answer = async (permission: CodePermissionRequest, optionId: string): Promise<void> => {
733
905
  const id = permission.sessionId;
734
906
  const state = this.details.get(id), session = this.info(id), draft = this.draft(id);
@@ -2,10 +2,11 @@ import type {
2
2
  CodeCreateSessionRequest, CodeItem, CodeModelCatalog, CodePermissionRequest, CodeSessionInfo,
3
3
  } from '../../../../src/code-mode/wire';
4
4
  import type { CodeGitInfo } from './code-session-client';
5
+ import type { CodeResendReason } from './code-types';
5
6
 
6
7
  export type CodeTransportState = 'connected' | 'reconnecting' | 'disconnected';
7
8
  export type CodeSessionFilter = { scope: 'all' | 'cwd'; archived: boolean };
8
- export type CodeOperationKind = 'idle' | 'creating' | 'sending' | 'stopping' | 'resuming' | 'patching' | 'unknown-send';
9
+ export type CodeOperationKind = 'idle' | 'creating' | 'sending' | 'stopping' | 'resuming' | 'patching' | 'rolling-back' | 'unknown-send';
9
10
  export interface CodeControllerOptions { port: number; workingDir: string }
10
11
 
11
12
  export interface CodeControllerModel {
@@ -16,11 +17,24 @@ export interface CodeControllerModel {
16
17
  items: CodeItem[];
17
18
  permissions: CodePermissionRequest[];
18
19
  input: string;
20
+ /** The fresh (un-sent) draft holds content or an unreconciled attempt. */
21
+ hasUnsentDraft: boolean;
19
22
  selection: CodeCreateSessionRequest;
20
23
  gitInfo: CodeGitInfo | null;
21
24
  loading: boolean;
22
25
  pending: boolean;
23
26
  busy: boolean;
27
+ /** The selected session is working: a send awaiting its turn, or a busy runtime. */
28
+ working: boolean;
29
+ /** Rows that are working, by session id. */
30
+ workingIds: ReadonlySet<string>;
31
+ /** The selected, synchronized Claude session is streaming a turn that takes a follow-up. */
32
+ followUp: boolean;
33
+ /** That turn's one follow-up is already in the transcript, so Send follow-up stays closed. */
34
+ followUpSent: boolean;
35
+ /** A follow-up for the selected session is in flight. */
36
+ steering: boolean;
37
+ workspacePicking: boolean;
24
38
  synced: boolean;
25
39
  error: string | null;
26
40
  transport: CodeTransportState;
@@ -29,6 +43,8 @@ export interface CodeControllerModel {
29
43
  canRetrySameSend: boolean;
30
44
  /** The previous key is spent: Retry sends a new message rather than reusing it. */
31
45
  resendRequired?: boolean;
46
+ /** Why the key was spent, when a rollback removed its turn rather than the server settling it. */
47
+ resendReason?: CodeResendReason | null;
32
48
  permissionOperations: Record<string, { pending: boolean; error: string | null }>;
33
49
  hasMoreSessions: boolean;
34
50
  hasOlderHistory: boolean;
@@ -44,6 +60,8 @@ export interface CodeControllerModel {
44
60
  retrySameSend(): Promise<void>;
45
61
  stop(): Promise<void>;
46
62
  resume(): Promise<void>;
63
+ /** Claude only: remove the turns after this `${turnId}:user` row. Files are not reverted and nothing is sent. */
64
+ rollbackSession(itemId: string): Promise<void>;
47
65
  rename(id: string, title: string): Promise<void>;
48
66
  archive(id: string, archived: boolean): Promise<void>;
49
67
  answer(permission: CodePermissionRequest, optionId: string): Promise<void>;
@@ -0,0 +1,25 @@
1
+ import type { CodeSessionInfo } from '../../../../src/code-mode/wire';
2
+
3
+ /**
4
+ * Which workspaces the draft empty state offers, kept apart from rendering so
5
+ * the rule can be tested on its own.
6
+ *
7
+ * "Recent" is last use, not creation: a session created months ago and used
8
+ * this morning is a more useful suggestion than one created yesterday and
9
+ * abandoned. Uniqueness is on the directory — the workspace, not the session —
10
+ * so the list never offers the same folder twice.
11
+ */
12
+ export function recentCodeWorkspaces(sessions: readonly CodeSessionInfo[], max = 5): string[] {
13
+ const seen = new Set<string>();
14
+ const out: string[] = [];
15
+ const ordered = [...sessions].sort((left, right) => right.lastUsedAt - left.lastUsedAt
16
+ || right.createdAt - left.createdAt || left.sessionId.localeCompare(right.sessionId));
17
+ for (const session of ordered) {
18
+ const cwd = session.cwd;
19
+ if (!cwd.trim() || seen.has(cwd)) continue;
20
+ seen.add(cwd);
21
+ out.push(cwd);
22
+ if (out.length >= max) break;
23
+ }
24
+ return out;
25
+ }
@@ -1,7 +1,7 @@
1
1
  import type {
2
2
  CodeCancelRequest, CodeCreateSessionRequest, CodeEventsPage, CodeHistoryPage, CodeModelCatalog,
3
3
  CodePatchSessionRequest, CodePermissionAnswer, CodePromptReceipt, CodePromptRequest,
4
- CodeSessionInfo, CodeSessionPage, CodeSnapshot,
4
+ CodeRollbackRequest, CodeSessionInfo, CodeSessionPage, CodeSnapshot, CodeSteerRequest,
5
5
  } from '../../../../src/code-mode/wire';
6
6
 
7
7
  export interface CodeGitInfo {
@@ -40,8 +40,12 @@ export interface CodeSessionClient {
40
40
  createSession(input: CodeCreateSessionRequest): Promise<CodeSessionInfo>;
41
41
  patchSession(id: string, input: CodePatchSessionRequest): Promise<CodeSessionInfo>;
42
42
  sendPrompt(id: string, input: CodePromptRequest): Promise<CodePromptReceipt>;
43
+ /** A Claude follow-up for the captured running turn; never retried. */
44
+ steerSession(id: string, input: CodeSteerRequest): Promise<CodePromptReceipt>;
43
45
  cancelPrompt(id: string, input: CodeCancelRequest): Promise<CodeSessionInfo>;
44
46
  attachSession(id: string): Promise<CodeSessionInfo>;
47
+ rollbackSession(id: string, input: CodeRollbackRequest): Promise<CodeSessionInfo>;
48
+ visitSession(id: string): Promise<CodeSessionInfo>;
45
49
  answerPermission(id: string, input: CodePermissionAnswer): Promise<void>;
46
50
  getGitInfo(cwd: string, signal?: AbortSignal): Promise<CodeGitInfo>;
47
51
  pickWorkspace(): Promise<{ ok: boolean; path?: string; cancelled?: boolean }>;
@@ -61,7 +65,18 @@ const ERROR_COPY: Record<string, string> = {
61
65
  unsupported_effort: 'This effort is not supported by the selected runtime.',
62
66
  unsupported_policy: 'This permission mode is not supported by the selected runtime.',
63
67
  unsupported_capability: 'This runtime does not support this action.',
68
+ session_not_steerable: 'This turn cannot take a follow-up right now. Your text is kept.',
69
+ steer_queue_full: 'This turn already has its follow-up. Send this after the turn finishes.',
70
+ steer_in_flight: 'This follow-up is still being delivered.',
71
+ steer_key_spent: 'This follow-up was not delivered. Your text is kept; send it again when ready.',
72
+ steer_outcome_unknown: 'Follow-up delivery not confirmed. It was not resent.',
73
+ steer_command_unsupported: 'A slash command cannot be sent as a follow-up. Send it after this turn finishes.',
64
74
  session_not_found: 'This session is no longer available. Its local draft is retained.',
75
+ rollback_unavailable: 'Rollback is unavailable after this conversation was compacted, or while its history cannot be read.',
76
+ rollback_boundary_unavailable: "This turn can't be a rollback point: it or a later turn never reached Claude, or the conversation was compacted.",
77
+ rollback_noop: 'This is already the latest turn, so there is nothing to roll back.',
78
+ rollback_target_not_found: 'This message is no longer in the conversation. Refreshing the session.',
79
+ cleanup_pending: 'The previous runtime is still closing. Try again in a moment.',
65
80
  unauthorized: 'Authentication is required to access this instance.',
66
81
  };
67
82
 
@@ -106,8 +121,11 @@ export function createCodeSessionClient(port: number): CodeSessionClient {
106
121
  createSession: input => sessionRequest('POST', '/sessions', input),
107
122
  patchSession: (id, input) => sessionRequest('PATCH', sessionPath(id), input),
108
123
  sendPrompt: (id, input) => request('POST', `${sessionPath(id)}/prompt`, input),
124
+ steerSession: (id, input) => request('POST', `${sessionPath(id)}/steer`, input),
109
125
  cancelPrompt: (id, input) => sessionRequest('POST', `${sessionPath(id)}/cancel`, input),
110
126
  attachSession: id => sessionRequest('POST', `${sessionPath(id)}/attach`, {}),
127
+ rollbackSession: (id, input) => sessionRequest('POST', `${sessionPath(id)}/rollback`, input),
128
+ visitSession: id => sessionRequest('POST', `${sessionPath(id)}/visit`, {}),
111
129
  async answerPermission(id, input) { await request('POST', `/permissions/${encodeURIComponent(id)}`, input); },
112
130
  getGitInfo: (cwd, signal) => request('GET', `/git-info?cwd=${encodeURIComponent(cwd)}`, undefined, signal),
113
131
  pickWorkspace: () => request('POST', '/workspace/pick'),
@@ -0,0 +1,38 @@
1
+ /* Code sidebar: Projects/Activity header, bell toggle, unread markers. */
2
+ .code-session-list-header { display: flex; align-items: center; justify-content: space-between; gap: 6px; }
3
+ .code-session-header-actions { display: inline-flex; align-items: center; gap: 2px; }
4
+ .code-session-header-btn {
5
+ position: relative; display: inline-flex; align-items: center; justify-content: center;
6
+ width: 24px; height: 24px; min-height: 24px; padding: 0; border: none; border-radius: 6px;
7
+ background: transparent; color: var(--text-dim); cursor: pointer;
8
+ }
9
+ .code-session-header-btn:hover { background: var(--bg-raised); color: var(--text-primary); }
10
+ .code-session-header-btn.active { color: var(--accent); background: var(--accent-soft); }
11
+ .code-session-header-btn:focus-visible { outline: 2px solid var(--ring); outline-offset: 1px; }
12
+ .code-session-search-row { display: flex; align-items: center; gap: 6px; margin: 0 8px 6px; }
13
+ .code-session-search-row .code-session-search { flex: 1 1 auto; width: auto; min-width: 0; margin: 0; }
14
+ .code-session-archived-btn {
15
+ flex: none; padding: 3px 8px; min-height: 24px; border: 1px solid var(--border-subtle); border-radius: 4px;
16
+ background: transparent; color: var(--text-dim); font-size: 11px; cursor: pointer;
17
+ }
18
+ .code-session-archived-btn.active { color: var(--accent); border-color: var(--accent); background: var(--accent-soft); }
19
+ .code-session-archived-btn:focus-visible { outline: 2px solid var(--ring); outline-offset: 1px; }
20
+ .code-session-bell .code-session-unread-dot { position: absolute; top: 3px; right: 3px; margin: 0; }
21
+ .code-session-unread-dot {
22
+ display: inline-block; flex: none; width: 7px; height: 7px; margin-right: 6px;
23
+ border-radius: 50%; background: var(--accent); vertical-align: 1px;
24
+ }
25
+
26
+ /* Working indicator: sidebar rows and the transcript tail, from Send until the turn ends. */
27
+ .code-session-spinner {
28
+ display: inline-block; flex: none; width: 9px; height: 9px; margin-right: 4px; vertical-align: -1px;
29
+ border: 1.5px solid currentColor; border-right-color: transparent; border-radius: 50%;
30
+ }
31
+ @media (prefers-reduced-motion: no-preference) {
32
+ .code-session-spinner { animation: code-session-spin 0.8s linear infinite; }
33
+ }
34
+ @keyframes code-session-spin { to { transform: rotate(360deg); } }
35
+ .code-transcript-working {
36
+ display: flex; align-items: center; gap: 6px; padding: 8px 12px 12px;
37
+ color: var(--text-dim); font-size: 12px;
38
+ }
@@ -61,6 +61,8 @@ function apply(state: CodeSessionState, event: CodeWireEvent): CodeSessionState
61
61
  let { items, session, permissions } = state;
62
62
  if (event.event === 'code_session') {
63
63
  if (!event.session || event.session.sessionId !== state.sessionId) return { ...state, needsSnapshot: true, synced: false };
64
+ // A rollback removed items this state still holds; only a new snapshot drops them.
65
+ if ((event.session.historyGeneration ?? 0) > (state.session?.historyGeneration ?? 0)) return { ...state, needsSnapshot: true, synced: false };
64
66
  // Usage is attached at read time, so a stored session event does not
65
67
  // carry it. Replacing the record wholesale would blank the meter on
66
68
  // every unrelated session change; the last figure stands until a
@@ -5,11 +5,16 @@ export const CODE_RUNTIME_LABELS: Record<CodeProviderId, string> = {
5
5
  };
6
6
  export const CODE_POLICY_LABELS: Record<CodePermissionMode, string> = {
7
7
  ask: 'Ask first', auto: 'Auto (YOLO)', 'read-only': 'Read only',
8
+ plan: 'Plan', 'accept-edits': 'Accept edits', 'dont-ask': "Don't ask", 'auto-review': 'Auto review',
8
9
  };
9
10
  export const CODE_POLICY_DETAILS: Record<CodePermissionMode, string> = {
10
11
  ask: 'Review native permission requests before allowing actions.',
11
12
  auto: 'The native runtime may execute actions without asking for approval.',
12
13
  'read-only': 'The native runtime restricts actions to read-only access.',
14
+ plan: 'Claude plans without applying edits.',
15
+ 'accept-edits': 'File edits proceed; other actions still ask.',
16
+ 'dont-ask': 'Actions not already allowed for this session are denied without asking.',
17
+ 'auto-review': 'A classifier approves or denies each action; unsure calls still ask.',
13
18
  };
14
19
  export const CODE_SESSION_LABELS: Record<CodeSessionStatus, string> = {
15
20
  idle: 'Ready', starting: 'Starting', streaming: 'Running', stopping: 'Stopping', suspended: 'Suspended', failed: 'Runtime failed',
@@ -17,10 +22,52 @@ export const CODE_SESSION_LABELS: Record<CodeSessionStatus, string> = {
17
22
  export function codeSessionBusy(session: CodeSessionInfo): boolean {
18
23
  return session.status === 'starting' || session.status === 'streaming' || session.status === 'stopping';
19
24
  }
25
+ /** Why a send's key was retired: the server spent it without running, or a rollback removed its turn. */
26
+ export type CodeResendReason = 'rolled-back';
27
+ /** What the composer says about a retired send: `error` in the alert, `heading`/`detail` in its recovery strip. */
28
+ export function codeResendCopy(reason: CodeResendReason | undefined): { heading: string; detail: string; error: string } {
29
+ return reason === 'rolled-back' ? {
30
+ heading: 'Turn rolled back',
31
+ detail: "This message's turn was removed by a rollback. Files it changed were not reverted. Retry sends it as a new message.",
32
+ error: "This message's turn was removed by a rollback. The message was not resent; Retry will submit it as a new message.",
33
+ } : {
34
+ heading: 'Send did not start',
35
+ detail: 'The original attempt ended on the server without running. Retry sends this as a new message.',
36
+ error: 'The original attempt ended on the server without running. The message was not resent; Retry will submit it as a new message.',
37
+ };
38
+ }
20
39
  export function codeCanResume(session: CodeSessionInfo): boolean {
21
40
  return session.archivedAt === null && session.capabilities.resume && session.resume.available
22
41
  && (session.status === 'suspended' || (session.status === 'failed' && session.error?.recoverable === true));
23
42
  }
43
+ /**
44
+ * Whether the open session may roll its conversation back right now. An older server
45
+ * without the `rollback` field reads as unavailable.
46
+ */
47
+ export function codeCanRollback(session: CodeSessionInfo | null, synced: boolean, idleOperation: boolean): boolean {
48
+ return !!session && session.rollback?.available === true && session.archivedAt === null && synced && idleOperation
49
+ && (session.status === 'idle' || session.status === 'failed');
50
+ }
51
+
52
+ /**
53
+ * User rows a rollback may target: a settled turn's own `${turnId}:user` row (never the
54
+ * unsent row or a follow-up), at or after the first recorded boundary, with a later turn.
55
+ */
56
+ export function codeRollbackRows(items: readonly CodeItem[], sinceSequence: number | null | undefined): ReadonlySet<string> {
57
+ const rows = new Set<string>();
58
+ if (sinceSequence == null) return rows;
59
+ const settled = new Set(items.filter(item => item.kind === 'turn_completed' || item.kind === 'turn_failed'
60
+ || item.kind === 'turn_cancelled').map(item => item.turnId));
61
+ const turns = items.filter(item => item.kind === 'user_message' && item.turnId !== null
62
+ && item.itemId === `${item.turnId}:user` && item.firstSequence !== undefined);
63
+ // One row per turn, so a later turn exists exactly when the row is not the newest one.
64
+ const newest = Math.max(...turns.map(row => row.firstSequence!));
65
+ for (const row of turns) {
66
+ if (row.firstSequence! >= sinceSequence && row.firstSequence! < newest && settled.has(row.turnId)) rows.add(row.itemId);
67
+ }
68
+ return rows;
69
+ }
70
+
24
71
  export function codeItemStatus(item: CodeItem): string {
25
72
  if (item.kind === 'turn_cancelled' || item.status === 'cancelled') return 'Stopped';
26
73
  if (item.kind === 'turn_failed' || item.status === 'error') return 'Failed';