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
@@ -3,7 +3,7 @@ import { getDesktop, isElectron } from '../panels/desktop-bridge';
3
3
  import type { BrowserPickedElement, BrowserWebviewCommand, BrowserWebviewNativeAction, BrowserWebviewScreenshot, BrowserWebviewTabState } from '../panels/desktop-bridge';
4
4
  import { DEFAULT_BROWSER_URL, isRestrictedBrowserHost, normalizeBrowserTarget } from './browser-url';
5
5
  import { BrowserAddressBar } from './browser-address-bar';
6
- import { createAddressBarState, displayedAddress, reduceAddressBar, type AddressBarAction, type AddressBarState } from './browser-address-state';
6
+ import { createAddressBarState, displayedAddress, reduceAddressBar, shouldShowRecentVisits, type AddressBarAction, type AddressBarState } from './browser-address-state';
7
7
  import { pickFaviconUrl, faviconInitial } from './browser-favicon';
8
8
  import { loadBrowserHistory, saveBrowserHistory, upsertBrowserHistory, type BrowserHistoryEntry } from './browser-history-store';
9
9
  import './browser-panel.css';
@@ -308,7 +308,6 @@ export function BrowserPanel(props: BrowserPanelProps = {}) {
308
308
  const inputRef = useRef<HTMLInputElement | null>(null);
309
309
  const [addressState, setAddressState] = useState<AddressBarState>(() => createAddressBarState(initialTab.current.url));
310
310
  const [historyEntries, setHistoryEntries] = useState<BrowserHistoryEntry[]>(() => loadBrowserHistory());
311
- const committedByUserRef = useRef<Set<string>>(new Set());
312
311
  const pendingNavigationRefs = useRef<Map<string, string>>(new Map());
313
312
  /**
314
313
  * The webview `src` attribute is bound ONCE per page tab and never
@@ -384,10 +383,17 @@ export function BrowserPanel(props: BrowserPanelProps = {}) {
384
383
  const activeRegistrationId = registrationIdFor(activeTab.id);
385
384
  const activeBridgeState = bridgeStates[activeRegistrationId] ?? null;
386
385
  const nativeInspecting = activeBridgeState?.inspecting === true;
386
+ // Page coordinates from the guest (CDP box models) are CSS px; the panel
387
+ // overlays live in DIP, so bounds picked under a non-1 zoomFactor must be
388
+ // scaled before they are drawn.
389
+ const zoomFactorRef = useRef(1);
390
+ zoomFactorRef.current = activeBridgeState?.zoomFactor ?? activeTab.zoomFactor ?? 1;
387
391
 
388
392
  const panelInstanceId = useRef<symbol>(Symbol('browser-panel'));
389
393
  /** regId -> guest webContentsId for this panel's live registrations. */
390
394
  const registeredWebContentsIds = useRef<Map<string, number>>(new Map());
395
+ /** Stable viewport box the active webview fills (survives tab switches). */
396
+ const webviewStackRef = useRef<HTMLDivElement | null>(null);
391
397
 
392
398
  const registerWebviewTarget = useCallback((tabId: string, webview: ElectronWebviewElement) => {
393
399
  const browserBridge = getDesktop()?.browser;
@@ -439,6 +445,39 @@ export function BrowserPanel(props: BrowserPanelProps = {}) {
439
445
  });
440
446
  }, [updateTab]);
441
447
 
448
+ // Fit-to-width: ask the guest to re-fit its zoom whenever the panel box
449
+ // resizes (drawer drag, sidebar toggle, window resize). The initial
450
+ // observe() fire also covers first layout; the guest no-ops while the
451
+ // user holds a manual zoom from the zoom menu.
452
+ useEffect(() => {
453
+ const browserBridge = getDesktop()?.browser;
454
+ const host = webviewStackRef.current;
455
+ if (!canUseElectronWebview || !host || typeof browserBridge?.controlWebview !== 'function') return undefined;
456
+ const controlWebview = browserBridge.controlWebview;
457
+ let timer = 0;
458
+ const requestFit = () => {
459
+ window.clearTimeout(timer);
460
+ timer = window.setTimeout(() => {
461
+ void controlWebview({ kind: 'fitToWidth', tabId: registrationIdFor(activeTabIdRef.current) });
462
+ }, 200);
463
+ };
464
+ const observer = new ResizeObserver(requestFit);
465
+ observer.observe(host);
466
+ return () => {
467
+ window.clearTimeout(timer);
468
+ observer.disconnect();
469
+ };
470
+ }, [canUseElectronWebview, registrationIdFor]);
471
+
472
+ // A hidden tab keeps whatever zoom it had when the panel resized; the
473
+ // observer above only covers the active tab and does not refire for an
474
+ // unchanged box, so re-fit once when a tab comes back to the front.
475
+ useEffect(() => {
476
+ const browserBridge = getDesktop()?.browser;
477
+ if (!canUseElectronWebview || !activeRegistrationId || typeof browserBridge?.controlWebview !== 'function') return;
478
+ void browserBridge.controlWebview({ kind: 'fitToWidth', tabId: activeRegistrationId });
479
+ }, [activeRegistrationId, canUseElectronWebview]);
480
+
442
481
  // v5: native inspect returns the REAL element (selector/role/name/bounds).
443
482
  // When it fires for this panel's active tab, pin the element and open the
444
483
  // composer anchored to the element's box center.
@@ -449,7 +488,8 @@ export function BrowserPanel(props: BrowserPanelProps = {}) {
449
488
  if (tabId !== activeRegistrationId) return;
450
489
  setPickedElement({ ...element, pickedAt: Date.now(), pageUrl: activeTab.url, tabId: activeRegistrationId });
451
490
  if (element.bounds) {
452
- setCommentAnchor({ x: element.bounds.x + Math.round(element.bounds.width / 2), y: element.bounds.y + Math.round(element.bounds.height / 2) });
491
+ const zoom = zoomFactorRef.current;
492
+ setCommentAnchor({ x: Math.round((element.bounds.x + element.bounds.width / 2) * zoom), y: Math.round((element.bounds.y + element.bounds.height / 2) * zoom) });
453
493
  }
454
494
  setCommentMode(true);
455
495
  const label = element.name || element.text || element.selector;
@@ -705,7 +745,6 @@ export function BrowserPanel(props: BrowserPanelProps = {}) {
705
745
  pendingNavigationRefs.current.delete(id);
706
746
  initialSrcRefs.current.delete(id);
707
747
  lastKnownUrlRefs.current.delete(id);
708
- committedByUserRef.current.delete(id);
709
748
  stableWebviewRefCallbacks.current.delete(id);
710
749
  unregisterWebviewTarget(id);
711
750
  setTabs(current => {
@@ -727,16 +766,11 @@ export function BrowserPanel(props: BrowserPanelProps = {}) {
727
766
  const navigate = useCallback(() => {
728
767
  const rawTarget = addressState.focused ? addressState.draft : displayedAddress(addressState);
729
768
  dispatchAddress({ type: 'submit' });
730
- const normalized = normalizeBrowserTarget(rawTarget);
731
- if (normalized && normalized !== DEFAULT_BROWSER_URL) {
732
- committedByUserRef.current.add(activeTab.id);
733
- }
734
769
  openUrlInTab(activeTab.id, rawTarget);
735
770
  inputRef.current?.blur();
736
771
  }, [activeTab.id, addressState, dispatchAddress, openUrlInTab]);
737
772
 
738
773
  const openHistoryEntry = useCallback((url: string) => {
739
- committedByUserRef.current.add(activeTab.id);
740
774
  dispatchAddress({ type: 'submit' });
741
775
  openUrlInTab(activeTab.id, url);
742
776
  inputRef.current?.blur();
@@ -1131,7 +1165,7 @@ export function BrowserPanel(props: BrowserPanelProps = {}) {
1131
1165
  )}
1132
1166
  <div className="browser-more-zoom" role="group" aria-label="Page zoom" onClick={event => event.stopPropagation()}>
1133
1167
  <button type="button" className="browser-more-item" onClick={() => void handleZoom('zoomOut')}>Zoom out</button>
1134
- <span className="browser-more-zoom-label">{Math.round((activeTab.zoomFactor ?? 1) * 100)}%</span>
1168
+ <span className="browser-more-zoom-label">{Math.round((activeTab.zoomFactor ?? 1) * 100)}%{activeBridgeState?.zoomMode === 'auto' && (activeTab.zoomFactor ?? 1) < 1 ? ' fit' : ''}</span>
1135
1169
  <button type="button" className="browser-more-item" onClick={() => void handleZoom('zoomIn')}>Zoom in</button>
1136
1170
  <button type="button" className="browser-more-item" onClick={() => void handleZoom('zoomReset')}>Reset</button>
1137
1171
  </div>
@@ -1150,15 +1184,16 @@ export function BrowserPanel(props: BrowserPanelProps = {}) {
1150
1184
  </div>
1151
1185
  )}
1152
1186
  {canUseElectronWebview ? (
1153
- <div className="browser-webview-stack">
1187
+ <div className="browser-webview-stack" ref={webviewStackRef}>
1154
1188
  <div key={activeTab.id} className="browser-webview-host is-active">
1155
- {historyEntries.length > 0 && !activeTab.loading && ((addressState.focused && addressState.draft.trim() === '') || !committedByUserRef.current.has(activeTab.id)) && (
1189
+ {historyEntries.length > 0 && shouldShowRecentVisits(addressState) && (
1156
1190
  <div className="browser-history-empty" aria-label="Recent visits">
1157
1191
  {historyEntries.map(entry => (
1158
1192
  <button
1159
1193
  key={`${entry.url}:${entry.at}`}
1160
1194
  type="button"
1161
1195
  className="browser-history-item"
1196
+ onMouseDown={event => event.preventDefault()}
1162
1197
  onClick={() => openHistoryEntry(entry.url)}
1163
1198
  >
1164
1199
  <span className="browser-history-title">{entry.title || entry.url}</span>
@@ -20,6 +20,17 @@ export function displayedAddress(state: AddressBarState): string {
20
20
  return state.focused ? state.draft : state.liveUrl;
21
21
  }
22
22
 
23
+ /**
24
+ * Recents overlay gate. The overlay covers the page, so it belongs to the
25
+ * address bar only while that bar holds focus: focus seeds the draft with the
26
+ * live URL, and an empty draft means the user cleared the field to search.
27
+ * Anything else typed is a query the user is composing, and an unfocused bar
28
+ * always leaves the page visible.
29
+ */
30
+ export function shouldShowRecentVisits(state: AddressBarState): boolean {
31
+ return state.focused && (state.draft.trim() === '' || state.draft === state.liveUrl);
32
+ }
33
+
23
34
  export function reduceAddressBar(state: AddressBarState, action: AddressBarAction): AddressBarState {
24
35
  switch (action.type) {
25
36
  case 'sync-live':
@@ -0,0 +1,18 @@
1
+ // Client-side OS detection for keyboard behavior and labels.
2
+ //
3
+ // This reads the browser or desktop window that receives the key events, never
4
+ // the server's process.platform: a dashboard served from a Mac can be opened from
5
+ // a Windows machine, and the keyboard in front of the user is what matters.
6
+
7
+ type NavigatorWithUserAgentData = Navigator & { userAgentData?: { platform?: string } };
8
+
9
+ export function currentClientPlatform(): string {
10
+ if (typeof navigator === 'undefined') return '';
11
+ const nav = navigator as NavigatorWithUserAgentData;
12
+ return nav.userAgentData?.platform || nav.platform || nav.userAgent || '';
13
+ }
14
+
15
+ export function isMacLikePlatform(platform: string = currentClientPlatform()): boolean {
16
+ return /mac|iphone|ipad|ipod/i.test(platform);
17
+ }
18
+
@@ -9,9 +9,11 @@ type CodeCanvasProps = {
9
9
  workingDir: string;
10
10
  onWorkingDirChange?: (path: string | null) => void;
11
11
  onOpenLocalFile?: (path: string) => void;
12
+ /** Resolved `newCodeSession` chord, shown as the button's shortcut hint. */
13
+ newSessionShortcut?: string | undefined;
12
14
  };
13
15
 
14
- export function CodeCanvas({ port, workingDir, onWorkingDirChange, onOpenLocalFile }: CodeCanvasProps) {
16
+ export function CodeCanvas({ port, workingDir, onWorkingDirChange, onOpenLocalFile, newSessionShortcut }: CodeCanvasProps) {
15
17
  const controller = useCodeController({ port, workingDir });
16
18
  const [sidebarHost, setSidebarHost] = useState<HTMLElement | null>(null);
17
19
  const previousWorkspace = useRef({ port, id: controller.selectedId, cwd: controller.selection.cwd });
@@ -26,7 +28,7 @@ export function CodeCanvas({ port, workingDir, onWorkingDirChange, onOpenLocalFi
26
28
  onWorkingDirChange?.(next.cwd || null);
27
29
  }
28
30
  }, [port, controller.selectedId, controller.selection.cwd, onWorkingDirChange]);
29
- const navigator = <CodeSessionList key={port} controller={controller} />;
31
+ const navigator = <CodeSessionList key={port} controller={controller} newSessionShortcut={newSessionShortcut} />;
30
32
  const workbench = <CodeWorkbench key={port} controller={controller} endpointKey={String(port)} onOpenLocalFile={onOpenLocalFile} />;
31
33
  if (sidebarHost) return <>
32
34
  {createPortal(<div className="code-manager-session-navigator-content">{navigator}</div>, sidebarHost)}
@@ -9,7 +9,16 @@ type CodeComposerProps = {
9
9
  canStop: boolean;
10
10
  stopping: boolean;
11
11
  pending: boolean;
12
+ /** A busy Claude turn takes a follow-up: Send follow-up renders beside Stop. */
13
+ followUp?: boolean;
14
+ /** The running turn already has its one follow-up. */
15
+ followUpSent?: boolean;
16
+ /** A follow-up is in flight. */
17
+ steering?: boolean;
18
+ /** What the pending operation is doing, when it is not a send. */
19
+ pendingLabel?: string;
12
20
  readOnly: boolean;
21
+ autoFocus?: boolean;
13
22
  onInputChange: (text: string) => void;
14
23
  onSubmit: () => Promise<void>;
15
24
  onStop: () => Promise<void>;
@@ -52,9 +61,12 @@ export function CodeComposer(props: CodeComposerProps) {
52
61
  return <div className="code-composer">
53
62
  <div className="code-composer-input-shell">
54
63
  <textarea className="code-composer-input" aria-label="Code prompt" value={props.inputText}
64
+ autoFocus={props.autoFocus}
55
65
  onChange={event => props.onInputChange(event.target.value)} onKeyDown={handleKeyDown}
56
66
  onCompositionStart={() => { composing.current = true; }} onCompositionEnd={() => { composing.current = false; }}
57
- placeholder={props.busy ? 'Draft a follow-up while this turn runs…' : 'Describe a task or ask a question…'}
67
+ placeholder={props.followUpSent ? 'This turn already has its follow-up. Draft the next message…'
68
+ : props.followUp ? 'Send a follow-up while this turn runs…'
69
+ : props.busy ? 'Draft a follow-up while this turn runs…' : 'Describe a task or ask a question…'}
58
70
  rows={2} readOnly={props.readOnly} />
59
71
  <span className="code-composer-hint">Enter to send · Shift+Enter for a new line</span>
60
72
  </div>
@@ -66,18 +78,18 @@ export function CodeComposer(props: CodeComposerProps) {
66
78
  title={dictation.reason} onClick={dictation.toggle}>
67
79
  <MicGlyph muted={dictation.status === 'unsupported'} />
68
80
  </button>
69
- {props.busy
70
- ? <button type="button" className="code-composer-send code-composer-stop" aria-label="Stop current turn"
71
- title={props.stopping ? 'Stopping' : 'Stop current turn'}
72
- disabled={!props.canStop || props.stopping} onClick={() => void stop()}><StopGlyph /></button>
73
- : <button type="button" className="code-composer-send" aria-label="Send prompt"
74
- title={props.pending ? 'Sending' : 'Send prompt'}
75
- disabled={!props.canSend || !props.inputText.trim()} onClick={() => void submit()}><SendGlyph /></button>}
81
+ {props.busy && <button type="button" className="code-composer-send code-composer-stop" aria-label="Stop current turn"
82
+ title={props.stopping ? 'Stopping' : 'Stop current turn'}
83
+ disabled={!props.canStop || props.stopping} onClick={() => void stop()}><StopGlyph /></button>}
84
+ {(!props.busy || props.followUp) && <button type="button" className="code-composer-send"
85
+ aria-label={props.busy ? 'Send follow-up' : 'Send prompt'}
86
+ title={props.steering ? 'Sending follow-up' : props.pending ? 'Sending' : props.busy ? 'Send follow-up' : 'Send prompt'}
87
+ disabled={!props.canSend || !props.inputText.trim()} onClick={() => void submit()}><SendGlyph /></button>}
76
88
  </div>
77
89
  {/* An icon button cannot announce progress on its own. */}
78
- {(props.stopping || props.pending || dictation.status === 'recording' || dictation.status === 'transcribing') &&
90
+ {(props.stopping || props.steering || props.pending || dictation.status === 'recording' || dictation.status === 'transcribing') &&
79
91
  <span className="code-composer-status" role="status">
80
- {props.stopping ? 'Stopping…' : props.pending ? 'Sending…'
92
+ {props.stopping ? 'Stopping…' : props.steering ? 'Sending follow-up…' : props.pending ? props.pendingLabel ?? 'Sending…'
81
93
  : dictation.status === 'recording' ? 'Recording…' : 'Transcribing…'}</span>}
82
94
  {(error || dictation.error) && <div className="code-action-error" role="alert">{error ?? dictation.error}</div>}
83
95
  </div>;
@@ -0,0 +1,64 @@
1
+ import type { CodeControllerModel } from './code-controller-types';
2
+ import { CODE_RUNTIME_LABELS } from './code-types';
3
+ import { recentCodeWorkspaces } from './code-recent-workspaces';
4
+
5
+ /**
6
+ * What a fresh draft looks like before its first prompt. Modelled on ZCode's
7
+ * ConversationDraftEmptyState and T3 Code's NoActiveThreadState: the workspace
8
+ * is the decision that gates everything else, so it is the one prominent
9
+ * control; the suggested prompts only fill the composer, they never send.
10
+ */
11
+ const SUGGESTED_PROMPTS: readonly string[] = [
12
+ 'Explain this codebase',
13
+ 'Find and fix a bug',
14
+ 'Write tests for the module I name',
15
+ 'Review uncommitted changes',
16
+ ];
17
+
18
+ // A chip and a recent both unmount the control that was activated -- the chip
19
+ // disappears once the draft is non-empty and the chosen recent leaves the
20
+ // list -- so keyboard focus is handed to the composer instead of dropping to
21
+ // <body>.
22
+ function focusComposer(): void {
23
+ document.querySelector<HTMLTextAreaElement>('textarea[aria-label="Code prompt"]')?.focus();
24
+ }
25
+
26
+ export function CodeDraftEmptyState({ controller: c }: { controller: CodeControllerModel }) {
27
+ const cwd = c.selection.cwd;
28
+ // Same freeze rule as CodeWorkspaceHeader: once creation is in flight the
29
+ // draft belongs to it, and an unconfirmed create must not be edited around.
30
+ const frozen = c.selectedId !== null || c.pending || c.creationUnknown || c.operation.kind !== 'idle';
31
+ // Shared with the header picker so only one folder dialog can be open.
32
+ const picking = c.workspacePicking;
33
+ // Exclude the current cwd before the cap; otherwise it silently eats a slot.
34
+ const recents = recentCodeWorkspaces(c.sessions.filter(session => session.cwd !== cwd));
35
+ function pick() {
36
+ if (frozen || picking) return;
37
+ void c.pickWorkspace();
38
+ }
39
+ function chooseWorkspace(dir: string) {
40
+ if (frozen) return;
41
+ void c.setSelection({ cwd: dir });
42
+ focusComposer();
43
+ }
44
+ return <section className="code-draft-empty" aria-label="New session draft">
45
+ <h2 className="code-draft-empty-title">New session</h2>
46
+ {frozen ? <span className="code-workspace-chip" title={cwd}>{cwd || 'Workspace not set'}</span>
47
+ : <button type="button" className="code-workspace-picker code-draft-empty-picker" aria-label="Choose workspace for the new session"
48
+ disabled={picking} title={cwd || 'Choose a folder'} onClick={pick}>
49
+ <span className="code-workspace-picker-label">{picking ? 'Choosing folder…' : cwd || 'Choose workspace'}</span>
50
+ </button>}
51
+ <p className="code-draft-empty-selection">
52
+ {CODE_RUNTIME_LABELS[c.selection.provider]} · {c.selection.model || 'No model selected'}
53
+ </p>
54
+ {recents.length > 0 && <div className="code-draft-recents">
55
+ <span className="code-draft-recents-label">Recent workspaces</span>
56
+ {recents.map(dir => <button type="button" key={dir} className="code-draft-recent" title={dir}
57
+ disabled={frozen} onClick={() => chooseWorkspace(dir)}>{dir}</button>)}
58
+ </div>}
59
+ {!c.input.trim() && <div className="code-draft-prompts">
60
+ {SUGGESTED_PROMPTS.map(text => <button type="button" key={text} className="code-draft-prompt"
61
+ disabled={frozen} onClick={() => { c.setInput(text); focusComposer(); }}>{text}</button>)}
62
+ </div>}
63
+ </section>;
64
+ }
@@ -1,10 +1,44 @@
1
- import { useId, useRef, useState } from 'react';
1
+ import { useEffect, useId, useRef, useState } from 'react';
2
2
  import type { CodeSessionInfo } from '../../../../src/code-mode/wire';
3
3
  import type { CodeControllerModel } from './code-controller-types';
4
4
  import { CODE_RUNTIME_LABELS, CODE_SESSION_LABELS, codeCanResume, codeSessionBusy } from './code-types';
5
- import { codeSessionAttention, codeSessionAttentionLabel, groupCodeSessions } from './session-order';
5
+ import {
6
+ CODE_ACTIVITY_BUCKET_LABELS, codeSessionAttention, codeSessionAttentionLabel, codeSessionUnread, codeWorkspaceName,
7
+ groupCodeSessions, groupCodeSessionsByActivity, groupCodeSessionsByWorkspace,
8
+ } from './session-order';
9
+ import { DEFAULT_MANAGER_SHORTCUT_KEYMAP, formatShortcut } from '../manager-shortcuts';
6
10
 
7
- function SessionRow({ session: s, controller: c }: { session: CodeSessionInfo; controller: CodeControllerModel }) {
11
+ const NEW_SESSION_SHORTCUT = DEFAULT_MANAGER_SHORTCUT_KEYMAP.newCodeSession;
12
+
13
+ type CodeSidebarView = 'projects' | 'activity';
14
+ const SIDEBAR_VIEW_KEY = 'jaw.code.sidebarView';
15
+
16
+ function readSidebarView(): CodeSidebarView {
17
+ try { return localStorage.getItem(SIDEBAR_VIEW_KEY) === 'activity' ? 'activity' : 'projects'; }
18
+ catch { return 'projects'; }
19
+ }
20
+
21
+ function BellGlyph() {
22
+ return <svg viewBox="0 0 16 16" width="15" height="15" aria-hidden="true" focusable="false">
23
+ <path d="M8 2.5a3.5 3.5 0 0 0-3.5 3.5v2.3L3.3 10.6h9.4L11.5 8.3V6A3.5 3.5 0 0 0 8 2.5Z M6.6 12.4a1.5 1.5 0 0 0 2.8 0"
24
+ fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinejoin="round" strokeLinecap="round" />
25
+ </svg>;
26
+ }
27
+
28
+ function SearchGlyph() {
29
+ return <svg viewBox="0 0 16 16" width="15" height="15" aria-hidden="true" focusable="false">
30
+ <circle cx="7" cy="7" r="4.25" fill="none" stroke="currentColor" strokeWidth="1.4" />
31
+ <path d="M10.2 10.2 13.5 13.5" fill="none" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" />
32
+ </svg>;
33
+ }
34
+
35
+ function PlusGlyph() {
36
+ return <svg viewBox="0 0 16 16" width="14" height="14" aria-hidden="true" focusable="false">
37
+ <path d="M8 3.5v9M3.5 8h9" fill="none" stroke="currentColor" strokeWidth="1.7" strokeLinecap="round" />
38
+ </svg>;
39
+ }
40
+
41
+ function SessionRow({ session: s, controller: c, view }: { session: CodeSessionInfo; controller: CodeControllerModel; view: CodeSidebarView }) {
8
42
  const [renaming, setRenaming] = useState(false);
9
43
  const [title, setTitle] = useState(s.title ?? '');
10
44
  const [pending, setPending] = useState(false);
@@ -16,6 +50,9 @@ function SessionRow({ session: s, controller: c }: { session: CodeSessionInfo; c
16
50
  const busy = codeSessionBusy(s);
17
51
  const count = active && c.synced ? c.permissions.length : s.pendingPermissionCount;
18
52
  const attention = codeSessionAttention(count);
53
+ const unread = !active && codeSessionUnread(s);
54
+ const working = c.workingIds?.has(s.sessionId) ?? false;
55
+ const waiting = working && s.status === 'idle';
19
56
  async function action(run: () => Promise<void>, after?: () => void) {
20
57
  if (guard.current) return;
21
58
  guard.current = true; setPending(true); setError(null);
@@ -27,13 +64,20 @@ function SessionRow({ session: s, controller: c }: { session: CodeSessionInfo; c
27
64
  return <li className="code-session-row">
28
65
  <button ref={selectButton} type="button" className={`code-session-item${active ? ' active' : ''}`}
29
66
  aria-current={active ? 'true' : undefined} onClick={() => void action(() => c.selectSession(s.sessionId))}>
30
- <span className="code-session-cwd">{s.title || 'Untitled session'}</span>
31
- <span className="code-session-meta" title={s.cwd}>{CODE_RUNTIME_LABELS[s.provider]} · {s.cwd}</span>
67
+ <span className="code-session-cwd">
68
+ {unread && <span className="code-session-unread-dot" role="img" aria-label="Unread" />}
69
+ {s.title || 'Untitled session'}
70
+ </span>
71
+ {/* Projects view already names the workspace in the group heading. */}
72
+ <span className="code-session-meta" title={s.cwd}>{view === 'activity'
73
+ ? `${codeWorkspaceName(s.cwd)} · ${CODE_RUNTIME_LABELS[s.provider]}` : CODE_RUNTIME_LABELS[s.provider]}</span>
32
74
  {/* Ready is the common case and stays silent. A status on every row
33
75
  is a status on no row, and it costs the one session that is
34
76
  actually doing something its visibility. */}
35
- <span className={`code-session-status code-session-status-${s.status}`}
36
- data-quiet={s.status === 'idle' ? 'true' : undefined}>{CODE_SESSION_LABELS[s.status]}</span>
77
+ <span className={`code-session-status code-session-status-${waiting ? 'starting' : s.status}`}
78
+ data-quiet={s.status === 'idle' && !working ? 'true' : undefined}>
79
+ {working && <span className="code-session-spinner" aria-hidden="true" />}
80
+ {waiting ? CODE_SESSION_LABELS.starting : CODE_SESSION_LABELS[s.status]}</span>
37
81
  <span className={`code-session-attention code-session-attention-${attention.kind}`}
38
82
  data-quiet={attention.kind === 'none' ? 'true' : undefined}>{codeSessionAttentionLabel(attention)}</span>
39
83
  </button>
@@ -63,26 +107,68 @@ function SessionRow({ session: s, controller: c }: { session: CodeSessionInfo; c
63
107
  </li>;
64
108
  }
65
109
 
66
- export function CodeSessionList({ controller: c }: { controller: CodeControllerModel }) {
110
+ export function CodeSessionList({ controller: c, newSessionShortcut = NEW_SESSION_SHORTCUT }: { controller: CodeControllerModel; newSessionShortcut?: string | undefined }) {
67
111
  const [search, setSearch] = useState('');
68
- const [grouped, setGrouped] = useState(false);
112
+ const [searching, setSearching] = useState(false);
113
+ const searchInput = useRef<HTMLInputElement>(null);
114
+ function toggleSearch() {
115
+ // Closing clears the query so the list is never silently filtered by a hidden box.
116
+ if (searching) { setSearching(false); setSearch(''); return; }
117
+ setSearching(true);
118
+ queueMicrotask(() => searchInput.current?.focus());
119
+ }
120
+ const draftBadgeId = useId();
121
+ const [view, setViewState] = useState<CodeSidebarView>(readSidebarView);
122
+ function setView(next: CodeSidebarView) {
123
+ setViewState(next);
124
+ try { localStorage.setItem(SIDEBAR_VIEW_KEY, next); } catch { /* storage unavailable: the choice lasts for this page */ }
125
+ }
69
126
  const [error, setError] = useState<string | null>(null);
70
127
  const [paging, setPaging] = useState(false);
71
128
  const pagingRef = useRef(false);
72
129
  const visible = c.sessions.filter(s => `${s.title ?? ''} ${s.cwd} ${CODE_RUNTIME_LABELS[s.provider]}`.toLowerCase().includes(search.toLowerCase()));
73
- // Two orderings, deliberately: by workspace when the reader asks for it,
74
- // otherwise by lifecycle. Neither uses last activity, so answering a prompt
75
- // in one session does not move it under the reader's cursor -- which is
76
- // what the server's own last-used ordering would do.
77
- const groups = grouped
78
- ? [...visible.reduce((map, s) => {
79
- const rows = map.get(s.cwd) ?? []; rows.push(s); map.set(s.cwd, rows); return map;
80
- }, new Map<string, CodeSessionInfo[]>())].map(([cwd, sessions]) => ({ title: cwd, key: cwd, sessions }))
81
- : groupCodeSessions(visible).map(group => ({
82
- key: group.section,
83
- title: group.section === 'archived' ? 'Archived' : '',
84
- sessions: group.sessions,
85
- }));
130
+ // Projects (the default) keeps rows in creation order under their workspace, so
131
+ // answering a prompt never moves a row under the reader's cursor. Activity is the
132
+ // reader's explicit choice: unread first, then last activity by day.
133
+ const anyUnread = c.sessions.some(row => row.sessionId !== c.selectedId && codeSessionUnread(row));
134
+ const live = visible.filter(row => row.archivedAt === null);
135
+ const archived = visible.filter(row => row.archivedAt !== null);
136
+ const groups: { key: string; title: string; cwd: string | null; sessions: CodeSessionInfo[] }[] = view === 'activity'
137
+ ? groupCodeSessionsByActivity(live, Date.now(), c.selectedId).map(group => ({
138
+ key: group.bucket, title: CODE_ACTIVITY_BUCKET_LABELS[group.bucket], cwd: null, sessions: group.sessions }))
139
+ : groupCodeSessionsByWorkspace(live).map(group => ({ key: group.cwd, title: group.cwd, cwd: group.cwd, sessions: group.sessions }));
140
+ if (archived.length) groups.push({ key: 'archived', title: 'Archived', cwd: null, sessions: groupCodeSessions(archived).flatMap(group => group.sessions) });
141
+ // The chord resolves through the manager shortcut system (rebindable in
142
+ // Settings); the runner re-broadcasts it here so Code mode can be absent
143
+ // without the event reaching a dead handler.
144
+ const newSession = c.newSession;
145
+ useEffect(() => {
146
+ function onShortcutAction(event: Event) {
147
+ if ((event as CustomEvent<string>).detail === 'newCodeSession') newSession();
148
+ }
149
+ document.addEventListener('jaw:shortcut-action', onShortcutAction);
150
+ return () => document.removeEventListener('jaw:shortcut-action', onShortcutAction);
151
+ }, [newSession]);
152
+ async function newInWorkspace(cwd: string) {
153
+ c.newSession();
154
+ // Silently retargeting a draft that holds unsent text or an unreconciled
155
+ // send would move that content to another workspace; setSelection also
156
+ // early-returns while the draft it is about to retarget is mid-operation.
157
+ // Say why instead, each reason in its own words.
158
+ if (c.hasUnsentDraft) {
159
+ setError('Send or clear the current draft before choosing another workspace.');
160
+ return;
161
+ }
162
+ // A busy *session* is not that case: newSession has already moved the
163
+ // reader onto a fresh draft, so the + still retargets that draft.
164
+ if (c.selectedId === null && c.pending) {
165
+ setError('Wait for the current change to finish before choosing another workspace.');
166
+ return;
167
+ }
168
+ setError(null);
169
+ try { await c.setSelection({ cwd }); }
170
+ catch (err) { setError(err instanceof Error ? err.message : String(err)); }
171
+ }
86
172
  async function loadMore() {
87
173
  if (pagingRef.current) return;
88
174
  pagingRef.current = true; setPaging(true); setError(null);
@@ -91,28 +177,56 @@ export function CodeSessionList({ controller: c }: { controller: CodeControllerM
91
177
  finally { pagingRef.current = false; setPaging(false); }
92
178
  }
93
179
  return <nav className="code-session-list" aria-label="Code sessions">
94
- <div className="code-session-list-header"><span className="code-session-list-title">Sessions</span>
95
- <button type="button" className="code-session-new-btn" onClick={c.newSession} aria-label="New Code session">+</button>
96
- </div>
97
- <div className="code-session-view-toggle" aria-label="Session view">
98
- <button type="button" aria-pressed={c.filter.scope === 'all'} className={`code-session-view-btn${c.filter.scope === 'all' ? ' active' : ''}`}
99
- onClick={() => c.setFilter({ ...c.filter, scope: 'all' })}>All</button>
100
- <button type="button" aria-pressed={c.filter.scope === 'cwd'} className={`code-session-view-btn${c.filter.scope === 'cwd' ? ' active' : ''}`}
101
- onClick={() => c.setFilter({ ...c.filter, scope: 'cwd' })}>This cwd</button>
102
- <button type="button" aria-pressed={grouped} className={`code-session-view-btn${grouped ? ' active' : ''}`} onClick={() => setGrouped(!grouped)}>Group</button>
180
+ <button type="button" className={`code-session-new-primary${c.selectedId === null ? ' active' : ''}`}
181
+ aria-current={c.selectedId === null ? 'true' : undefined}
182
+ // The name is fixed so it survives the label, badge and chord
183
+ // changes; the draft state therefore travels as a description.
184
+ aria-label="New Code session"
185
+ aria-describedby={c.hasUnsentDraft ? draftBadgeId : undefined}
186
+ title={`Start a new session (${formatShortcut(newSessionShortcut)})`} onClick={c.newSession}>
187
+ <PlusGlyph />
188
+ <span className="code-session-new-label">New session</span>
189
+ {c.hasUnsentDraft && <span id={draftBadgeId} className="code-session-draft-badge">Draft</span>}
190
+ <kbd className="code-session-new-hint" aria-hidden="true">{formatShortcut(newSessionShortcut)}</kbd>
191
+ </button>
192
+ <div className="code-session-list-header">
193
+ <span className="code-session-list-title">{view === 'activity' ? 'Recent activity' : 'Projects'}</span>
194
+ <span className="code-session-header-actions">
195
+ <button type="button" className={`code-session-header-btn code-session-search-btn${searching ? ' active' : ''}`}
196
+ aria-pressed={searching} aria-label="Search sessions" title="Search sessions" onClick={toggleSearch}>
197
+ <SearchGlyph />
198
+ </button>
199
+ <button type="button" className={`code-session-header-btn code-session-bell${view === 'activity' ? ' active' : ''}`}
200
+ aria-pressed={view === 'activity'}
201
+ aria-label={anyUnread ? 'Recent activity (unread sessions)' : 'Recent activity'}
202
+ title={view === 'activity' ? 'Back to projects' : 'Recent activity'}
203
+ onClick={() => setView(view === 'activity' ? 'projects' : 'activity')}>
204
+ <BellGlyph />
205
+ {anyUnread && <span className="code-session-unread-dot" aria-hidden="true" />}
206
+ </button>
207
+ </span>
103
208
  </div>
104
- <label className="code-session-archive-filter"><input type="checkbox" checked={c.filter.archived}
105
- onChange={event => c.setFilter({ ...c.filter, archived: event.target.checked })} />Archived</label>
106
- <input className="code-session-search" type="search" aria-label="Search loaded sessions" placeholder="Search loaded sessions…"
107
- value={search} onChange={event => setSearch(event.target.value)} />
108
- <button type="button" className={`code-session-item${c.selectedId === null ? ' active' : ''}`} aria-current={c.selectedId === null ? 'true' : undefined}
109
- onClick={c.newSession}><span className="code-session-cwd">New session draft</span><span className="code-session-meta">Preserved unsent draft</span></button>
209
+ {/* Projects already scopes by workspace, so the All / This cwd toggle is gone.
210
+ Search and the archived switch live behind the search icon. */}
211
+ {searching && <div className="code-session-search-row">
212
+ <input ref={searchInput} className="code-session-search" type="search" aria-label="Search loaded sessions"
213
+ placeholder="Search sessions…" value={search} onChange={event => setSearch(event.target.value)}
214
+ onKeyDown={event => { if (event.key === 'Escape') { event.preventDefault(); toggleSearch(); } }} />
215
+ <button type="button" className={`code-session-archived-btn${c.filter.archived ? ' active' : ''}`}
216
+ aria-pressed={c.filter.archived} title="Include archived sessions"
217
+ onClick={() => c.setFilter({ ...c.filter, archived: !c.filter.archived })}>Archived</button>
218
+ </div>}
110
219
  {c.loading && <div className="code-session-list-loading" role="status">Loading sessions…</div>}
111
220
  {groups.map(group => <section className="code-session-group" key={group.key}>
112
- {group.title && <h3 className="code-session-group-title" title={group.title}>{group.title}</h3>}
113
- <ul className="code-session-list-items">{group.sessions.map(s => <SessionRow key={s.sessionId} session={s} controller={c} />)}</ul>
221
+ {group.title && <h3 className={`code-session-group-title${group.key === 'priority' ? ' code-session-group-priority' : ''}`} title={group.title}>
222
+ <span>{group.cwd !== null ? codeWorkspaceName(group.cwd) : group.title}</span>
223
+ {group.cwd !== null && <button type="button" className="code-session-group-new"
224
+ aria-label={`New session in ${group.cwd}`} title={`New session in ${group.cwd}`}
225
+ onClick={() => void newInWorkspace(group.cwd!)}>+</button>}
226
+ </h3>}
227
+ <ul className="code-session-list-items">{group.sessions.map(s => <SessionRow key={s.sessionId} session={s} controller={c} view={view} />)}</ul>
114
228
  </section>)}
115
- {!c.loading && !visible.length && <p className="code-session-list-empty">{search ? 'No loaded sessions match. Clear search or load more.' : 'No sessions here. Start with the new session draft.'}</p>}
229
+ {!c.loading && !visible.length && <p className="code-session-list-empty">{search ? 'No loaded sessions match. Clear search or load more.' : 'No sessions here. Start a new session above.'}</p>}
116
230
  {c.hasMoreSessions && <button type="button" className="code-inline-action" disabled={paging} onClick={() => void loadMore()}>{paging ? 'Loading…' : 'Load more sessions'}</button>}
117
231
  <button type="button" className="code-inline-action" onClick={() => { setError(null); void c.refresh().catch(err => setError(err instanceof Error ? err.message : String(err))); }}>Refresh sessions</button>
118
232
  {error && <div className="code-session-list-error" role="alert">{error}</div>}