@clawpump/claw-agent 0.1.5 → 0.1.6

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 (1212) hide show
  1. package/agent/.dockerignore +67 -0
  2. package/agent/.envrc +1 -1
  3. package/agent/.gitattributes +8 -0
  4. package/agent/AGENTS.md +216 -4
  5. package/agent/CONTRIBUTING.md +46 -8
  6. package/agent/Dockerfile +78 -35
  7. package/agent/MANIFEST.in +2 -0
  8. package/agent/README.md +12 -5
  9. package/agent/README.ur-pk.md +261 -0
  10. package/agent/README.zh-CN.md +11 -8
  11. package/agent/SECURITY.md +5 -4
  12. package/agent/acp_adapter/provenance.py +127 -0
  13. package/agent/acp_adapter/server.py +112 -5
  14. package/agent/acp_adapter/session.py +1 -6
  15. package/agent/acp_registry/agent.json +2 -2
  16. package/agent/agent/account_usage.py +313 -1
  17. package/agent/agent/agent_init.py +140 -37
  18. package/agent/agent/agent_runtime_helpers.py +342 -83
  19. package/agent/agent/anthropic_adapter.py +320 -33
  20. package/agent/agent/auxiliary_client.py +525 -105
  21. package/agent/agent/background_review.py +157 -19
  22. package/agent/agent/bedrock_adapter.py +71 -6
  23. package/agent/agent/billing_view.py +295 -0
  24. package/agent/agent/chat_completion_helpers.py +229 -4
  25. package/agent/agent/codex_responses_adapter.py +86 -10
  26. package/agent/agent/codex_runtime.py +153 -1
  27. package/agent/agent/coding_context.py +738 -0
  28. package/agent/agent/context_compressor.py +392 -44
  29. package/agent/agent/context_references.py +34 -1
  30. package/agent/agent/conversation_compression.py +159 -22
  31. package/agent/agent/conversation_loop.py +643 -908
  32. package/agent/agent/copilot_acp_client.py +4 -11
  33. package/agent/agent/credential_pool.py +5 -3
  34. package/agent/agent/credits_tracker.py +794 -0
  35. package/agent/agent/curator.py +91 -18
  36. package/agent/agent/curator_backup.py +26 -10
  37. package/agent/agent/display.py +42 -1
  38. package/agent/agent/error_classifier.py +52 -3
  39. package/agent/agent/errors.py +3 -0
  40. package/agent/agent/file_safety.py +0 -17
  41. package/agent/agent/gemini_native_adapter.py +31 -1
  42. package/agent/agent/i18n.py +48 -4
  43. package/agent/agent/image_gen_provider.py +74 -5
  44. package/agent/agent/image_routing.py +29 -0
  45. package/agent/agent/insights.py +8 -17
  46. package/agent/agent/lsp/install.py +3 -0
  47. package/agent/agent/memory_manager.py +326 -31
  48. package/agent/agent/message_content.py +50 -0
  49. package/agent/agent/model_metadata.py +214 -3
  50. package/agent/agent/moonshot_schema.py +8 -1
  51. package/agent/agent/onboarding.py +60 -0
  52. package/agent/agent/prompt_builder.py +327 -37
  53. package/agent/agent/redact.py +1 -0
  54. package/agent/agent/runtime_cwd.py +34 -5
  55. package/agent/agent/secret_scope.py +205 -0
  56. package/agent/agent/secret_sources/bitwarden.py +34 -2
  57. package/agent/agent/skill_commands.py +90 -1
  58. package/agent/agent/skill_preprocessing.py +1 -0
  59. package/agent/agent/skill_utils.py +209 -36
  60. package/agent/agent/ssl_guard.py +94 -0
  61. package/agent/agent/system_prompt.py +133 -5
  62. package/agent/agent/tool_executor.py +496 -70
  63. package/agent/agent/transports/anthropic.py +83 -21
  64. package/agent/agent/transports/chat_completions.py +94 -5
  65. package/agent/agent/transports/codex.py +67 -2
  66. package/agent/agent/transports/codex_app_server.py +1 -0
  67. package/agent/agent/transports/codex_app_server_session.py +30 -0
  68. package/agent/agent/transports/types.py +12 -0
  69. package/agent/agent/turn_context.py +408 -0
  70. package/agent/agent/turn_finalizer.py +428 -0
  71. package/agent/agent/turn_retry_state.py +68 -0
  72. package/agent/agent/usage_pricing.py +3 -0
  73. package/agent/apps/bootstrap-installer/package.json +6 -5
  74. package/agent/apps/bootstrap-installer/src/routes/failure.tsx +12 -5
  75. package/agent/apps/bootstrap-installer/src/routes/progress.tsx +1 -3
  76. package/agent/apps/bootstrap-installer/src/store.ts +3 -2
  77. package/agent/apps/bootstrap-installer/src-tauri/src/bootstrap.rs +172 -7
  78. package/agent/apps/bootstrap-installer/src-tauri/src/events.rs +14 -1
  79. package/agent/apps/bootstrap-installer/src-tauri/src/paths.rs +29 -0
  80. package/agent/apps/bootstrap-installer/src-tauri/src/powershell.rs +93 -3
  81. package/agent/apps/bootstrap-installer/src-tauri/src/update.rs +695 -39
  82. package/agent/apps/bootstrap-installer/tsconfig.json +3 -4
  83. package/agent/apps/desktop/DESIGN.md +167 -0
  84. package/agent/apps/desktop/README.md +20 -16
  85. package/agent/apps/desktop/assets/icon.icns +0 -0
  86. package/agent/apps/desktop/assets/icon.ico +0 -0
  87. package/agent/apps/desktop/assets/icon.png +0 -0
  88. package/agent/apps/desktop/electron/backend-env.cjs +112 -0
  89. package/agent/apps/desktop/electron/backend-env.test.cjs +111 -0
  90. package/agent/apps/desktop/electron/backend-probes.test.cjs +3 -1
  91. package/agent/apps/desktop/electron/backend-ready.cjs +66 -0
  92. package/agent/apps/desktop/electron/bootstrap-platform.cjs +52 -0
  93. package/agent/apps/desktop/electron/bootstrap-platform.test.cjs +59 -1
  94. package/agent/apps/desktop/electron/bootstrap-runner.cjs +176 -38
  95. package/agent/apps/desktop/electron/bootstrap-runner.test.cjs +112 -1
  96. package/agent/apps/desktop/electron/connection-config.cjs +288 -0
  97. package/agent/apps/desktop/electron/connection-config.test.cjs +396 -0
  98. package/agent/apps/desktop/electron/dashboard-token.cjs +99 -0
  99. package/agent/apps/desktop/electron/dashboard-token.test.cjs +142 -0
  100. package/agent/apps/desktop/electron/desktop-uninstall.cjs +232 -0
  101. package/agent/apps/desktop/electron/desktop-uninstall.test.cjs +246 -0
  102. package/agent/apps/desktop/electron/entitlements.mac.inherit.plist +2 -0
  103. package/agent/apps/desktop/electron/fs-read-dir.cjs +109 -0
  104. package/agent/apps/desktop/electron/fs-read-dir.test.cjs +364 -0
  105. package/agent/apps/desktop/electron/gateway-ws-probe.cjs +188 -0
  106. package/agent/apps/desktop/electron/gateway-ws-probe.test.cjs +122 -0
  107. package/agent/apps/desktop/electron/git-root.cjs +54 -0
  108. package/agent/apps/desktop/electron/git-root.test.cjs +40 -0
  109. package/agent/apps/desktop/electron/git-worktrees.cjs +174 -0
  110. package/agent/apps/desktop/electron/hardening.cjs +123 -28
  111. package/agent/apps/desktop/electron/hardening.test.cjs +163 -0
  112. package/agent/apps/desktop/electron/main.cjs +3121 -331
  113. package/agent/apps/desktop/electron/oauth-net-request.cjs +20 -0
  114. package/agent/apps/desktop/electron/oauth-net-request.test.cjs +34 -0
  115. package/agent/apps/desktop/electron/preload.cjs +52 -2
  116. package/agent/apps/desktop/electron/session-windows.cjs +124 -0
  117. package/agent/apps/desktop/electron/session-windows.test.cjs +199 -0
  118. package/agent/apps/desktop/electron/update-rebuild.cjs +29 -0
  119. package/agent/apps/desktop/electron/update-rebuild.test.cjs +55 -0
  120. package/agent/apps/desktop/electron/update-remote.cjs +56 -0
  121. package/agent/apps/desktop/electron/update-remote.test.cjs +78 -0
  122. package/agent/apps/desktop/electron/vscode-marketplace.cjs +331 -0
  123. package/agent/apps/desktop/electron/vscode-marketplace.test.cjs +113 -0
  124. package/agent/apps/desktop/electron/windows-child-process.test.cjs +57 -0
  125. package/agent/apps/desktop/electron/windows-user-env.cjs +76 -0
  126. package/agent/apps/desktop/electron/windows-user-env.test.cjs +90 -0
  127. package/agent/apps/desktop/electron/workspace-cwd.cjs +38 -0
  128. package/agent/apps/desktop/electron/workspace-cwd.test.cjs +45 -0
  129. package/agent/apps/desktop/eslint.config.mjs +0 -3
  130. package/agent/apps/desktop/index.html +27 -2
  131. package/agent/apps/desktop/package.json +31 -11
  132. package/agent/apps/desktop/pr-assets/session-source-folders.png +0 -0
  133. package/agent/apps/desktop/public/apple-touch-icon.png +0 -0
  134. package/agent/apps/desktop/public/nous-girl.jpg +0 -0
  135. package/agent/apps/desktop/scripts/assert-dist-built.cjs +70 -0
  136. package/agent/apps/desktop/scripts/assert-dist-built.test.cjs +84 -0
  137. package/agent/apps/desktop/scripts/before-pack.cjs +78 -0
  138. package/agent/apps/desktop/scripts/before-pack.test.cjs +53 -0
  139. package/agent/apps/desktop/scripts/diag-scroll-reset.mjs +229 -0
  140. package/agent/apps/desktop/scripts/patch-electron-builder-mac-binary.cjs +64 -0
  141. package/agent/apps/desktop/scripts/run-electron-builder.cjs +57 -0
  142. package/agent/apps/desktop/src/app/agents/index.tsx +53 -45
  143. package/agent/apps/desktop/src/app/artifacts/index.tsx +102 -83
  144. package/agent/apps/desktop/src/app/chat/chat-drop-overlay.tsx +29 -8
  145. package/agent/apps/desktop/src/app/chat/chat-swap-overlay.tsx +47 -0
  146. package/agent/apps/desktop/src/app/chat/composer/attachments.tsx +81 -45
  147. package/agent/apps/desktop/src/app/chat/composer/completion-drawer.tsx +13 -24
  148. package/agent/apps/desktop/src/app/chat/composer/context-menu.tsx +138 -88
  149. package/agent/apps/desktop/src/app/chat/composer/controls.tsx +138 -90
  150. package/agent/apps/desktop/src/app/chat/composer/enter-submit-dom-race.test.tsx +218 -0
  151. package/agent/apps/desktop/src/app/chat/composer/focus.ts +32 -0
  152. package/agent/apps/desktop/src/app/chat/composer/help-hint.tsx +38 -25
  153. package/agent/apps/desktop/src/app/chat/composer/hooks/use-live-completion-adapter.ts +7 -0
  154. package/agent/apps/desktop/src/app/chat/composer/hooks/use-mic-recorder.ts +22 -12
  155. package/agent/apps/desktop/src/app/chat/composer/hooks/use-slash-completions.ts +142 -14
  156. package/agent/apps/desktop/src/app/chat/composer/hooks/use-voice-conversation.ts +14 -11
  157. package/agent/apps/desktop/src/app/chat/composer/hooks/use-voice-recorder.ts +9 -6
  158. package/agent/apps/desktop/src/app/chat/composer/ime-composition-dom-repro.test.tsx +108 -0
  159. package/agent/apps/desktop/src/app/chat/composer/index.tsx +930 -180
  160. package/agent/apps/desktop/src/app/chat/composer/inline-refs.ts +136 -32
  161. package/agent/apps/desktop/src/app/chat/composer/model-pill.tsx +86 -0
  162. package/agent/apps/desktop/src/app/chat/composer/queue-panel.tsx +54 -75
  163. package/agent/apps/desktop/src/app/chat/composer/rich-editor.test.ts +117 -1
  164. package/agent/apps/desktop/src/app/chat/composer/rich-editor.ts +117 -6
  165. package/agent/apps/desktop/src/app/chat/composer/slash-nav-dom-repro.test.tsx +186 -0
  166. package/agent/apps/desktop/src/app/chat/composer/status-stack/index.tsx +202 -0
  167. package/agent/apps/desktop/src/app/chat/composer/status-stack/status-row.tsx +155 -0
  168. package/agent/apps/desktop/src/app/chat/composer/text-utils.test.ts +104 -0
  169. package/agent/apps/desktop/src/app/chat/composer/text-utils.ts +37 -9
  170. package/agent/apps/desktop/src/app/chat/composer/trigger-popover.test.tsx +50 -0
  171. package/agent/apps/desktop/src/app/chat/composer/trigger-popover.tsx +105 -40
  172. package/agent/apps/desktop/src/app/chat/composer/types.ts +5 -0
  173. package/agent/apps/desktop/src/app/chat/composer/url-dialog.tsx +11 -15
  174. package/agent/apps/desktop/src/app/chat/composer/voice-activity.tsx +8 -4
  175. package/agent/apps/desktop/src/app/chat/hooks/use-composer-actions.test.ts +57 -0
  176. package/agent/apps/desktop/src/app/chat/hooks/use-composer-actions.ts +70 -16
  177. package/agent/apps/desktop/src/app/chat/hooks/use-file-drop-zone.ts +52 -16
  178. package/agent/apps/desktop/src/app/chat/index.tsx +234 -81
  179. package/agent/apps/desktop/src/app/chat/perf-probe.tsx +69 -21
  180. package/agent/apps/desktop/src/app/chat/right-rail/preview-console.tsx +44 -40
  181. package/agent/apps/desktop/src/app/chat/right-rail/preview-file.tsx +71 -25
  182. package/agent/apps/desktop/src/app/chat/right-rail/preview-pane.test.tsx +40 -1
  183. package/agent/apps/desktop/src/app/chat/right-rail/preview-pane.tsx +55 -53
  184. package/agent/apps/desktop/src/app/chat/right-rail/preview.tsx +35 -17
  185. package/agent/apps/desktop/src/app/chat/scroll-to-bottom-button.test.tsx +67 -0
  186. package/agent/apps/desktop/src/app/chat/scroll-to-bottom-button.tsx +74 -0
  187. package/agent/apps/desktop/src/app/chat/sidebar/cron-jobs-section.tsx +356 -0
  188. package/agent/apps/desktop/src/app/chat/sidebar/index.tsx +1189 -364
  189. package/agent/apps/desktop/src/app/chat/sidebar/load-more-row.tsx +30 -0
  190. package/agent/apps/desktop/src/app/chat/sidebar/order.test.ts +21 -0
  191. package/agent/apps/desktop/src/app/chat/sidebar/order.ts +17 -0
  192. package/agent/apps/desktop/src/app/chat/sidebar/profile-switcher.tsx +524 -0
  193. package/agent/apps/desktop/src/app/chat/sidebar/session-actions-menu.tsx +80 -45
  194. package/agent/apps/desktop/src/app/chat/sidebar/session-row.tsx +120 -25
  195. package/agent/apps/desktop/src/app/chat/sidebar/virtual-session-list.tsx +7 -13
  196. package/agent/apps/desktop/src/app/chat/sidebar/workspace-groups.test.ts +149 -0
  197. package/agent/apps/desktop/src/app/chat/sidebar/workspace-groups.ts +326 -0
  198. package/agent/apps/desktop/src/app/chat/thread-loading.ts +7 -2
  199. package/agent/apps/desktop/src/app/command-center/index.tsx +320 -581
  200. package/agent/apps/desktop/src/app/command-palette/index.tsx +681 -0
  201. package/agent/apps/desktop/src/app/command-palette/marketplace-theme-page.tsx +157 -0
  202. package/agent/apps/desktop/src/app/cron/index.tsx +392 -324
  203. package/agent/apps/desktop/src/app/cron/job-state.ts +29 -0
  204. package/agent/apps/desktop/src/app/desktop-controller.tsx +618 -123
  205. package/agent/apps/desktop/src/app/floating-hud.ts +22 -0
  206. package/agent/apps/desktop/src/app/gateway/hooks/use-gateway-boot.test.tsx +265 -0
  207. package/agent/apps/desktop/src/app/gateway/hooks/use-gateway-boot.ts +260 -14
  208. package/agent/apps/desktop/src/app/gateway/hooks/use-gateway-request.ts +48 -4
  209. package/agent/apps/desktop/src/app/hooks/use-keybinds.ts +270 -0
  210. package/agent/apps/desktop/src/app/hooks/use-refresh-hotkey.ts +45 -0
  211. package/agent/apps/desktop/src/app/layout-constants.ts +19 -0
  212. package/agent/apps/desktop/src/app/messaging/index.tsx +136 -241
  213. package/agent/apps/desktop/src/app/messaging/platform-icon.tsx +95 -0
  214. package/agent/apps/desktop/src/app/model-visibility-overlay.tsx +31 -0
  215. package/agent/apps/desktop/src/app/overlays/overlay-search-input.tsx +18 -62
  216. package/agent/apps/desktop/src/app/overlays/overlay-split-layout.tsx +59 -7
  217. package/agent/apps/desktop/src/app/overlays/overlay-view.tsx +9 -5
  218. package/agent/apps/desktop/src/app/page-search-shell.tsx +42 -20
  219. package/agent/apps/desktop/src/app/profiles/create-profile-dialog.tsx +165 -0
  220. package/agent/apps/desktop/src/app/profiles/delete-profile-dialog.tsx +65 -0
  221. package/agent/apps/desktop/src/app/profiles/index.tsx +174 -199
  222. package/agent/apps/desktop/src/app/profiles/rename-profile-dialog.tsx +125 -0
  223. package/agent/apps/desktop/src/app/right-sidebar/files/dnd-manager.ts +27 -0
  224. package/agent/apps/desktop/src/app/right-sidebar/files/ipc.test.ts +100 -0
  225. package/agent/apps/desktop/src/app/right-sidebar/files/ipc.ts +12 -18
  226. package/agent/apps/desktop/src/app/right-sidebar/files/remote-picker.tsx +177 -0
  227. package/agent/apps/desktop/src/app/right-sidebar/files/tree.tsx +35 -21
  228. package/agent/apps/desktop/src/app/right-sidebar/files/use-project-tree.test.ts +75 -3
  229. package/agent/apps/desktop/src/app/right-sidebar/files/use-project-tree.ts +152 -5
  230. package/agent/apps/desktop/src/app/right-sidebar/index.test.tsx +75 -0
  231. package/agent/apps/desktop/src/app/right-sidebar/index.tsx +166 -129
  232. package/agent/apps/desktop/src/app/right-sidebar/store.ts +19 -4
  233. package/agent/apps/desktop/src/app/right-sidebar/terminal/buffer.ts +65 -0
  234. package/agent/apps/desktop/src/app/right-sidebar/terminal/index.tsx +29 -34
  235. package/agent/apps/desktop/src/app/right-sidebar/terminal/persistent.tsx +18 -6
  236. package/agent/apps/desktop/src/app/right-sidebar/terminal/selection.ts +93 -32
  237. package/agent/apps/desktop/src/app/right-sidebar/terminal/use-terminal-session.ts +381 -119
  238. package/agent/apps/desktop/src/app/routes.ts +9 -0
  239. package/agent/apps/desktop/src/app/session/hooks/use-cwd-actions.ts +17 -7
  240. package/agent/apps/desktop/src/app/session/hooks/use-message-stream.ts +365 -47
  241. package/agent/apps/desktop/src/app/session/hooks/use-model-controls.test.tsx +198 -0
  242. package/agent/apps/desktop/src/app/session/hooks/use-model-controls.ts +70 -34
  243. package/agent/apps/desktop/src/app/session/hooks/use-prompt-actions.test.tsx +1061 -0
  244. package/agent/apps/desktop/src/app/session/hooks/use-prompt-actions.ts +1143 -165
  245. package/agent/apps/desktop/src/app/session/hooks/use-route-resume.test.tsx +341 -2
  246. package/agent/apps/desktop/src/app/session/hooks/use-route-resume.ts +176 -5
  247. package/agent/apps/desktop/src/app/session/hooks/use-session-actions.test.tsx +259 -0
  248. package/agent/apps/desktop/src/app/session/hooks/use-session-actions.ts +452 -149
  249. package/agent/apps/desktop/src/app/session/hooks/use-session-state-cache.test.tsx +327 -0
  250. package/agent/apps/desktop/src/app/session/hooks/use-session-state-cache.ts +133 -4
  251. package/agent/apps/desktop/src/app/session-picker-overlay.tsx +32 -0
  252. package/agent/apps/desktop/src/app/session-switcher.tsx +107 -0
  253. package/agent/apps/desktop/src/app/settings/about-settings.tsx +45 -36
  254. package/agent/apps/desktop/src/app/settings/appearance-settings.tsx +243 -162
  255. package/agent/apps/desktop/src/app/settings/config-settings.tsx +86 -66
  256. package/agent/apps/desktop/src/app/settings/constants.ts +459 -122
  257. package/agent/apps/desktop/src/app/settings/credential-key-ui.tsx +373 -0
  258. package/agent/apps/desktop/src/app/settings/env-credentials.tsx +198 -0
  259. package/agent/apps/desktop/src/app/settings/env-var-actions-menu.tsx +136 -0
  260. package/agent/apps/desktop/src/app/settings/field-copy.ts +56 -0
  261. package/agent/apps/desktop/src/app/settings/gateway-settings.tsx +385 -72
  262. package/agent/apps/desktop/src/app/settings/helpers.test.ts +156 -1
  263. package/agent/apps/desktop/src/app/settings/helpers.ts +30 -2
  264. package/agent/apps/desktop/src/app/settings/index.tsx +118 -84
  265. package/agent/apps/desktop/src/app/settings/keys-settings.tsx +62 -419
  266. package/agent/apps/desktop/src/app/settings/mcp-settings.tsx +65 -60
  267. package/agent/apps/desktop/src/app/settings/model-settings.test.tsx +129 -5
  268. package/agent/apps/desktop/src/app/settings/model-settings.tsx +370 -65
  269. package/agent/apps/desktop/src/app/settings/notifications-settings.tsx +150 -0
  270. package/agent/apps/desktop/src/app/settings/primitives.tsx +5 -11
  271. package/agent/apps/desktop/src/app/settings/provider-config-panel.test.tsx +142 -0
  272. package/agent/apps/desktop/src/app/settings/provider-config-panel.tsx +182 -0
  273. package/agent/apps/desktop/src/app/settings/providers-settings.test.tsx +171 -0
  274. package/agent/apps/desktop/src/app/settings/providers-settings.tsx +471 -0
  275. package/agent/apps/desktop/src/app/settings/sessions-settings.tsx +183 -71
  276. package/agent/apps/desktop/src/app/settings/toolset-config-panel.test.tsx +135 -1
  277. package/agent/apps/desktop/src/app/settings/toolset-config-panel.tsx +180 -57
  278. package/agent/apps/desktop/src/app/settings/types.ts +9 -6
  279. package/agent/apps/desktop/src/app/settings/uninstall-section.tsx +185 -0
  280. package/agent/apps/desktop/src/app/settings/use-deep-link-highlight.ts +60 -0
  281. package/agent/apps/desktop/src/app/shell/app-shell.tsx +59 -13
  282. package/agent/apps/desktop/src/app/shell/gateway-menu-panel.tsx +37 -32
  283. package/agent/apps/desktop/src/app/shell/hooks/use-overlay-routing.ts +6 -3
  284. package/agent/apps/desktop/src/app/shell/hooks/use-statusbar-items.tsx +212 -53
  285. package/agent/apps/desktop/src/app/shell/keybind-panel.tsx +215 -0
  286. package/agent/apps/desktop/src/app/shell/model-edit-submenu.test.tsx +84 -0
  287. package/agent/apps/desktop/src/app/shell/model-edit-submenu.tsx +244 -0
  288. package/agent/apps/desktop/src/app/shell/model-menu-panel.tsx +392 -0
  289. package/agent/apps/desktop/src/app/shell/statusbar-controls.tsx +23 -33
  290. package/agent/apps/desktop/src/app/shell/titlebar-controls.tsx +79 -95
  291. package/agent/apps/desktop/src/app/shell/titlebar.ts +8 -2
  292. package/agent/apps/desktop/src/app/skills/index.test.tsx +11 -0
  293. package/agent/apps/desktop/src/app/skills/index.tsx +79 -64
  294. package/agent/apps/desktop/src/app/types.ts +85 -0
  295. package/agent/apps/desktop/src/app/updates-overlay.tsx +110 -105
  296. package/agent/apps/desktop/src/components/assistant-ui/ansi-text.tsx +34 -0
  297. package/agent/apps/desktop/src/components/assistant-ui/block-direction.test.tsx +129 -0
  298. package/agent/apps/desktop/src/components/assistant-ui/clarify-tool.tsx +102 -81
  299. package/agent/apps/desktop/src/components/assistant-ui/directive-text.tsx +92 -15
  300. package/agent/apps/desktop/src/components/assistant-ui/markdown-text.test.ts +38 -0
  301. package/agent/apps/desktop/src/components/assistant-ui/markdown-text.tsx +304 -45
  302. package/agent/apps/desktop/src/components/assistant-ui/message-render-boundary.test.tsx +80 -0
  303. package/agent/apps/desktop/src/components/assistant-ui/message-render-boundary.tsx +48 -0
  304. package/agent/apps/desktop/src/components/assistant-ui/streaming.test.tsx +142 -90
  305. package/agent/apps/desktop/src/components/assistant-ui/thread-list.tsx +337 -0
  306. package/agent/apps/desktop/src/components/assistant-ui/thread.tsx +667 -190
  307. package/agent/apps/desktop/src/components/assistant-ui/tool-approval-group.test.tsx +299 -0
  308. package/agent/apps/desktop/src/components/assistant-ui/tool-approval.test.tsx +133 -0
  309. package/agent/apps/desktop/src/components/assistant-ui/tool-approval.tsx +239 -0
  310. package/agent/apps/desktop/src/components/assistant-ui/tool-fallback-model.test.ts +31 -0
  311. package/agent/apps/desktop/src/components/assistant-ui/tool-fallback-model.ts +152 -134
  312. package/agent/apps/desktop/src/components/assistant-ui/tool-fallback.tsx +142 -150
  313. package/agent/apps/desktop/src/components/assistant-ui/tooltip-icon-button.tsx +14 -12
  314. package/agent/apps/desktop/src/components/assistant-ui/user-message-edit.test.tsx +141 -0
  315. package/agent/apps/desktop/src/components/assistant-ui/user-message-text.tsx +152 -0
  316. package/agent/apps/desktop/src/components/boot-failure-overlay.tsx +150 -33
  317. package/agent/apps/desktop/src/components/boot-failure-reauth.test.ts +100 -0
  318. package/agent/apps/desktop/src/components/boot-failure-reauth.ts +81 -0
  319. package/agent/apps/desktop/src/components/brand-mark.tsx +19 -0
  320. package/agent/apps/desktop/src/components/chat/code-card.tsx +1 -1
  321. package/agent/apps/desktop/src/components/chat/composer-dock.ts +31 -0
  322. package/agent/apps/desktop/src/components/chat/diff-lines.tsx +1 -1
  323. package/agent/apps/desktop/src/components/chat/disclosure-row.tsx +13 -3
  324. package/agent/apps/desktop/src/components/chat/expandable-block.tsx +52 -0
  325. package/agent/apps/desktop/src/components/chat/generated-image-result.tsx +174 -0
  326. package/agent/apps/desktop/src/components/chat/image-generation-placeholder.tsx +70 -37
  327. package/agent/apps/desktop/src/components/chat/intro.tsx +8 -7
  328. package/agent/apps/desktop/src/components/chat/preview-attachment.tsx +4 -2
  329. package/agent/apps/desktop/src/components/chat/shiki-highlighter.test.ts +37 -0
  330. package/agent/apps/desktop/src/components/chat/shiki-highlighter.tsx +96 -22
  331. package/agent/apps/desktop/src/components/chat/status-row.tsx +70 -0
  332. package/agent/apps/desktop/src/components/chat/status-section.tsx +42 -0
  333. package/agent/apps/desktop/src/components/chat/terminal-output.tsx +54 -0
  334. package/agent/apps/desktop/src/components/chat/zoomable-image.tsx +70 -109
  335. package/agent/apps/desktop/src/components/desktop-install-overlay.tsx +154 -84
  336. package/agent/apps/desktop/src/components/desktop-onboarding-overlay.test.tsx +38 -8
  337. package/agent/apps/desktop/src/components/desktop-onboarding-overlay.tsx +789 -233
  338. package/agent/apps/desktop/src/components/error-boundary.tsx +77 -0
  339. package/agent/apps/desktop/src/components/gateway-connecting-overlay.test.tsx +144 -0
  340. package/agent/apps/desktop/src/components/gateway-connecting-overlay.tsx +7 -1
  341. package/agent/apps/desktop/src/components/haptics-provider.tsx +24 -0
  342. package/agent/apps/desktop/src/components/language-switcher.test.tsx +53 -0
  343. package/agent/apps/desktop/src/components/language-switcher.tsx +175 -0
  344. package/agent/apps/desktop/src/components/model-picker.tsx +42 -40
  345. package/agent/apps/desktop/src/components/model-visibility-dialog.tsx +166 -0
  346. package/agent/apps/desktop/src/components/notifications.tsx +48 -27
  347. package/agent/apps/desktop/src/components/pane-shell/index.ts +1 -1
  348. package/agent/apps/desktop/src/components/pane-shell/pane-shell.tsx +146 -9
  349. package/agent/apps/desktop/src/components/prompt-overlays.tsx +234 -0
  350. package/agent/apps/desktop/src/components/session-picker.tsx +108 -0
  351. package/agent/apps/desktop/src/components/ui/action-status.tsx +25 -0
  352. package/agent/apps/desktop/src/components/ui/badge.tsx +35 -0
  353. package/agent/apps/desktop/src/components/ui/button.tsx +37 -13
  354. package/agent/apps/desktop/src/components/ui/confirm-dialog.tsx +109 -0
  355. package/agent/apps/desktop/src/components/ui/control.ts +25 -0
  356. package/agent/apps/desktop/src/components/ui/copy-button.test.tsx +36 -0
  357. package/agent/apps/desktop/src/components/ui/copy-button.tsx +38 -27
  358. package/agent/apps/desktop/src/components/ui/dialog.tsx +39 -11
  359. package/agent/apps/desktop/src/components/ui/dropdown-menu.tsx +98 -24
  360. package/agent/apps/desktop/src/components/ui/error-state.tsx +50 -0
  361. package/agent/apps/desktop/src/components/ui/fade-text.tsx +9 -2
  362. package/agent/apps/desktop/src/components/ui/{braille-spinner.tsx → glyph-spinner.tsx} +15 -13
  363. package/agent/apps/desktop/src/components/ui/input.tsx +5 -2
  364. package/agent/apps/desktop/src/components/ui/kbd.tsx +83 -12
  365. package/agent/apps/desktop/src/components/ui/log-view.tsx +19 -0
  366. package/agent/apps/desktop/src/components/ui/pagination.tsx +12 -5
  367. package/agent/apps/desktop/src/components/ui/popover.tsx +44 -0
  368. package/agent/apps/desktop/src/components/ui/search-field.tsx +80 -0
  369. package/agent/apps/desktop/src/components/ui/segmented-control.tsx +51 -0
  370. package/agent/apps/desktop/src/components/ui/select.tsx +10 -3
  371. package/agent/apps/desktop/src/components/ui/sheet.tsx +8 -2
  372. package/agent/apps/desktop/src/components/ui/sidebar.tsx +18 -25
  373. package/agent/apps/desktop/src/components/ui/switch.tsx +38 -15
  374. package/agent/apps/desktop/src/components/ui/textarea.tsx +4 -11
  375. package/agent/apps/desktop/src/components/ui/tool-icon.tsx +65 -0
  376. package/agent/apps/desktop/src/components/ui/tooltip.tsx +31 -4
  377. package/agent/apps/desktop/src/fonts/JetBrainsMono-Bold.woff2 +0 -0
  378. package/agent/apps/desktop/src/fonts/JetBrainsMono-Italic.woff2 +0 -0
  379. package/agent/apps/desktop/src/fonts/JetBrainsMono-Regular.woff2 +0 -0
  380. package/agent/apps/desktop/src/global.d.ts +181 -4
  381. package/agent/apps/desktop/src/hermes.test.ts +60 -0
  382. package/agent/apps/desktop/src/hermes.ts +190 -13
  383. package/agent/apps/desktop/src/hooks/use-image-download.ts +85 -0
  384. package/agent/apps/desktop/src/hooks/use-resize-observer.ts +13 -4
  385. package/agent/apps/desktop/src/hooks/use-worktree-info.ts +68 -0
  386. package/agent/apps/desktop/src/i18n/catalog.ts +12 -0
  387. package/agent/apps/desktop/src/i18n/context.test.tsx +232 -0
  388. package/agent/apps/desktop/src/i18n/context.tsx +183 -0
  389. package/agent/apps/desktop/src/i18n/define-locale.ts +41 -0
  390. package/agent/apps/desktop/src/i18n/en.ts +1921 -0
  391. package/agent/apps/desktop/src/i18n/index.ts +20 -0
  392. package/agent/apps/desktop/src/i18n/ja.ts +2053 -0
  393. package/agent/apps/desktop/src/i18n/languages.test.ts +43 -0
  394. package/agent/apps/desktop/src/i18n/languages.ts +86 -0
  395. package/agent/apps/desktop/src/i18n/runtime.test.ts +75 -0
  396. package/agent/apps/desktop/src/i18n/runtime.ts +53 -0
  397. package/agent/apps/desktop/src/i18n/types.ts +1559 -0
  398. package/agent/apps/desktop/src/i18n/zh-hant.ts +1992 -0
  399. package/agent/apps/desktop/src/i18n/zh.ts +2099 -0
  400. package/agent/apps/desktop/src/lib/ansi.test.ts +123 -0
  401. package/agent/apps/desktop/src/lib/ansi.ts +186 -0
  402. package/agent/apps/desktop/src/lib/chat-messages.test.ts +79 -0
  403. package/agent/apps/desktop/src/lib/chat-messages.ts +68 -29
  404. package/agent/apps/desktop/src/lib/chat-runtime.test.ts +65 -1
  405. package/agent/apps/desktop/src/lib/chat-runtime.ts +39 -3
  406. package/agent/apps/desktop/src/lib/completion-sound.ts +519 -0
  407. package/agent/apps/desktop/src/lib/desktop-fs.test.ts +116 -0
  408. package/agent/apps/desktop/src/lib/desktop-fs.ts +113 -0
  409. package/agent/apps/desktop/src/lib/desktop-slash-commands.test.ts +89 -6
  410. package/agent/apps/desktop/src/lib/desktop-slash-commands.ts +270 -131
  411. package/agent/apps/desktop/src/lib/external-link.test.tsx +27 -0
  412. package/agent/apps/desktop/src/lib/external-link.tsx +9 -2
  413. package/agent/apps/desktop/src/lib/gateway-events.test.ts +27 -0
  414. package/agent/apps/desktop/src/lib/gateway-events.ts +16 -0
  415. package/agent/apps/desktop/src/lib/gateway-ws-url.test.ts +78 -0
  416. package/agent/apps/desktop/src/lib/gateway-ws-url.ts +91 -0
  417. package/agent/apps/desktop/src/lib/generated-images.test.ts +97 -0
  418. package/agent/apps/desktop/src/lib/generated-images.ts +116 -0
  419. package/agent/apps/desktop/src/lib/haptics.ts +17 -0
  420. package/agent/apps/desktop/src/lib/icons.ts +10 -2
  421. package/agent/apps/desktop/src/lib/keybinds/actions.ts +137 -0
  422. package/agent/apps/desktop/src/lib/keybinds/combo.test.ts +86 -0
  423. package/agent/apps/desktop/src/lib/keybinds/combo.ts +195 -0
  424. package/agent/apps/desktop/src/lib/local-preview.ts +23 -2
  425. package/agent/apps/desktop/src/lib/markdown-preprocess.ts +20 -7
  426. package/agent/apps/desktop/src/lib/media.remote.test.ts +90 -0
  427. package/agent/apps/desktop/src/lib/media.ts +40 -1
  428. package/agent/apps/desktop/src/lib/model-status-label.test.ts +59 -0
  429. package/agent/apps/desktop/src/lib/model-status-label.ts +122 -0
  430. package/agent/apps/desktop/src/lib/mutable-ref.ts +6 -0
  431. package/agent/apps/desktop/src/lib/profile-color.ts +58 -0
  432. package/agent/apps/desktop/src/lib/query-client.ts +13 -0
  433. package/agent/apps/desktop/src/lib/remend-tail.test.ts +105 -0
  434. package/agent/apps/desktop/src/lib/remend-tail.ts +108 -0
  435. package/agent/apps/desktop/src/lib/session-export.ts +6 -3
  436. package/agent/apps/desktop/src/lib/session-ids.test.ts +44 -0
  437. package/agent/apps/desktop/src/lib/session-ids.ts +26 -0
  438. package/agent/apps/desktop/src/lib/session-search.test.ts +66 -0
  439. package/agent/apps/desktop/src/lib/session-search.ts +21 -0
  440. package/agent/apps/desktop/src/lib/session-source.ts +126 -0
  441. package/agent/apps/desktop/src/lib/storage.test.ts +25 -0
  442. package/agent/apps/desktop/src/lib/storage.ts +35 -1
  443. package/agent/apps/desktop/src/lib/todos.test.ts +46 -1
  444. package/agent/apps/desktop/src/lib/todos.ts +37 -0
  445. package/agent/apps/desktop/src/lib/tool-result-summary.ts +5 -1
  446. package/agent/apps/desktop/src/lib/update-copy.test.ts +38 -0
  447. package/agent/apps/desktop/src/lib/update-copy.ts +44 -0
  448. package/agent/apps/desktop/src/lib/use-enter-animation.ts +2 -2
  449. package/agent/apps/desktop/src/lib/yolo-session.ts +50 -0
  450. package/agent/apps/desktop/src/main.tsx +19 -19
  451. package/agent/apps/desktop/src/store/boot.ts +4 -3
  452. package/agent/apps/desktop/src/store/clarify.test.ts +81 -0
  453. package/agent/apps/desktop/src/store/clarify.ts +50 -13
  454. package/agent/apps/desktop/src/store/command-palette.ts +20 -0
  455. package/agent/apps/desktop/src/store/compaction.test.ts +53 -0
  456. package/agent/apps/desktop/src/store/compaction.ts +38 -0
  457. package/agent/apps/desktop/src/store/completion-sound.ts +32 -0
  458. package/agent/apps/desktop/src/store/composer-input-history.test.ts +147 -0
  459. package/agent/apps/desktop/src/store/composer-input-history.ts +158 -0
  460. package/agent/apps/desktop/src/store/composer-queue.test.ts +68 -0
  461. package/agent/apps/desktop/src/store/composer-queue.ts +76 -0
  462. package/agent/apps/desktop/src/store/composer-status.test.ts +99 -0
  463. package/agent/apps/desktop/src/store/composer-status.ts +277 -0
  464. package/agent/apps/desktop/src/store/composer.test.ts +106 -0
  465. package/agent/apps/desktop/src/store/composer.ts +116 -0
  466. package/agent/apps/desktop/src/store/cron.ts +19 -0
  467. package/agent/apps/desktop/src/store/gateway.ts +280 -6
  468. package/agent/apps/desktop/src/store/keybinds.ts +143 -0
  469. package/agent/apps/desktop/src/store/layout.ts +107 -9
  470. package/agent/apps/desktop/src/store/model-presets.test.ts +51 -0
  471. package/agent/apps/desktop/src/store/model-presets.ts +86 -0
  472. package/agent/apps/desktop/src/store/model-visibility.test.ts +99 -0
  473. package/agent/apps/desktop/src/store/model-visibility.ts +161 -0
  474. package/agent/apps/desktop/src/store/native-notifications.test.ts +192 -0
  475. package/agent/apps/desktop/src/store/native-notifications.ts +203 -0
  476. package/agent/apps/desktop/src/store/notifications.ts +10 -7
  477. package/agent/apps/desktop/src/store/onboarding.test.ts +271 -1
  478. package/agent/apps/desktop/src/store/onboarding.ts +268 -38
  479. package/agent/apps/desktop/src/store/preview.ts +10 -1
  480. package/agent/apps/desktop/src/store/profile.test.ts +89 -0
  481. package/agent/apps/desktop/src/store/profile.ts +395 -0
  482. package/agent/apps/desktop/src/store/prompts.test.ts +127 -0
  483. package/agent/apps/desktop/src/store/prompts.ts +117 -0
  484. package/agent/apps/desktop/src/store/session-switcher.test.ts +115 -0
  485. package/agent/apps/desktop/src/store/session-switcher.ts +128 -0
  486. package/agent/apps/desktop/src/store/session-sync.ts +25 -0
  487. package/agent/apps/desktop/src/store/session.test.ts +268 -2
  488. package/agent/apps/desktop/src/store/session.ts +392 -18
  489. package/agent/apps/desktop/src/store/subagents.ts +3 -0
  490. package/agent/apps/desktop/src/store/system-actions.ts +48 -0
  491. package/agent/apps/desktop/src/store/thread-scroll.ts +58 -5
  492. package/agent/apps/desktop/src/store/todos.test.ts +47 -0
  493. package/agent/apps/desktop/src/store/todos.ts +64 -0
  494. package/agent/apps/desktop/src/store/tool-dismiss.ts +45 -0
  495. package/agent/apps/desktop/src/store/translucency.ts +38 -0
  496. package/agent/apps/desktop/src/store/updates.test.ts +187 -2
  497. package/agent/apps/desktop/src/store/updates.ts +268 -18
  498. package/agent/apps/desktop/src/store/windows.test.ts +143 -0
  499. package/agent/apps/desktop/src/store/windows.ts +115 -0
  500. package/agent/apps/desktop/src/styles.css +510 -119
  501. package/agent/apps/desktop/src/themes/color.ts +142 -0
  502. package/agent/apps/desktop/src/themes/context.tsx +128 -75
  503. package/agent/apps/desktop/src/themes/install.test.ts +119 -0
  504. package/agent/apps/desktop/src/themes/install.ts +95 -0
  505. package/agent/apps/desktop/src/themes/presets.test.ts +33 -0
  506. package/agent/apps/desktop/src/themes/presets.ts +13 -4
  507. package/agent/apps/desktop/src/themes/profile-theme.test.ts +41 -0
  508. package/agent/apps/desktop/src/themes/types.ts +35 -0
  509. package/agent/apps/desktop/src/themes/user-themes.test.ts +63 -0
  510. package/agent/apps/desktop/src/themes/user-themes.ts +122 -0
  511. package/agent/apps/desktop/src/themes/vscode.test.ts +171 -0
  512. package/agent/apps/desktop/src/themes/vscode.ts +343 -0
  513. package/agent/apps/desktop/src/types/hermes.ts +138 -1
  514. package/agent/apps/desktop/tsconfig.json +2 -2
  515. package/agent/apps/desktop/vite.config.ts +18 -0
  516. package/agent/apps/shared/package.json +1 -1
  517. package/agent/apps/shared/src/json-rpc-gateway.ts +63 -2
  518. package/agent/apps/shared/tsconfig.json +2 -2
  519. package/agent/cli-config.yaml.example +78 -1
  520. package/agent/cli.py +2177 -3162
  521. package/agent/cron/blueprint_catalog.py +713 -0
  522. package/agent/cron/jobs.py +226 -110
  523. package/agent/cron/scheduler.py +468 -193
  524. package/agent/cron/scheduler_provider.py +177 -0
  525. package/agent/cron/scripts/__init__.py +1 -0
  526. package/agent/cron/scripts/classify_items.py +226 -0
  527. package/agent/cron/suggestion_catalog.py +154 -0
  528. package/agent/cron/suggestions.py +257 -0
  529. package/agent/docs/chronos-managed-cron-contract.md +196 -0
  530. package/agent/docs/design/profile-builder.md +146 -0
  531. package/agent/docs/middleware/README.md +260 -0
  532. package/agent/docs/observability/README.md +316 -0
  533. package/agent/docs/plans/2026-06-09-003-fix-telegram-stream-overflow-continuations-plan.md +240 -0
  534. package/agent/docs/rca-ssl-cacert-post-git-pull.md +54 -0
  535. package/agent/docs/relay-connector-contract.md +285 -0
  536. package/agent/gateway/authz_mixin.py +536 -0
  537. package/agent/gateway/channel_directory.py +65 -3
  538. package/agent/gateway/config.py +222 -12
  539. package/agent/gateway/display_config.py +10 -0
  540. package/agent/gateway/hooks.py +17 -0
  541. package/agent/gateway/kanban_watchers.py +1146 -0
  542. package/agent/gateway/message_timestamps.py +166 -0
  543. package/agent/gateway/platforms/ADDING_A_PLATFORM.md +29 -0
  544. package/agent/gateway/platforms/api_server.py +216 -38
  545. package/agent/gateway/platforms/base.py +210 -58
  546. package/agent/gateway/platforms/email.py +122 -12
  547. package/agent/gateway/platforms/feishu.py +80 -11
  548. package/agent/gateway/platforms/feishu_meeting_invite.py +212 -0
  549. package/agent/gateway/platforms/matrix.py +1498 -297
  550. package/agent/gateway/platforms/qqbot/adapter.py +6 -0
  551. package/agent/gateway/platforms/signal.py +8 -0
  552. package/agent/gateway/platforms/slack.py +308 -12
  553. package/agent/gateway/platforms/telegram.py +831 -24
  554. package/agent/gateway/platforms/webhook.py +109 -21
  555. package/agent/gateway/platforms/weixin.py +113 -2
  556. package/agent/gateway/platforms/whatsapp.py +94 -288
  557. package/agent/gateway/platforms/whatsapp_cloud.py +1956 -0
  558. package/agent/gateway/platforms/whatsapp_common.py +367 -0
  559. package/agent/gateway/platforms/yuanbao.py +608 -191
  560. package/agent/gateway/platforms/yuanbao_proto.py +232 -23
  561. package/agent/gateway/relay/__init__.py +375 -0
  562. package/agent/gateway/relay/adapter.py +222 -0
  563. package/agent/gateway/relay/auth.py +168 -0
  564. package/agent/gateway/relay/descriptor.py +118 -0
  565. package/agent/gateway/relay/transport.py +101 -0
  566. package/agent/gateway/relay/ws_transport.py +327 -0
  567. package/agent/gateway/response_filters.py +53 -0
  568. package/agent/gateway/rich_sent_store.py +80 -0
  569. package/agent/gateway/run.py +2940 -5001
  570. package/agent/gateway/session.py +109 -8
  571. package/agent/gateway/session_context.py +22 -4
  572. package/agent/gateway/slash_commands.py +3854 -0
  573. package/agent/gateway/status.py +141 -21
  574. package/agent/gateway/stream_consumer.py +288 -31
  575. package/agent/hermes-already-has-routines.md +1 -1
  576. package/agent/hermes_cli/__init__.py +62 -17
  577. package/agent/hermes_cli/_parser.py +30 -0
  578. package/agent/hermes_cli/_subprocess_compat.py +61 -0
  579. package/agent/hermes_cli/active_sessions.py +320 -0
  580. package/agent/hermes_cli/auth.py +707 -59
  581. package/agent/hermes_cli/auth_commands.py +39 -22
  582. package/agent/hermes_cli/backup.py +109 -7
  583. package/agent/hermes_cli/banner.py +88 -0
  584. package/agent/hermes_cli/blueprint_cmd.py +318 -0
  585. package/agent/hermes_cli/cli_agent_setup_mixin.py +684 -0
  586. package/agent/hermes_cli/cli_commands_mixin.py +2293 -0
  587. package/agent/hermes_cli/commands.py +215 -91
  588. package/agent/hermes_cli/config.py +967 -130
  589. package/agent/hermes_cli/container_boot.py +76 -11
  590. package/agent/hermes_cli/cron.py +5 -11
  591. package/agent/hermes_cli/curator.py +21 -0
  592. package/agent/hermes_cli/dashboard_auth/__init__.py +2 -0
  593. package/agent/hermes_cli/dashboard_auth/base.py +62 -0
  594. package/agent/hermes_cli/dashboard_auth/cookies.py +32 -19
  595. package/agent/hermes_cli/dashboard_auth/login_page.py +156 -6
  596. package/agent/hermes_cli/dashboard_auth/middleware.py +28 -4
  597. package/agent/hermes_cli/dashboard_auth/prefix.py +46 -2
  598. package/agent/hermes_cli/dashboard_auth/public_paths.py +6 -0
  599. package/agent/hermes_cli/dashboard_auth/routes.py +158 -2
  600. package/agent/hermes_cli/dashboard_auth/ws_tickets.py +85 -11
  601. package/agent/hermes_cli/dashboard_register.py +427 -0
  602. package/agent/hermes_cli/debug.py +155 -50
  603. package/agent/hermes_cli/doctor.py +255 -14
  604. package/agent/hermes_cli/dump.py +60 -6
  605. package/agent/hermes_cli/env_loader.py +33 -0
  606. package/agent/hermes_cli/gateway.py +755 -103
  607. package/agent/hermes_cli/gateway_enroll.py +250 -0
  608. package/agent/hermes_cli/gateway_windows.py +254 -11
  609. package/agent/hermes_cli/gui_uninstall.py +285 -0
  610. package/agent/hermes_cli/inventory.py +105 -4
  611. package/agent/hermes_cli/kanban.py +58 -71
  612. package/agent/hermes_cli/kanban_db.py +391 -14
  613. package/agent/hermes_cli/kanban_decompose.py +2 -2
  614. package/agent/hermes_cli/kanban_specify.py +3 -1
  615. package/agent/hermes_cli/logs.py +2 -0
  616. package/agent/hermes_cli/main.py +2889 -5287
  617. package/agent/hermes_cli/managed_scope.py +214 -0
  618. package/agent/hermes_cli/managed_uv.py +254 -0
  619. package/agent/hermes_cli/mcp_catalog.py +6 -3
  620. package/agent/hermes_cli/mcp_config.py +145 -21
  621. package/agent/hermes_cli/mcp_security.py +96 -0
  622. package/agent/hermes_cli/mcp_startup.py +32 -3
  623. package/agent/hermes_cli/memory_providers.py +149 -0
  624. package/agent/hermes_cli/memory_setup.py +97 -42
  625. package/agent/hermes_cli/middleware.py +313 -0
  626. package/agent/hermes_cli/model_catalog.py +31 -0
  627. package/agent/hermes_cli/model_cost_guard.py +134 -0
  628. package/agent/hermes_cli/model_normalize.py +2 -1
  629. package/agent/hermes_cli/model_setup_flows.py +2759 -0
  630. package/agent/hermes_cli/model_switch.py +242 -27
  631. package/agent/hermes_cli/models.py +284 -44
  632. package/agent/hermes_cli/nous_account.py +33 -6
  633. package/agent/hermes_cli/nous_billing.py +406 -0
  634. package/agent/hermes_cli/nous_subscription.py +202 -5
  635. package/agent/hermes_cli/platforms.py +1 -0
  636. package/agent/hermes_cli/plugins.py +218 -18
  637. package/agent/hermes_cli/plugins_cmd.py +249 -105
  638. package/agent/hermes_cli/portal_cli.py +56 -16
  639. package/agent/hermes_cli/profile_distribution.py +6 -1
  640. package/agent/hermes_cli/profiles.py +283 -32
  641. package/agent/hermes_cli/provider_catalog.py +170 -0
  642. package/agent/hermes_cli/providers.py +4 -1
  643. package/agent/hermes_cli/pty_bridge.py +53 -4
  644. package/agent/hermes_cli/runtime_provider.py +216 -34
  645. package/agent/hermes_cli/secret_prompt.py +4 -4
  646. package/agent/hermes_cli/secrets_cli.py +24 -0
  647. package/agent/hermes_cli/send_cmd.py +28 -2
  648. package/agent/hermes_cli/service_manager.py +166 -19
  649. package/agent/hermes_cli/session_listing.py +97 -0
  650. package/agent/hermes_cli/setup.py +158 -94
  651. package/agent/hermes_cli/setup_whatsapp_cloud.py +541 -0
  652. package/agent/hermes_cli/skills_config.py +8 -2
  653. package/agent/hermes_cli/skills_hub.py +149 -7
  654. package/agent/hermes_cli/status.py +2 -2
  655. package/agent/hermes_cli/subcommands/__init__.py +18 -0
  656. package/agent/hermes_cli/subcommands/_shared.py +29 -0
  657. package/agent/hermes_cli/subcommands/acp.py +52 -0
  658. package/agent/hermes_cli/subcommands/auth.py +109 -0
  659. package/agent/hermes_cli/subcommands/backup.py +38 -0
  660. package/agent/hermes_cli/subcommands/claw.py +92 -0
  661. package/agent/hermes_cli/subcommands/config.py +49 -0
  662. package/agent/hermes_cli/subcommands/cron.py +163 -0
  663. package/agent/hermes_cli/subcommands/dashboard.py +143 -0
  664. package/agent/hermes_cli/subcommands/debug.py +77 -0
  665. package/agent/hermes_cli/subcommands/doctor.py +35 -0
  666. package/agent/hermes_cli/subcommands/dump.py +28 -0
  667. package/agent/hermes_cli/subcommands/gateway.py +332 -0
  668. package/agent/hermes_cli/subcommands/gui.py +63 -0
  669. package/agent/hermes_cli/subcommands/hooks.py +77 -0
  670. package/agent/hermes_cli/subcommands/import_cmd.py +31 -0
  671. package/agent/hermes_cli/subcommands/insights.py +25 -0
  672. package/agent/hermes_cli/subcommands/login.py +78 -0
  673. package/agent/hermes_cli/subcommands/logout.py +28 -0
  674. package/agent/hermes_cli/subcommands/logs.py +78 -0
  675. package/agent/hermes_cli/subcommands/mcp.py +108 -0
  676. package/agent/hermes_cli/subcommands/memory.py +53 -0
  677. package/agent/hermes_cli/subcommands/model.py +72 -0
  678. package/agent/hermes_cli/subcommands/pairing.py +36 -0
  679. package/agent/hermes_cli/subcommands/plugins.py +94 -0
  680. package/agent/hermes_cli/subcommands/postinstall.py +23 -0
  681. package/agent/hermes_cli/subcommands/profile.py +203 -0
  682. package/agent/hermes_cli/subcommands/prompt_size.py +36 -0
  683. package/agent/hermes_cli/subcommands/security.py +62 -0
  684. package/agent/hermes_cli/subcommands/setup.py +58 -0
  685. package/agent/hermes_cli/subcommands/skills.py +298 -0
  686. package/agent/hermes_cli/subcommands/slack.py +60 -0
  687. package/agent/hermes_cli/subcommands/status.py +28 -0
  688. package/agent/hermes_cli/subcommands/tools.py +95 -0
  689. package/agent/hermes_cli/subcommands/uninstall.py +41 -0
  690. package/agent/hermes_cli/subcommands/update.py +70 -0
  691. package/agent/hermes_cli/subcommands/version.py +18 -0
  692. package/agent/hermes_cli/subcommands/webhook.py +76 -0
  693. package/agent/hermes_cli/subcommands/whatsapp.py +22 -0
  694. package/agent/hermes_cli/suggestions_cmd.py +153 -0
  695. package/agent/hermes_cli/telegram_managed_bot.py +358 -0
  696. package/agent/hermes_cli/tips.py +3 -4
  697. package/agent/hermes_cli/tools_config.py +155 -28
  698. package/agent/hermes_cli/uninstall.py +231 -35
  699. package/agent/hermes_cli/web_server.py +6188 -975
  700. package/agent/hermes_cli/win_pty_bridge.py +179 -0
  701. package/agent/hermes_cli/write_approval_commands.py +209 -0
  702. package/agent/hermes_constants.py +164 -33
  703. package/agent/hermes_logging.py +74 -2
  704. package/agent/hermes_state.py +919 -106
  705. package/agent/hermes_time.py +20 -0
  706. package/agent/locales/af.yaml +23 -0
  707. package/agent/locales/de.yaml +23 -0
  708. package/agent/locales/en.yaml +20 -0
  709. package/agent/locales/es.yaml +23 -0
  710. package/agent/locales/fr.yaml +23 -0
  711. package/agent/locales/ga.yaml +23 -0
  712. package/agent/locales/hu.yaml +23 -0
  713. package/agent/locales/it.yaml +23 -0
  714. package/agent/locales/ja.yaml +23 -0
  715. package/agent/locales/ko.yaml +23 -0
  716. package/agent/locales/pt.yaml +23 -0
  717. package/agent/locales/ru.yaml +23 -0
  718. package/agent/locales/tr.yaml +23 -0
  719. package/agent/locales/uk.yaml +23 -0
  720. package/agent/locales/zh-hant.yaml +23 -0
  721. package/agent/locales/zh.yaml +23 -0
  722. package/agent/model_tools.py +204 -40
  723. package/agent/optional-mcps/clawpump/manifest.yaml +4 -2
  724. package/agent/optional-mcps/clawpump-stdio/manifest.yaml +2 -0
  725. package/agent/optional-mcps/unreal-engine/manifest.yaml +54 -0
  726. package/agent/optional-skills/blockchain/hyperliquid/SKILL.md +2 -2
  727. package/agent/optional-skills/blockchain/hyperliquid/scripts/hyperliquid_client.py +1 -1
  728. package/agent/optional-skills/creative/kanban-video-orchestrator/SKILL.md +1 -1
  729. package/agent/optional-skills/creative/kanban-video-orchestrator/assets/setup.sh.tmpl +4 -3
  730. package/agent/optional-skills/creative/kanban-video-orchestrator/references/kanban-setup.md +6 -4
  731. package/agent/optional-skills/creative/kanban-video-orchestrator/references/tool-matrix.md +2 -2
  732. package/agent/{skills/software-development → optional-skills/devops}/hermes-s6-container-supervision/SKILL.md +2 -0
  733. package/agent/optional-skills/devops/watchers/SKILL.md +1 -1
  734. package/agent/optional-skills/devops/watchers/scripts/watch_github.py +2 -1
  735. package/agent/optional-skills/payments/mpp-agent/SKILL.md +124 -0
  736. package/agent/optional-skills/payments/stripe-link-cli/SKILL.md +184 -0
  737. package/agent/optional-skills/payments/stripe-projects/SKILL.md +120 -0
  738. package/agent/optional-skills/productivity/canvas/SKILL.md +1 -1
  739. package/agent/optional-skills/productivity/canvas/scripts/canvas_api.py +4 -1
  740. package/agent/optional-skills/productivity/shop/SKILL.md +224 -0
  741. package/agent/optional-skills/productivity/shop/references/catalog-mcp.md +236 -0
  742. package/agent/optional-skills/productivity/shop/references/direct-api.md +278 -0
  743. package/agent/optional-skills/productivity/shop/references/legal.md +3 -0
  744. package/agent/optional-skills/productivity/shop/references/safety.md +36 -0
  745. package/agent/optional-skills/productivity/shopify/SKILL.md +1 -1
  746. package/agent/optional-skills/productivity/siyuan/SKILL.md +1 -1
  747. package/agent/optional-skills/productivity/telephony/SKILL.md +4 -4
  748. package/agent/optional-skills/productivity/telephony/scripts/telephony.py +15 -15
  749. package/agent/optional-skills/security/1password/SKILL.md +1 -1
  750. package/agent/{skills/red-teaming → optional-skills/security}/godmode/SKILL.md +3 -4
  751. package/agent/{skills/red-teaming → optional-skills/security}/godmode/scripts/auto_jailbreak.py +3 -1
  752. package/agent/optional-skills/software-development/rest-graphql-debug/SKILL.md +1 -1
  753. package/agent/{skills → optional-skills}/software-development/subagent-driven-development/SKILL.md +5 -5
  754. package/agent/package-lock.json +4082 -7907
  755. package/agent/package.json +18 -3
  756. package/agent/plugins/browser/firecrawl/provider.py +4 -1
  757. package/agent/plugins/cron/__init__.py +344 -0
  758. package/agent/plugins/cron/chronos/__init__.py +241 -0
  759. package/agent/plugins/cron/chronos/_nas_client.py +123 -0
  760. package/agent/plugins/cron/chronos/plugin.yaml +9 -0
  761. package/agent/plugins/cron/chronos/verify.py +103 -0
  762. package/agent/plugins/dashboard_auth/basic/__init__.py +491 -0
  763. package/agent/plugins/dashboard_auth/basic/plugin.yaml +7 -0
  764. package/agent/plugins/dashboard_auth/nous/__init__.py +12 -14
  765. package/agent/plugins/dashboard_auth/self_hosted/__init__.py +736 -0
  766. package/agent/plugins/dashboard_auth/self_hosted/plugin.yaml +8 -0
  767. package/agent/plugins/disk-cleanup/disk_cleanup.py +100 -20
  768. package/agent/plugins/google_meet/audio_bridge.py +4 -0
  769. package/agent/plugins/google_meet/meet_bot.py +7 -1
  770. package/agent/plugins/hermes-achievements/dashboard/dist/index.js +9 -15
  771. package/agent/plugins/image_gen/fal/__init__.py +35 -6
  772. package/agent/plugins/image_gen/krea/__init__.py +56 -13
  773. package/agent/plugins/image_gen/openai/__init__.py +122 -24
  774. package/agent/plugins/image_gen/openai-codex/__init__.py +28 -2
  775. package/agent/plugins/image_gen/xai/__init__.py +92 -12
  776. package/agent/plugins/kanban/dashboard/dist/index.js +63 -48
  777. package/agent/plugins/kanban/dashboard/plugin_api.py +39 -35
  778. package/agent/plugins/memory/__init__.py +48 -5
  779. package/agent/plugins/memory/byterover/__init__.py +1 -0
  780. package/agent/plugins/memory/hindsight/README.md +1 -1
  781. package/agent/plugins/memory/hindsight/__init__.py +138 -24
  782. package/agent/plugins/memory/hindsight/plugin.yaml +1 -1
  783. package/agent/plugins/memory/honcho/README.md +13 -10
  784. package/agent/plugins/memory/honcho/cli.py +247 -122
  785. package/agent/plugins/memory/honcho/client.py +112 -102
  786. package/agent/plugins/memory/openviking/README.md +12 -1
  787. package/agent/plugins/memory/openviking/__init__.py +2281 -107
  788. package/agent/plugins/memory/openviking/plugin.yaml +1 -2
  789. package/agent/plugins/memory/supermemory/README.md +22 -10
  790. package/agent/plugins/memory/supermemory/__init__.py +142 -37
  791. package/agent/plugins/memory/supermemory/plugin.yaml +1 -1
  792. package/agent/plugins/model-providers/anthropic/__init__.py +1 -0
  793. package/agent/plugins/model-providers/bedrock/__init__.py +1 -0
  794. package/agent/plugins/model-providers/copilot-acp/__init__.py +1 -0
  795. package/agent/plugins/model-providers/custom/__init__.py +8 -2
  796. package/agent/plugins/model-providers/kimi-coding/__init__.py +16 -7
  797. package/agent/plugins/model-providers/minimax/__init__.py +60 -8
  798. package/agent/plugins/model-providers/opencode-zen/__init__.py +12 -3
  799. package/agent/plugins/model-providers/openrouter/__init__.py +75 -4
  800. package/agent/plugins/model-providers/xiaomi/__init__.py +2 -0
  801. package/agent/plugins/model-providers/zai/__init__.py +1 -0
  802. package/agent/plugins/observability/langfuse/__init__.py +147 -14
  803. package/agent/plugins/observability/nemo_relay/README.md +559 -0
  804. package/agent/plugins/observability/nemo_relay/__init__.py +962 -0
  805. package/agent/plugins/observability/nemo_relay/plugin.yaml +20 -0
  806. package/agent/plugins/platforms/discord/adapter.py +932 -61
  807. package/agent/plugins/platforms/discord/voice_mixer.py +379 -0
  808. package/agent/plugins/platforms/google_chat/adapter.py +9 -3
  809. package/agent/plugins/platforms/google_chat/oauth.py +1 -1
  810. package/agent/plugins/platforms/homeassistant/__init__.py +3 -0
  811. package/agent/{gateway/platforms/homeassistant.py → plugins/platforms/homeassistant/adapter.py} +128 -0
  812. package/agent/plugins/platforms/homeassistant/plugin.yaml +22 -0
  813. package/agent/plugins/platforms/irc/adapter.py +4 -1
  814. package/agent/plugins/platforms/line/adapter.py +16 -1
  815. package/agent/plugins/platforms/mattermost/adapter.py +100 -24
  816. package/agent/plugins/platforms/photon/README.md +179 -0
  817. package/agent/plugins/platforms/photon/__init__.py +4 -0
  818. package/agent/plugins/platforms/photon/adapter.py +1586 -0
  819. package/agent/plugins/platforms/photon/auth.py +1046 -0
  820. package/agent/plugins/platforms/photon/cli.py +439 -0
  821. package/agent/plugins/platforms/photon/plugin.yaml +88 -0
  822. package/agent/plugins/platforms/photon/sidecar/README.md +52 -0
  823. package/agent/plugins/platforms/photon/sidecar/index.mjs +720 -0
  824. package/agent/plugins/platforms/photon/sidecar/package-lock.json +1730 -0
  825. package/agent/plugins/platforms/photon/sidecar/package.json +25 -0
  826. package/agent/plugins/platforms/photon/sidecar/patch-spectrum-mixed-attachments.mjs +155 -0
  827. package/agent/plugins/platforms/raft/__init__.py +3 -0
  828. package/agent/plugins/platforms/raft/adapter.py +774 -0
  829. package/agent/plugins/platforms/raft/plugin.yaml +19 -0
  830. package/agent/plugins/platforms/simplex/adapter.py +777 -220
  831. package/agent/plugins/platforms/simplex/plugin.yaml +21 -2
  832. package/agent/plugins/platforms/teams/adapter.py +175 -5
  833. package/agent/plugins/plugin_utils.py +135 -0
  834. package/agent/plugins/video_gen/fal/__init__.py +10 -3
  835. package/agent/plugins/web/searxng/provider.py +15 -2
  836. package/agent/plugins/web/xai/provider.py +2 -2
  837. package/agent/providers/base.py +22 -3
  838. package/agent/pyproject.toml +115 -21
  839. package/agent/run_agent.py +733 -39
  840. package/agent/scripts/build_skills_index.py +51 -19
  841. package/agent/scripts/check_subprocess_stdin.py +177 -0
  842. package/agent/scripts/contributor_audit.py +2 -0
  843. package/agent/scripts/docker_config_migrate.py +67 -0
  844. package/agent/scripts/install.cmd +3 -3
  845. package/agent/scripts/install.ps1 +580 -154
  846. package/agent/scripts/install.sh +402 -185
  847. package/agent/scripts/lib/node-bootstrap.sh +39 -4
  848. package/agent/scripts/release.py +183 -0
  849. package/agent/scripts/run_tests.sh +1 -0
  850. package/agent/scripts/run_tests_parallel.py +18 -23
  851. package/agent/scripts/whatsapp-bridge/bridge.js +25 -4
  852. package/agent/setup.py +59 -0
  853. package/agent/skills/autonomous-ai-agents/codex/SKILL.md +19 -0
  854. package/agent/skills/autonomous-ai-agents/hermes-agent/SKILL.md +10 -3
  855. package/agent/skills/{mcp/native-mcp/SKILL.md → autonomous-ai-agents/hermes-agent/references/native-mcp.md} +0 -13
  856. package/agent/skills/{devops/webhook-subscriptions/SKILL.md → autonomous-ai-agents/hermes-agent/references/webhooks.md} +1 -11
  857. package/agent/skills/clawpump/SKILL.md +3 -1
  858. package/agent/skills/devops/kanban-orchestrator/SKILL.md +1 -0
  859. package/agent/skills/devops/kanban-worker/SKILL.md +1 -0
  860. package/agent/skills/github/github-auth/SKILL.md +2 -2
  861. package/agent/skills/github/github-auth/scripts/gh-env.sh +2 -2
  862. package/agent/skills/github/github-code-review/SKILL.md +2 -2
  863. package/agent/skills/github/github-issues/SKILL.md +2 -2
  864. package/agent/skills/github/github-pr-workflow/SKILL.md +2 -2
  865. package/agent/skills/github/github-repo-management/SKILL.md +2 -2
  866. package/agent/skills/media/gif-search/SKILL.md +1 -1
  867. package/agent/skills/media/youtube-content/SKILL.md +10 -7
  868. package/agent/skills/media/youtube-content/scripts/fetch_transcript.py +3 -3
  869. package/agent/skills/note-taking/obsidian/SKILL.md +1 -1
  870. package/agent/skills/productivity/airtable/SKILL.md +2 -2
  871. package/agent/skills/productivity/google-workspace/scripts/setup.py +33 -7
  872. package/agent/skills/productivity/notion/SKILL.md +2 -2
  873. package/agent/skills/productivity/teams-meeting-pipeline/SKILL.md +1 -1
  874. package/agent/skills/research/llm-wiki/SKILL.md +1 -1
  875. package/agent/skills/social-media/xurl/SKILL.md +9 -0
  876. package/agent/skills/software-development/hermes-agent-skill-authoring/SKILL.md +1 -1
  877. package/agent/skills/software-development/plan/SKILL.md +285 -5
  878. package/agent/skills/software-development/requesting-code-review/SKILL.md +2 -2
  879. package/agent/skills/software-development/simplify-code/SKILL.md +212 -0
  880. package/agent/skills/software-development/spike/SKILL.md +2 -2
  881. package/agent/skills/software-development/systematic-debugging/SKILL.md +1 -1
  882. package/agent/skills/software-development/test-driven-development/SKILL.md +1 -1
  883. package/agent/tools/approval.py +302 -4
  884. package/agent/tools/async_delegation.py +386 -0
  885. package/agent/tools/blueprints.py +325 -0
  886. package/agent/tools/browser_cdp_tool.py +3 -3
  887. package/agent/tools/browser_tool.py +34 -6
  888. package/agent/tools/checkpoint_manager.py +31 -1
  889. package/agent/tools/clarify_tool.py +55 -5
  890. package/agent/tools/code_execution_tool.py +31 -14
  891. package/agent/tools/computer_use/cua_backend.py +81 -3
  892. package/agent/tools/computer_use/tool.py +79 -5
  893. package/agent/tools/computer_use/vision_routing.py +55 -3
  894. package/agent/tools/credential_files.py +31 -12
  895. package/agent/tools/cronjob_tools.py +30 -20
  896. package/agent/tools/delegate_tool.py +356 -31
  897. package/agent/tools/env_probe.py +1 -0
  898. package/agent/tools/environments/docker.py +163 -8
  899. package/agent/tools/environments/file_sync.py +2 -1
  900. package/agent/tools/environments/local.py +74 -23
  901. package/agent/tools/environments/singularity.py +4 -1
  902. package/agent/tools/environments/ssh.py +78 -11
  903. package/agent/tools/file_operations.py +277 -41
  904. package/agent/tools/file_tools.py +166 -28
  905. package/agent/tools/image_generation_tool.py +515 -29
  906. package/agent/tools/kanban_tools.py +99 -0
  907. package/agent/tools/lazy_deps.py +33 -2
  908. package/agent/tools/mcp_oauth.py +5 -5
  909. package/agent/tools/mcp_oauth_manager.py +7 -5
  910. package/agent/tools/mcp_tool.py +840 -33
  911. package/agent/tools/memory_tool.py +335 -38
  912. package/agent/tools/osv_check.py +15 -1
  913. package/agent/tools/process_registry.py +155 -11
  914. package/agent/tools/read_extract.py +248 -0
  915. package/agent/tools/read_terminal_tool.py +93 -0
  916. package/agent/tools/schema_sanitizer.py +38 -0
  917. package/agent/tools/send_message_tool.py +163 -49
  918. package/agent/tools/session_search_tool.py +189 -7
  919. package/agent/tools/skill_manager_tool.py +202 -3
  920. package/agent/tools/skill_usage.py +52 -4
  921. package/agent/tools/skills_hub.py +184 -44
  922. package/agent/tools/skills_sync.py +232 -5
  923. package/agent/tools/skills_tool.py +125 -11
  924. package/agent/tools/terminal_tool.py +148 -26
  925. package/agent/tools/tirith_security.py +2 -0
  926. package/agent/tools/todo_tool.py +32 -1
  927. package/agent/tools/transcription_tools.py +13 -5
  928. package/agent/tools/tts_tool.py +332 -38
  929. package/agent/tools/url_safety.py +52 -1
  930. package/agent/tools/vision_tools.py +124 -39
  931. package/agent/tools/voice_mode.py +4 -3
  932. package/agent/tools/web_tools.py +45 -15
  933. package/agent/tools/write_approval.py +493 -0
  934. package/agent/toolsets.py +34 -10
  935. package/agent/trajectory_compressor.py +81 -10
  936. package/agent/tui_gateway/entry.py +43 -6
  937. package/agent/tui_gateway/server.py +3335 -330
  938. package/agent/tui_gateway/slash_worker.py +61 -0
  939. package/agent/tui_gateway/ws.py +67 -9
  940. package/agent/ui-tui/eslint.config.mjs +0 -4
  941. package/agent/ui-tui/package.json +6 -6
  942. package/agent/ui-tui/packages/hermes-ink/package.json +1 -1
  943. package/agent/ui-tui/packages/hermes-ink/src/ink/app-mouse.test.ts +34 -1
  944. package/agent/ui-tui/packages/hermes-ink/src/ink/app-rawmode-mouse.test.ts +91 -0
  945. package/agent/ui-tui/packages/hermes-ink/src/ink/components/App.tsx +35 -2
  946. package/agent/ui-tui/packages/hermes-ink/src/ink/events/input-event.ts +4 -11
  947. package/agent/ui-tui/packages/hermes-ink/src/ink/parse-keypress.test.ts +23 -57
  948. package/agent/ui-tui/packages/hermes-ink/src/ink/parse-keypress.ts +11 -135
  949. package/agent/ui-tui/packages/hermes-ink/src/ink/termio/tokenize.test.ts +185 -0
  950. package/agent/ui-tui/packages/hermes-ink/src/ink/termio/tokenize.ts +37 -3
  951. package/agent/ui-tui/packages/hermes-ink/src/utils/execFileNoThrow.ts +5 -5
  952. package/agent/ui-tui/src/__tests__/appChromeStatusRule.test.tsx +217 -0
  953. package/agent/ui-tui/src/__tests__/appChromeStatusRuleDevCredits.test.tsx +73 -0
  954. package/agent/ui-tui/src/__tests__/approvalAction.test.ts +11 -0
  955. package/agent/ui-tui/src/__tests__/billingCommand.test.ts +301 -0
  956. package/agent/ui-tui/src/__tests__/blockLayout.test.ts +122 -0
  957. package/agent/ui-tui/src/__tests__/brandingMcpCount.test.ts +111 -0
  958. package/agent/ui-tui/src/__tests__/completionApply.test.ts +51 -0
  959. package/agent/ui-tui/src/__tests__/createGatewayEventHandler.test.ts +487 -2
  960. package/agent/ui-tui/src/__tests__/createSlashHandler.test.ts +54 -0
  961. package/agent/ui-tui/src/__tests__/creditsCommand.test.ts +144 -0
  962. package/agent/ui-tui/src/__tests__/gatewayClient.test.ts +120 -99
  963. package/agent/ui-tui/src/__tests__/gracefulExit.test.ts +11 -0
  964. package/agent/ui-tui/src/__tests__/memoryMonitor.test.ts +102 -0
  965. package/agent/ui-tui/src/__tests__/paths.test.ts +41 -1
  966. package/agent/ui-tui/src/__tests__/terminalModes.test.ts +22 -0
  967. package/agent/ui-tui/src/__tests__/text.test.ts +23 -0
  968. package/agent/ui-tui/src/__tests__/textInputFastEcho.test.ts +37 -0
  969. package/agent/ui-tui/src/__tests__/turnControllerNotice.test.ts +43 -0
  970. package/agent/ui-tui/src/__tests__/useInputHandlers.test.ts +38 -1
  971. package/agent/ui-tui/src/__tests__/virtualHeights.test.ts +8 -0
  972. package/agent/ui-tui/src/app/createGatewayEventHandler.ts +102 -7
  973. package/agent/ui-tui/src/app/interfaces.ts +64 -1
  974. package/agent/ui-tui/src/app/overlayStore.ts +18 -2
  975. package/agent/ui-tui/src/app/slash/commands/billing.ts +332 -0
  976. package/agent/ui-tui/src/app/slash/commands/core.ts +31 -2
  977. package/agent/ui-tui/src/app/slash/commands/credits.ts +57 -0
  978. package/agent/ui-tui/src/app/slash/commands/ops.ts +28 -0
  979. package/agent/ui-tui/src/app/slash/commands/session.ts +32 -4
  980. package/agent/ui-tui/src/app/slash/registry.ts +4 -0
  981. package/agent/ui-tui/src/app/turnController.ts +145 -2
  982. package/agent/ui-tui/src/app/uiStore.ts +2 -0
  983. package/agent/ui-tui/src/app/useInputHandlers.ts +42 -4
  984. package/agent/ui-tui/src/app/useMainApp.ts +54 -8
  985. package/agent/ui-tui/src/app/useSessionLifecycle.ts +40 -31
  986. package/agent/ui-tui/src/app/useSubmission.ts +23 -31
  987. package/agent/ui-tui/src/components/appChrome.tsx +112 -5
  988. package/agent/ui-tui/src/components/appLayout.tsx +9 -0
  989. package/agent/ui-tui/src/components/appOverlays.tsx +25 -1
  990. package/agent/ui-tui/src/components/billingOverlay.tsx +684 -0
  991. package/agent/ui-tui/src/components/branding.tsx +15 -3
  992. package/agent/ui-tui/src/components/messageLine.tsx +25 -3
  993. package/agent/ui-tui/src/components/pluginsHub.tsx +238 -0
  994. package/agent/ui-tui/src/components/prompts.tsx +31 -17
  995. package/agent/ui-tui/src/components/streamingAssistant.tsx +63 -55
  996. package/agent/ui-tui/src/components/textInput.tsx +16 -0
  997. package/agent/ui-tui/src/config/env.ts +12 -0
  998. package/agent/ui-tui/src/config/limits.ts +13 -0
  999. package/agent/ui-tui/src/domain/blockLayout.ts +146 -0
  1000. package/agent/ui-tui/src/domain/paths.ts +24 -0
  1001. package/agent/ui-tui/src/domain/slash.ts +40 -0
  1002. package/agent/ui-tui/src/entry.tsx +35 -4
  1003. package/agent/ui-tui/src/gatewayClient.ts +22 -10
  1004. package/agent/ui-tui/src/gatewayTypes.ts +130 -1
  1005. package/agent/ui-tui/src/lib/gracefulExit.ts +24 -4
  1006. package/agent/ui-tui/src/lib/memory.test.ts +162 -0
  1007. package/agent/ui-tui/src/lib/memory.ts +60 -1
  1008. package/agent/ui-tui/src/lib/memoryMonitor.ts +79 -4
  1009. package/agent/ui-tui/src/lib/osc52.ts +1 -1
  1010. package/agent/ui-tui/src/lib/text.test.ts +32 -1
  1011. package/agent/ui-tui/src/lib/text.ts +29 -2
  1012. package/agent/ui-tui/src/lib/virtualHeights.ts +13 -0
  1013. package/agent/ui-tui/src/types.ts +5 -0
  1014. package/agent/ui-tui/tsconfig.build.json +0 -1
  1015. package/agent/ui-tui/tsconfig.json +2 -1
  1016. package/agent/utils.py +66 -2
  1017. package/agent/uv.lock +300 -684
  1018. package/agent/web/index.html +2 -2
  1019. package/agent/web/package.json +11 -6
  1020. package/agent/web/public/claw-bg.webp +0 -0
  1021. package/agent/web/public/claw-logo.webp +0 -0
  1022. package/agent/web/src/App.tsx +138 -48
  1023. package/agent/web/src/components/AutomationBlueprints.tsx +225 -0
  1024. package/agent/web/src/components/Backdrop.tsx +15 -0
  1025. package/agent/web/src/components/ChatSessionList.tsx +260 -0
  1026. package/agent/web/src/components/ChatSidebar.tsx +262 -78
  1027. package/agent/web/src/components/ConfirmDialog.tsx +122 -0
  1028. package/agent/web/src/components/ModelPickerDialog.tsx +111 -16
  1029. package/agent/web/src/components/ModelReloadConfirm.tsx +40 -0
  1030. package/agent/web/src/components/ProfileScopeBanner.tsx +30 -0
  1031. package/agent/web/src/components/ProfileSwitcher.tsx +67 -0
  1032. package/agent/web/src/components/ReasoningPicker.tsx +167 -0
  1033. package/agent/web/src/components/SkillEditorDialog.tsx +215 -0
  1034. package/agent/web/src/components/ThemeSwitcher.tsx +119 -4
  1035. package/agent/web/src/components/ToolsetConfigDrawer.tsx +457 -0
  1036. package/agent/web/src/contexts/PageHeaderProvider.tsx +7 -4
  1037. package/agent/web/src/contexts/ProfileProvider.tsx +137 -0
  1038. package/agent/web/src/contexts/SystemActions.tsx +6 -8
  1039. package/agent/web/src/contexts/profile-context.ts +19 -0
  1040. package/agent/web/src/contexts/useProfileScope.ts +6 -0
  1041. package/agent/web/src/i18n/af.ts +5 -4
  1042. package/agent/web/src/i18n/de.ts +5 -4
  1043. package/agent/web/src/i18n/en.ts +58 -4
  1044. package/agent/web/src/i18n/es.ts +5 -3
  1045. package/agent/web/src/i18n/fr.ts +5 -3
  1046. package/agent/web/src/i18n/ga.ts +5 -4
  1047. package/agent/web/src/i18n/hu.ts +5 -4
  1048. package/agent/web/src/i18n/it.ts +5 -4
  1049. package/agent/web/src/i18n/ja.ts +5 -4
  1050. package/agent/web/src/i18n/ko.ts +5 -4
  1051. package/agent/web/src/i18n/pt.ts +5 -3
  1052. package/agent/web/src/i18n/ru.ts +5 -4
  1053. package/agent/web/src/i18n/tr.ts +5 -4
  1054. package/agent/web/src/i18n/types.ts +59 -1
  1055. package/agent/web/src/i18n/uk.ts +5 -3
  1056. package/agent/web/src/i18n/zh-hant.ts +5 -4
  1057. package/agent/web/src/i18n/zh.ts +5 -4
  1058. package/agent/web/src/index.css +2 -2
  1059. package/agent/web/src/lib/api.ts +819 -52
  1060. package/agent/web/src/lib/dashboard-flags.ts +16 -7
  1061. package/agent/web/src/lib/reasoning-effort.test.ts +48 -0
  1062. package/agent/web/src/lib/reasoning-effort.ts +36 -0
  1063. package/agent/web/src/lib/session-refresh.test.ts +21 -0
  1064. package/agent/web/src/lib/session-refresh.ts +26 -0
  1065. package/agent/web/src/pages/ChannelsPage.tsx +529 -68
  1066. package/agent/web/src/pages/ChatPage.tsx +249 -56
  1067. package/agent/web/src/pages/ConfigPage.tsx +11 -1
  1068. package/agent/web/src/pages/CronPage.tsx +219 -31
  1069. package/agent/web/src/pages/EnvPage.tsx +25 -6
  1070. package/agent/web/src/pages/FilesPage.tsx +525 -0
  1071. package/agent/web/src/pages/McpPage.tsx +80 -3
  1072. package/agent/web/src/pages/ModelsPage.tsx +97 -12
  1073. package/agent/web/src/pages/PluginsPage.tsx +1 -1
  1074. package/agent/web/src/pages/ProfileBuilderPage.tsx +611 -0
  1075. package/agent/web/src/pages/ProfilesPage.tsx +1038 -172
  1076. package/agent/web/src/pages/SessionsPage.tsx +144 -13
  1077. package/agent/web/src/pages/SkillsPage.tsx +851 -70
  1078. package/agent/web/src/pages/SystemPage.tsx +340 -4
  1079. package/agent/web/src/pages/WalletPage.tsx +401 -0
  1080. package/agent/web/src/pages/WebhooksPage.tsx +145 -15
  1081. package/agent/web/src/pages/X402Page.tsx +207 -0
  1082. package/agent/web/src/plugins/registry.ts +28 -11
  1083. package/agent/web/src/plugins/sdk.d.ts +160 -0
  1084. package/agent/web/src/themes/context.tsx +112 -5
  1085. package/agent/web/src/themes/fonts.ts +167 -0
  1086. package/agent/web/src/themes/index.ts +7 -0
  1087. package/agent/web/tsconfig.app.json +0 -1
  1088. package/agent/web/vite.config.ts +1 -8
  1089. package/agent/web/vitest.config.ts +16 -0
  1090. package/package.json +1 -1
  1091. package/agent/apps/desktop/package-lock.json +0 -18363
  1092. package/agent/apps/desktop/src/app/chat/composer/skin-slash-popover.tsx +0 -56
  1093. package/agent/apps/desktop/src/components/assistant-ui/thread-virtualizer.tsx +0 -382
  1094. package/agent/apps/desktop/src/components/assistant-ui/todo-tool.tsx +0 -109
  1095. package/agent/apps/desktop/src/components/chat/generated-image-context.tsx +0 -19
  1096. package/agent/optional-skills/productivity/shop-app/SKILL.md +0 -340
  1097. package/agent/skills/autonomous-ai-agents/kanban-codex-lane/SKILL.md +0 -277
  1098. package/agent/skills/autonomous-ai-agents/kanban-codex-lane/templates/pmb-codex-lane-prompt.md +0 -57
  1099. package/agent/skills/diagramming/DESCRIPTION.md +0 -3
  1100. package/agent/skills/domain/DESCRIPTION.md +0 -24
  1101. package/agent/skills/gifs/DESCRIPTION.md +0 -3
  1102. package/agent/skills/inference-sh/DESCRIPTION.md +0 -19
  1103. package/agent/skills/mcp/DESCRIPTION.md +0 -3
  1104. package/agent/skills/media/spotify/SKILL.md +0 -135
  1105. package/agent/skills/mlops/training/DESCRIPTION.md +0 -3
  1106. package/agent/skills/mlops/vector-databases/DESCRIPTION.md +0 -3
  1107. package/agent/skills/productivity/linear/SKILL.md +0 -380
  1108. package/agent/skills/productivity/linear/scripts/linear_api.py +0 -445
  1109. package/agent/skills/software-development/debugging-hermes-tui-commands/SKILL.md +0 -152
  1110. package/agent/skills/software-development/writing-plans/SKILL.md +0 -297
  1111. package/agent/ui-tui/package-lock.json +0 -7449
  1112. package/agent/ui-tui/packages/hermes-ink/package-lock.json +0 -1289
  1113. package/agent/web/package-lock.json +0 -8887
  1114. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/PORT_NOTES.md +0 -0
  1115. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/SKILL.md +0 -0
  1116. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/prompts/system.md +0 -0
  1117. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/palettes/macaron.md +0 -0
  1118. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/palettes/mono-ink.md +0 -0
  1119. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/palettes/neon.md +0 -0
  1120. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/palettes/warm.md +0 -0
  1121. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/prompt-construction.md +0 -0
  1122. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/style-presets.md +0 -0
  1123. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/blueprint.md +0 -0
  1124. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/chalkboard.md +0 -0
  1125. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/editorial.md +0 -0
  1126. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/elegant.md +0 -0
  1127. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/fantasy-animation.md +0 -0
  1128. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/flat-doodle.md +0 -0
  1129. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/flat.md +0 -0
  1130. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/ink-notes.md +0 -0
  1131. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/intuition-machine.md +0 -0
  1132. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/minimal.md +0 -0
  1133. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/nature.md +0 -0
  1134. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/notion.md +0 -0
  1135. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/pixel-art.md +0 -0
  1136. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/playful.md +0 -0
  1137. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/retro.md +0 -0
  1138. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/scientific.md +0 -0
  1139. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/screen-print.md +0 -0
  1140. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/sketch-notes.md +0 -0
  1141. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/sketch.md +0 -0
  1142. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/vector-illustration.md +0 -0
  1143. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/vintage.md +0 -0
  1144. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/warm.md +0 -0
  1145. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles/watercolor.md +0 -0
  1146. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/styles.md +0 -0
  1147. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/usage.md +0 -0
  1148. /package/agent/{skills → optional-skills}/creative/baoyu-article-illustrator/references/workflow.md +0 -0
  1149. /package/agent/{skills → optional-skills}/creative/baoyu-comic/PORT_NOTES.md +0 -0
  1150. /package/agent/{skills → optional-skills}/creative/baoyu-comic/SKILL.md +0 -0
  1151. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/analysis-framework.md +0 -0
  1152. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/chalk.md +0 -0
  1153. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/ink-brush.md +0 -0
  1154. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/ligne-claire.md +0 -0
  1155. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/manga.md +0 -0
  1156. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/minimalist.md +0 -0
  1157. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/art-styles/realistic.md +0 -0
  1158. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/auto-selection.md +0 -0
  1159. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/base-prompt.md +0 -0
  1160. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/character-template.md +0 -0
  1161. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/cinematic.md +0 -0
  1162. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/dense.md +0 -0
  1163. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/four-panel.md +0 -0
  1164. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/mixed.md +0 -0
  1165. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/splash.md +0 -0
  1166. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/standard.md +0 -0
  1167. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/layouts/webtoon.md +0 -0
  1168. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/ohmsha-guide.md +0 -0
  1169. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/partial-workflows.md +0 -0
  1170. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/presets/concept-story.md +0 -0
  1171. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/presets/four-panel.md +0 -0
  1172. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/presets/ohmsha.md +0 -0
  1173. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/presets/shoujo.md +0 -0
  1174. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/presets/wuxia.md +0 -0
  1175. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/storyboard-template.md +0 -0
  1176. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/action.md +0 -0
  1177. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/dramatic.md +0 -0
  1178. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/energetic.md +0 -0
  1179. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/neutral.md +0 -0
  1180. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/romantic.md +0 -0
  1181. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/vintage.md +0 -0
  1182. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/tones/warm.md +0 -0
  1183. /package/agent/{skills → optional-skills}/creative/baoyu-comic/references/workflow.md +0 -0
  1184. /package/agent/{skills → optional-skills}/creative/creative-ideation/SKILL.md +0 -0
  1185. /package/agent/{skills → optional-skills}/creative/creative-ideation/references/full-prompt-library.md +0 -0
  1186. /package/agent/{skills → optional-skills}/creative/pixel-art/ATTRIBUTION.md +0 -0
  1187. /package/agent/{skills → optional-skills}/creative/pixel-art/SKILL.md +0 -0
  1188. /package/agent/{skills → optional-skills}/creative/pixel-art/references/palettes.md +0 -0
  1189. /package/agent/{skills → optional-skills}/creative/pixel-art/scripts/__init__.py +0 -0
  1190. /package/agent/{skills → optional-skills}/creative/pixel-art/scripts/palettes.py +0 -0
  1191. /package/agent/{skills → optional-skills}/creative/pixel-art/scripts/pixel_art.py +0 -0
  1192. /package/agent/{skills → optional-skills}/creative/pixel-art/scripts/pixel_art_video.py +0 -0
  1193. /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/SKILL.md +0 -0
  1194. /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/references/analysis-modules.md +0 -0
  1195. /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/references/methods-guide.md +0 -0
  1196. /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/templates/abliteration-config.yaml +0 -0
  1197. /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/templates/analysis-study.yaml +0 -0
  1198. /package/agent/{skills/mlops/inference → optional-skills/mlops}/obliteratus/templates/batch-abliteration.yaml +0 -0
  1199. /package/agent/{skills → optional-skills}/mlops/research/DESCRIPTION.md +0 -0
  1200. /package/agent/{skills → optional-skills}/mlops/research/dspy/SKILL.md +0 -0
  1201. /package/agent/{skills → optional-skills}/mlops/research/dspy/references/examples.md +0 -0
  1202. /package/agent/{skills → optional-skills}/mlops/research/dspy/references/modules.md +0 -0
  1203. /package/agent/{skills → optional-skills}/mlops/research/dspy/references/optimizers.md +0 -0
  1204. /package/agent/{skills/red-teaming → optional-skills/security}/godmode/references/jailbreak-templates.md +0 -0
  1205. /package/agent/{skills/red-teaming → optional-skills/security}/godmode/references/refusal-detection.md +0 -0
  1206. /package/agent/{skills/red-teaming → optional-skills/security}/godmode/scripts/godmode_race.py +0 -0
  1207. /package/agent/{skills/red-teaming → optional-skills/security}/godmode/scripts/load_godmode.py +0 -0
  1208. /package/agent/{skills/red-teaming → optional-skills/security}/godmode/scripts/parseltongue.py +0 -0
  1209. /package/agent/{skills/red-teaming → optional-skills/security}/godmode/templates/prefill-subtle.json +0 -0
  1210. /package/agent/{skills/red-teaming → optional-skills/security}/godmode/templates/prefill.json +0 -0
  1211. /package/agent/{skills → optional-skills}/software-development/subagent-driven-development/references/context-budget-discipline.md +0 -0
  1212. /package/agent/{skills → optional-skills}/software-development/subagent-driven-development/references/gates-taxonomy.md +0 -0
@@ -14,15 +14,16 @@ import {
14
14
  useSortable,
15
15
  verticalListSortingStrategy
16
16
  } from '@dnd-kit/sortable'
17
- import { CSS } from '@dnd-kit/utilities'
18
17
  import { useStore } from '@nanostores/react'
19
18
  import type * as React from 'react'
20
- import { useEffect, useMemo, useState } from 'react'
19
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
21
20
 
21
+ import { PlatformAvatar } from '@/app/messaging/platform-icon'
22
22
  import { Button } from '@/components/ui/button'
23
23
  import { Codicon } from '@/components/ui/codicon'
24
24
  import { DisclosureCaret } from '@/components/ui/disclosure-caret'
25
25
  import { KbdGroup } from '@/components/ui/kbd'
26
+ import { SearchField } from '@/components/ui/search-field'
26
27
  import {
27
28
  Sidebar,
28
29
  SidebarContent,
@@ -33,24 +34,60 @@ import {
33
34
  SidebarMenuItem
34
35
  } from '@/components/ui/sidebar'
35
36
  import { Skeleton } from '@/components/ui/skeleton'
37
+ import { Tip } from '@/components/ui/tooltip'
36
38
  import { searchSessions, type SessionInfo, type SessionSearchResult } from '@/hermes'
39
+ import { useWorktreeInfo } from '@/hooks/use-worktree-info'
40
+ import { useI18n } from '@/i18n'
41
+ import { comboTokens } from '@/lib/keybinds/combo'
42
+ import { profileColor } from '@/lib/profile-color'
43
+ import { sessionMatchesSearch } from '@/lib/session-search'
44
+ import { normalizeSessionSource, sessionSourceLabel } from '@/lib/session-source'
37
45
  import { cn } from '@/lib/utils'
46
+ import { $cronJobs } from '@/store/cron'
38
47
  import {
48
+ $panesFlipped,
39
49
  $pinnedSessionIds,
40
50
  $sidebarAgentsGrouped,
51
+ $sidebarCronOpen,
52
+ $sidebarMessagingOpenIds,
41
53
  $sidebarOpen,
54
+ $sidebarOverlayMounted,
42
55
  $sidebarPinsOpen,
43
56
  $sidebarRecentsOpen,
57
+ $sidebarSessionOrderIds,
58
+ $sidebarSessionOrderManual,
59
+ $sidebarWorkspaceOrderIds,
60
+ $sidebarWorkspaceParentOrderIds,
44
61
  pinSession,
45
- reorderPinnedSession,
62
+ SESSION_SEARCH_FOCUS_EVENT,
63
+ setPinnedSessionOrder,
46
64
  setSidebarAgentsGrouped,
65
+ setSidebarCronOpen,
47
66
  setSidebarPinsOpen,
48
67
  setSidebarRecentsOpen,
68
+ setSidebarSessionOrderIds,
69
+ setSidebarSessionOrderManual,
70
+ setSidebarWorkspaceOrderIds,
71
+ setSidebarWorkspaceParentOrderIds,
49
72
  SIDEBAR_SESSIONS_PAGE_SIZE,
73
+ toggleSidebarMessagingOpen,
50
74
  unpinSession
51
75
  } from '@/store/layout'
52
76
  import {
77
+ $newChatProfile,
78
+ $profiles,
79
+ $profileScope,
80
+ ALL_PROFILES,
81
+ newSessionInProfile,
82
+ normalizeProfileKey
83
+ } from '@/store/profile'
84
+ import {
85
+ $cronSessions,
86
+ $messagingPlatformTotals,
87
+ $messagingSessions,
88
+ $messagingTruncated,
53
89
  $selectedStoredSessionId,
90
+ $sessionProfileTotals,
54
91
  $sessions,
55
92
  $sessionsLoading,
56
93
  $sessionsTotal,
@@ -62,33 +99,103 @@ import { type AppView, ARTIFACTS_ROUTE, MESSAGING_ROUTE, SKILLS_ROUTE } from '..
62
99
  import { SidebarPanelLabel } from '../../shell/sidebar-label'
63
100
  import type { SidebarNavItem } from '../../types'
64
101
 
102
+ import { SidebarCronJobsSection } from './cron-jobs-section'
103
+ import { SidebarLoadMoreRow } from './load-more-row'
104
+ import { resolveManualSessionOrderIds } from './order'
105
+ import { ProfileRail } from './profile-switcher'
65
106
  import { SidebarSessionRow } from './session-row'
66
107
  import { VirtualSessionList } from './virtual-session-list'
108
+ import { type SidebarSessionGroup, type SidebarWorkspaceTree, workspaceTreeFor } from './workspace-groups'
67
109
 
68
110
  const VIRTUALIZE_THRESHOLD = 25
69
111
 
112
+ // Non-session groups (messaging platforms) stay compact: show a few rows up
113
+ // front, reveal more in larger steps on demand. Keeps a busy platform from
114
+ // dominating the sidebar before the user asks to see it.
115
+ const NON_SESSION_INITIAL_ROWS = 3
116
+ const NON_SESSION_LOAD_STEP = 10
117
+
118
+ const NEW_SESSION_KBD = comboTokens('mod+n')
119
+
70
120
  const SIDEBAR_NAV: SidebarNavItem[] = [
71
121
  {
72
122
  id: 'new-session',
73
- label: 'New session',
123
+ label: '',
74
124
  icon: props => <Codicon name="robot" {...props} />,
75
125
  action: 'new-session'
76
126
  },
77
127
  {
78
128
  id: 'skills',
79
- label: 'Skills & Tools',
129
+ label: '',
80
130
  icon: props => <Codicon name="symbol-misc" {...props} />,
81
131
  route: SKILLS_ROUTE
82
132
  },
83
- { id: 'messaging', label: 'Messaging', icon: props => <Codicon name="comment" {...props} />, route: MESSAGING_ROUTE },
84
- { id: 'artifacts', label: 'Artifacts', icon: props => <Codicon name="files" {...props} />, route: ARTIFACTS_ROUTE }
133
+ { id: 'messaging', label: '', icon: props => <Codicon name="comment" {...props} />, route: MESSAGING_ROUTE },
134
+ { id: 'artifacts', label: '', icon: props => <Codicon name="files" {...props} />, route: ARTIFACTS_ROUTE }
85
135
  ]
86
136
 
87
137
  const WORKSPACE_PAGE = 5
88
- const WS_ID_PREFIX = 'workspace:'
138
+ // ALL-profiles view: show only the latest N per profile up front to keep the
139
+ // unified list scannable, then reveal/fetch more in N-sized steps on demand.
140
+ const PROFILE_INITIAL_PAGE = 5
141
+ // Two modes via the `compact` height variant (styles.css):
142
+ // tall → each section is shrink-0, capped, its own scroller; Sessions is flex-1.
143
+ // compact → COMPACT_FLAT drops the caps so the whole stack scrolls as one.
144
+ // Sections stay shrink-0 so none can be squeezed below its content and bleed onto
145
+ // the next — the flexbox `min-height: auto` overlap trap that caused the bug.
146
+ const COMPACT_FLAT = 'compact:max-h-none compact:overflow-visible'
147
+
148
+ // Vertical scroll only — never a horizontal bar from glow bleed, long titles, etc.
149
+ const SCROLL_Y = 'overflow-y-auto overflow-x-hidden overscroll-contain'
150
+
151
+ // A non-session group's scroll body: own scroller when tall, flattened when compact.
152
+ const GROUP_BODY = cn(SCROLL_Y, COMPACT_FLAT)
153
+
154
+ // Sidebar reordering is a strictly vertical list. The dragged item's transform
155
+ // is rendered Y-only in useSortableBindings (no x, no scale); this just stops
156
+ // dnd-kit's auto-scroll from dragging the rail — or the window — sideways when
157
+ // the pointer nears an edge, killing the horizontal "drag to valhalla".
158
+ const reorderAutoScroll = { threshold: { x: 0, y: 0.2 } }
159
+
160
+ // One self-contained, nesting-safe reorderable list. It owns its DndContext, so a
161
+ // drag only ever collides with THIS list's own items — drop it at any depth (repos,
162
+ // worktrees, sessions) and reordering "just works" without leaking into the lists
163
+ // around or inside it. Pair each item with useSortableBindings(id); the list reports
164
+ // the new id order and the caller persists it. This is the single generic primitive
165
+ // behind every reorderable surface in the sidebar.
166
+ function ReorderableList({
167
+ children,
168
+ ids,
169
+ onReorder,
170
+ sensors
171
+ }: {
172
+ children: React.ReactNode
173
+ ids: string[]
174
+ onReorder: (ids: string[]) => void
175
+ sensors?: ReturnType<typeof useSensors>
176
+ }) {
177
+ const handleDragEnd = ({ active, over }: DragEndEvent) => {
178
+ if (!over || active.id === over.id) {
179
+ return
180
+ }
181
+
182
+ const from = ids.indexOf(String(active.id))
183
+ const to = ids.indexOf(String(over.id))
184
+
185
+ if (from >= 0 && to >= 0) {
186
+ onReorder(arrayMove(ids, from, to))
187
+ }
188
+ }
189
+
190
+ return (
191
+ <DndContext autoScroll={reorderAutoScroll} collisionDetection={closestCenter} onDragEnd={handleDragEnd} sensors={sensors}>
192
+ <SortableContext items={ids} strategy={verticalListSortingStrategy}>
193
+ {children}
194
+ </SortableContext>
195
+ </DndContext>
196
+ )
197
+ }
89
198
 
90
- const wsId = (id: string) => `${WS_ID_PREFIX}${id}`
91
- const parseWsId = (id: string) => (id.startsWith(WS_ID_PREFIX) ? id.slice(WS_ID_PREFIX.length) : null)
92
199
  const countLabel = (loaded: number, total: number) => (total > loaded ? `${loaded}/${total}` : String(loaded))
93
200
  const sessionTime = (s: SessionInfo) => s.last_active || s.started_at || 0
94
201
 
@@ -99,32 +206,51 @@ function orderByIds<T>(items: T[], getId: (item: T) => string, orderIds: string[
99
206
 
100
207
  const byId = new Map(items.map(item => [getId(item), item]))
101
208
  const seen = new Set<string>()
102
- const out: T[] = []
209
+ const ordered: T[] = []
103
210
 
104
211
  for (const id of orderIds) {
105
212
  const item = byId.get(id)
106
213
 
107
214
  if (item) {
108
- out.push(item)
215
+ ordered.push(item)
109
216
  seen.add(id)
110
217
  }
111
218
  }
112
219
 
113
- for (const item of items) {
114
- if (!seen.has(getId(item))) {
115
- out.push(item)
116
- }
220
+ // Items missing from the persisted order are new since it was last
221
+ // reconciled. Callers pass recency-sorted lists (newest first), so surface
222
+ // these at the TOP instead of burying them beneath the saved order —
223
+ // otherwise a brand-new session sinks to the bottom of the sidebar and reads
224
+ // as "my latest session never showed up".
225
+ const fresh = items.filter(item => !seen.has(getId(item)))
226
+
227
+ return fresh.length ? [...fresh, ...ordered] : ordered
228
+ }
229
+
230
+ function reconcileOrderIds(currentIds: string[], orderIds: string[]): string[] {
231
+ if (!currentIds.length) {
232
+ return []
117
233
  }
118
234
 
119
- return out
235
+ if (!orderIds.length) {
236
+ return currentIds
237
+ }
238
+
239
+ const current = new Set(currentIds)
240
+ const retained = orderIds.filter(id => current.has(id))
241
+ const retainedSet = new Set(retained)
242
+
243
+ // New ids (absent from the saved order) are the newest sessions/groups; keep
244
+ // them ahead of the persisted order so fresh activity surfaces at the top of
245
+ // the sidebar rather than being appended to the bottom.
246
+ const fresh = currentIds.filter(id => !retainedSet.has(id))
247
+
248
+ return [...fresh, ...retained]
120
249
  }
121
250
 
122
- const baseName = (path: string) =>
123
- path
124
- .replace(/[/\\]+$/, '')
125
- .split(/[/\\]/)
126
- .filter(Boolean)
127
- .pop()
251
+ function sameIds(left: string[], right: string[]) {
252
+ return left.length === right.length && left.every((item, index) => item === right[index])
253
+ }
128
254
 
129
255
  // FTS results cover sessions that aren't in the loaded page; synthesize a
130
256
  // minimal SessionInfo so they render in the same row component (resume works
@@ -137,6 +263,7 @@ function searchResultToSession(result: SessionSearchResult): SessionInfo {
137
263
  cwd: null,
138
264
  ended_at: null,
139
265
  id: result.session_id,
266
+ _lineage_root_id: result.lineage_root ?? null,
140
267
  input_tokens: 0,
141
268
  is_active: false,
142
269
  last_active: ts,
@@ -151,30 +278,6 @@ function searchResultToSession(result: SessionSearchResult): SessionInfo {
151
278
  }
152
279
  }
153
280
 
154
- function workspaceGroupsFor(sessions: SessionInfo[]): SidebarSessionGroup[] {
155
- const groups = new Map<string, SidebarSessionGroup>()
156
-
157
- for (const session of sessions) {
158
- const path = session.cwd?.trim() || ''
159
- const id = path || '__no_workspace__'
160
- const label = baseName(path) || path || 'No workspace'
161
-
162
- const group = groups.get(id) ?? { id, label, path: path || null, sessions: [] }
163
- group.sessions.push(session)
164
- groups.set(id, group)
165
- }
166
-
167
- // Groups keep recency order (Map insertion = first-seen in the recency-sorted
168
- // input, so an active project floats up), but rows *within* a group sort by
169
- // creation time so they don't reshuffle every time a message lands — keeps
170
- // muscle memory intact.
171
- for (const group of groups.values()) {
172
- group.sessions.sort((a, b) => b.started_at - a.started_at)
173
- }
174
-
175
- return [...groups.values()]
176
- }
177
-
178
281
  function useSortableBindings(id: string) {
179
282
  const { attributes, isDragging, listeners, setNodeRef, transform, transition } = useSortable({ id })
180
283
 
@@ -183,7 +286,14 @@ function useSortableBindings(id: string) {
183
286
  dragHandleProps: { ...attributes, ...listeners },
184
287
  ref: setNodeRef,
185
288
  reorderable: true as const,
186
- style: { transform: CSS.Transform.toString(transform), transition }
289
+ style: {
290
+ // Uniform vertical list: only ever translate on Y. Ignoring x and the
291
+ // scaleX/scaleY that CSS.Transform.toString would emit keeps a dragged
292
+ // group/row from drifting sideways or morphing its size mid-drag.
293
+ transform: transform ? `translate3d(0px, ${transform.y}px, 0)` : undefined,
294
+ transition: isDragging ? undefined : transition,
295
+ willChange: isDragging ? 'transform' : undefined
296
+ }
187
297
  }
188
298
  }
189
299
 
@@ -191,37 +301,104 @@ interface ChatSidebarProps extends React.ComponentProps<typeof Sidebar> {
191
301
  currentView: AppView
192
302
  onNavigate: (item: SidebarNavItem) => void
193
303
  onLoadMoreSessions: () => void
304
+ onLoadMoreProfileSessions?: (profile: string) => Promise<void> | void
305
+ onLoadMoreMessaging?: (platform: string) => Promise<void> | void
194
306
  onResumeSession: (sessionId: string) => void
195
307
  onDeleteSession: (sessionId: string) => void
196
308
  onArchiveSession: (sessionId: string) => void
197
309
  onNewSessionInWorkspace: (path: null | string) => void
310
+ onManageCronJob: (jobId: string) => void
311
+ onTriggerCronJob: (jobId: string) => void
198
312
  }
199
313
 
200
314
  export function ChatSidebar({
201
315
  currentView,
202
316
  onNavigate,
203
317
  onLoadMoreSessions,
318
+ onLoadMoreProfileSessions,
319
+ onLoadMoreMessaging,
204
320
  onResumeSession,
205
321
  onDeleteSession,
206
322
  onArchiveSession,
207
- onNewSessionInWorkspace
323
+ onNewSessionInWorkspace,
324
+ onManageCronJob,
325
+ onTriggerCronJob
208
326
  }: ChatSidebarProps) {
327
+ const { t } = useI18n()
328
+ const s = t.sidebar
209
329
  const sidebarOpen = useStore($sidebarOpen)
330
+ // Collapsed-but-overlay-mounted → render the full sidebar, not just the nav rail.
331
+ const overlayMounted = useStore($sidebarOverlayMounted)
332
+ const contentVisible = sidebarOpen || overlayMounted
333
+ const panesFlipped = useStore($panesFlipped)
210
334
  const agentsGrouped = useStore($sidebarAgentsGrouped)
211
335
  const pinnedSessionIds = useStore($pinnedSessionIds)
212
336
  const pinsOpen = useStore($sidebarPinsOpen)
213
337
  const agentsOpen = useStore($sidebarRecentsOpen)
338
+ const cronOpen = useStore($sidebarCronOpen)
214
339
  const selectedSessionId = useStore($selectedStoredSessionId)
215
340
  const sessions = useStore($sessions)
341
+ const cronSessions = useStore($cronSessions)
342
+ const cronJobs = useStore($cronJobs)
343
+ const messagingSessions = useStore($messagingSessions)
344
+ const messagingPlatformTotals = useStore($messagingPlatformTotals)
345
+ const messagingTruncated = useStore($messagingTruncated)
216
346
  const sessionsLoading = useStore($sessionsLoading)
217
347
  const sessionsTotal = useStore($sessionsTotal)
348
+ const sessionProfileTotals = useStore($sessionProfileTotals)
218
349
  const workingSessionIds = useStore($workingSessionIds)
219
- const [agentOrderIds, setAgentOrderIds] = useState<string[]>([])
220
- const [workspaceOrderIds, setWorkspaceOrderIds] = useState<string[]>([])
350
+ const profiles = useStore($profiles)
351
+ const profileScope = useStore($profileScope)
352
+ // Only surface the profile switcher when more than one profile exists, so
353
+ // single-profile users see the unchanged sidebar.
354
+ const multiProfile = profiles.length > 1
355
+ // Gate ALL-profiles grouping on multiProfile too: if a user drops back to one
356
+ // profile while scope is still ALL (persisted), the rail is hidden and they'd
357
+ // otherwise be stuck in the grouped view with no way out.
358
+ const showAllProfiles = multiProfile && profileScope === ALL_PROFILES
359
+ const agentOrderIds = useStore($sidebarSessionOrderIds)
360
+ const agentOrderManual = useStore($sidebarSessionOrderManual)
361
+ const workspaceOrderIds = useStore($sidebarWorkspaceOrderIds)
362
+ const workspaceParentOrderIds = useStore($sidebarWorkspaceParentOrderIds)
221
363
  const [searchQuery, setSearchQuery] = useState('')
222
364
  const [serverMatches, setServerMatches] = useState<SessionSearchResult[]>([])
365
+ const [newSessionKbdFlash, setNewSessionKbdFlash] = useState(false)
366
+ const [profileLoadMorePending, setProfileLoadMorePending] = useState<Record<string, boolean>>({})
367
+ const [messagingLoadMorePending, setMessagingLoadMorePending] = useState<Record<string, boolean>>({})
368
+ const messagingOpenIds = useStore($sidebarMessagingOpenIds)
369
+ // Per-platform count of rows currently revealed (starts at NON_SESSION_INITIAL_ROWS).
370
+ const [messagingVisible, setMessagingVisible] = useState<Record<string, number>>({})
371
+ const searchInputRef = useRef<HTMLInputElement>(null)
223
372
  const trimmedQuery = searchQuery.trim()
224
373
 
374
+ // Hotkey (session.focusSearch) → focus the field once it's mounted.
375
+ useEffect(() => {
376
+ const onFocus = () => searchInputRef.current?.focus({ preventScroll: true })
377
+
378
+ window.addEventListener(SESSION_SEARCH_FOCUS_EVENT, onFocus)
379
+
380
+ return () => window.removeEventListener(SESSION_SEARCH_FOCUS_EVENT, onFocus)
381
+ }, [])
382
+
383
+ // Flash the ⌘N hint full-opacity (no transition) for the press, so hitting
384
+ // the shortcut visibly pings its affordance in the sidebar.
385
+ useEffect(() => {
386
+ let timeout: ReturnType<typeof setTimeout> | undefined
387
+
388
+ const onShortcut = () => {
389
+ setNewSessionKbdFlash(true)
390
+ clearTimeout(timeout)
391
+ timeout = setTimeout(() => setNewSessionKbdFlash(false), 140)
392
+ }
393
+
394
+ window.addEventListener('hermes:new-session-shortcut', onShortcut)
395
+
396
+ return () => {
397
+ window.removeEventListener('hermes:new-session-shortcut', onShortcut)
398
+ clearTimeout(timeout)
399
+ }
400
+ }, [])
401
+
225
402
  const activeSidebarSessionId = currentView === 'chat' ? selectedSessionId : null
226
403
 
227
404
  const dndSensors = useSensors(
@@ -229,7 +406,22 @@ export function ChatSidebar({
229
406
  useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })
230
407
  )
231
408
 
232
- const sortedSessions = useMemo(() => [...sessions].sort((a, b) => sessionTime(b) - sessionTime(a)), [sessions])
409
+ // Profile scope = the "workspace switcher" context. Concrete scope shows only
410
+ // that profile's sessions (clean rows, no per-row tags); ALL fans every
411
+ // profile in, grouped by profile below. Single-profile users land here with
412
+ // scope === their only profile, so nothing is filtered out.
413
+ const visibleSessions = useMemo(
414
+ () => (showAllProfiles ? sessions : sessions.filter(s => normalizeProfileKey(s.profile) === profileScope)),
415
+ [sessions, showAllProfiles, profileScope]
416
+ )
417
+
418
+ // Agent session order is pinned to creation time (started_at), NOT activity —
419
+ // a new message must never float a session to the top. Position only changes
420
+ // for a brand-new session or an explicit manual drag (agentOrderIds).
421
+ const sortedSessions = useMemo(
422
+ () => [...visibleSessions].sort((a, b) => (b.started_at || 0) - (a.started_at || 0)),
423
+ [visibleSessions]
424
+ )
233
425
 
234
426
  const workingSessionIdSet = useMemo(() => new Set(workingSessionIds), [workingSessionIds])
235
427
 
@@ -238,7 +430,10 @@ export function ChatSidebar({
238
430
  const sessionByAnyId = useMemo(() => {
239
431
  const map = new Map<string, SessionInfo>()
240
432
 
241
- for (const s of sessions) {
433
+ // Cron sessions are listed separately but can still be pinned, so index
434
+ // them too — otherwise a pinned cron job can't resolve into the Pinned
435
+ // section. Recents take precedence on id collisions (set last).
436
+ for (const s of [...cronSessions, ...visibleSessions]) {
242
437
  map.set(s.id, s)
243
438
 
244
439
  if (s._lineage_root_id && !map.has(s._lineage_root_id)) {
@@ -247,7 +442,7 @@ export function ChatSidebar({
247
442
  }
248
443
 
249
444
  return map
250
- }, [sessions])
445
+ }, [visibleSessions, cronSessions])
251
446
 
252
447
  const pinnedSessions = useMemo(() => {
253
448
  const seen = new Set<string>()
@@ -300,11 +495,10 @@ export function ChatSidebar({
300
495
  return []
301
496
  }
302
497
 
303
- const needle = trimmedQuery.toLowerCase()
304
498
  const out = new Map<string, SessionInfo>()
305
499
 
306
500
  for (const s of sortedSessions) {
307
- if (`${s.title ?? ''} ${s.preview ?? ''} ${s.cwd ?? ''}`.toLowerCase().includes(needle)) {
501
+ if (sessionMatchesSearch(s, trimmedQuery)) {
308
502
  out.set(s.id, s)
309
503
  }
310
504
  }
@@ -326,83 +520,289 @@ export function ChatSidebar({
326
520
  [sortedSessions, pinnedRealIdSet]
327
521
  )
328
522
 
523
+ useEffect(() => {
524
+ const next = resolveManualSessionOrderIds(
525
+ unpinnedAgentSessions.map(s => s.id),
526
+ agentOrderIds,
527
+ agentOrderManual
528
+ )
529
+
530
+ if (!next.length && agentOrderManual) {
531
+ setSidebarSessionOrderManual(false)
532
+ }
533
+
534
+ if (!next.length && agentOrderIds.length) {
535
+ setSidebarSessionOrderIds([])
536
+ return
537
+ }
538
+
539
+ if (next.length && !sameIds(next, agentOrderIds)) {
540
+ setSidebarSessionOrderIds(next)
541
+ }
542
+ }, [agentOrderIds, agentOrderManual, unpinnedAgentSessions])
543
+
329
544
  const agentSessions = useMemo(
330
- () => orderByIds(unpinnedAgentSessions, s => s.id, agentOrderIds),
331
- [unpinnedAgentSessions, agentOrderIds]
545
+ () => (agentOrderManual ? orderByIds(unpinnedAgentSessions, s => s.id, agentOrderIds) : unpinnedAgentSessions),
546
+ [unpinnedAgentSessions, agentOrderIds, agentOrderManual]
332
547
  )
333
548
 
334
- const agentGroups = useMemo(
335
- () => orderByIds(workspaceGroupsFor(agentSessions), g => g.id, workspaceOrderIds),
336
- [agentSessions, workspaceOrderIds]
549
+ // Recents are local-only: messaging-platform sessions are fetched as their
550
+ // own slice ($messagingSessions) and rendered in self-managed per-platform
551
+ // sections below, so there is no source-grouping magic to untangle here.
552
+ //
553
+ // Workspace grouping is a `parent (repo) → worktree → sessions` tree. Git
554
+ // metadata (probed locally) is authoritative; unresolved cwds fall back to a
555
+ // path-name heuristic inside workspaceTreeFor. Parents reorder via
556
+ // workspaceParentOrderIds; worktrees within a parent via workspaceOrderIds.
557
+ const worktreeGroupingActive = agentsGrouped && !showAllProfiles
558
+ const worktreeResolver = useWorktreeInfo(agentSessions, worktreeGroupingActive)
559
+
560
+ const agentTree = useMemo<SidebarWorkspaceTree[] | undefined>(() => {
561
+ if (!worktreeGroupingActive) {
562
+ return undefined
563
+ }
564
+
565
+ const tree = workspaceTreeFor(agentSessions, s.noWorkspace, worktreeResolver)
566
+ const orderedParents = orderByIds(tree, parent => parent.id, workspaceParentOrderIds)
567
+
568
+ return orderedParents.map(parent => ({
569
+ ...parent,
570
+ groups: orderByIds(parent.groups, group => group.id, workspaceOrderIds)
571
+ }))
572
+ }, [worktreeGroupingActive, agentSessions, s.noWorkspace, worktreeResolver, workspaceParentOrderIds, workspaceOrderIds])
573
+
574
+ const loadMoreForProfileGroup = useCallback(
575
+ (profile: string) => {
576
+ if (!onLoadMoreProfileSessions) {
577
+ return
578
+ }
579
+
580
+ setProfileLoadMorePending(prev => ({ ...prev, [profile]: true }))
581
+
582
+ void Promise.resolve(onLoadMoreProfileSessions(profile))
583
+ .catch(() => undefined)
584
+ .finally(() => setProfileLoadMorePending(({ [profile]: _done, ...rest }) => rest))
585
+ },
586
+ [onLoadMoreProfileSessions]
337
587
  )
338
588
 
339
- const showSessionSkeletons = sessionsLoading && sortedSessions.length === 0
340
- const showSessionSections = showSessionSkeletons || sortedSessions.length > 0
341
- const knownSessionTotal = Math.max(sessionsTotal, sortedSessions.length)
342
- const hasMoreSessions = knownSessionTotal > sortedSessions.length
343
- const remainingSessionCount = Math.max(0, knownSessionTotal - sortedSessions.length)
589
+ const loadMoreForMessaging = useCallback(
590
+ (platform: string) => {
591
+ if (!onLoadMoreMessaging) {
592
+ return
593
+ }
344
594
 
345
- const handlePinnedDragEnd = ({ active, over }: DragEndEvent) => {
346
- if (!over || active.id === over.id) {
347
- return
348
- }
595
+ setMessagingLoadMorePending(prev => ({ ...prev, [platform]: true }))
349
596
 
350
- const newIndex = pinnedSessions.findIndex(s => s.id === String(over.id))
597
+ void Promise.resolve(onLoadMoreMessaging(platform))
598
+ .catch(() => undefined)
599
+ .finally(() => setMessagingLoadMorePending(({ [platform]: _done, ...rest }) => rest))
600
+ },
601
+ [onLoadMoreMessaging]
602
+ )
351
603
 
352
- if (newIndex < 0) {
353
- return
354
- }
604
+ // Reveal another batch of a platform's rows; fetch from the backend too if we
605
+ // run past what's loaded and more remain on disk.
606
+ const revealMoreMessaging = (platform: string, loaded: number, hasMore: boolean) => {
607
+ const next = (messagingVisible[platform] ?? NON_SESSION_INITIAL_ROWS) + NON_SESSION_LOAD_STEP
355
608
 
356
- // Sortable ids are live session ids; the pinned store is keyed by durable
357
- // (lineage-root) ids, so translate before reordering.
358
- const dragged = sessionByAnyId.get(String(active.id))
359
- reorderPinnedSession(dragged ? sessionPinId(dragged) : String(active.id), newIndex)
609
+ setMessagingVisible(prev => ({ ...prev, [platform]: next }))
610
+
611
+ if (next > loaded && hasMore) {
612
+ loadMoreForMessaging(platform)
613
+ }
360
614
  }
361
615
 
362
- const handleAgentDragEnd = ({ active, over }: DragEndEvent) => {
363
- if (!over || active.id === over.id) {
364
- return
616
+ // Each messaging platform is its own self-managed section: split the
617
+ // separately-fetched messaging slice by source, newest platform first, rows
618
+ // within a platform by recency. Per-platform totals (when a "load more" has
619
+ // resolved them) drive the count + whether more remain on disk.
620
+ const messagingGroups = useMemo<MessagingSection[]>(() => {
621
+ if (!messagingSessions.length) {
622
+ return []
365
623
  }
366
624
 
367
- const activeId = String(active.id)
368
- const overId = String(over.id)
369
- const activeWs = parseWsId(activeId)
370
- const overWs = parseWsId(overId)
625
+ const bySource = new Map<string, SessionInfo[]>()
371
626
 
372
- if (activeWs && overWs) {
373
- const oldIdx = agentGroups.findIndex(g => g.id === activeWs)
374
- const newIdx = agentGroups.findIndex(g => g.id === overWs)
627
+ for (const session of messagingSessions) {
628
+ const sourceId = normalizeSessionSource(session.source)
375
629
 
376
- if (oldIdx < 0 || newIdx < 0) {
377
- return
630
+ if (!sourceId) {
631
+ continue
378
632
  }
379
633
 
380
- setWorkspaceOrderIds(arrayMove(agentGroups, oldIdx, newIdx).map(g => g.id))
634
+ const list = bySource.get(sourceId) ?? []
635
+ list.push(session)
636
+ bySource.set(sourceId, list)
637
+ }
381
638
 
382
- return
639
+ return [...bySource.entries()]
640
+ .map(([sourceId, list]) => {
641
+ const ordered = [...list].sort((a, b) => sessionTime(b) - sessionTime(a))
642
+ const known = messagingPlatformTotals[sourceId]
643
+ const total = Math.max(ordered.length, known ?? 0)
644
+
645
+ return {
646
+ // Known exact total → more exist iff total exceeds loaded; otherwise
647
+ // the seed fetch was capped, so assume more until a per-platform load
648
+ // resolves the count.
649
+ hasMore: known != null ? known > ordered.length : messagingTruncated,
650
+ label: sessionSourceLabel(sourceId) ?? sourceId,
651
+ sessions: ordered,
652
+ sourceId,
653
+ total
654
+ }
655
+ })
656
+ .sort((a, b) => sessionTime(b.sessions[0]) - sessionTime(a.sessions[0]))
657
+ }, [messagingSessions, messagingPlatformTotals, messagingTruncated])
658
+
659
+ // ALL-profiles view: one collapsible group per profile, color on the header
660
+ // (not on every row). Default profile floats to the top, the rest alpha.
661
+ const profileGroups = useMemo<SidebarSessionGroup[] | undefined>(() => {
662
+ if (!showAllProfiles) {
663
+ return undefined
383
664
  }
384
665
 
385
- if (activeWs || overWs) {
386
- return
666
+ const groups = new Map<string, SidebarSessionGroup>()
667
+
668
+ for (const session of agentSessions) {
669
+ const key = normalizeProfileKey(session.profile)
670
+
671
+ const group = groups.get(key) ?? {
672
+ color: profileColor(key),
673
+ id: key,
674
+ label: key,
675
+ mode: 'profile',
676
+ path: null,
677
+ sessions: []
678
+ }
679
+
680
+ group.sessions.push(session)
681
+
682
+ groups.set(key, group)
387
683
  }
388
684
 
389
- const oldIdx = agentSessions.findIndex(s => s.id === activeId)
390
- const newIdx = agentSessions.findIndex(s => s.id === overId)
685
+ return (
686
+ [...groups.values()]
687
+ .map(group => ({
688
+ ...group,
689
+ loadingMore: Boolean(profileLoadMorePending[group.id]),
690
+ onLoadMore: onLoadMoreProfileSessions ? () => loadMoreForProfileGroup(group.id) : undefined,
691
+ totalCount: Math.max(group.sessions.length, sessionProfileTotals[group.id] ?? 0)
692
+ }))
693
+ // default (root) first, then the rest alphabetically.
694
+ .sort((a, b) => (a.id === 'default' ? -1 : b.id === 'default' ? 1 : a.label.localeCompare(b.label)))
695
+ )
696
+ }, [
697
+ showAllProfiles,
698
+ agentSessions,
699
+ loadMoreForProfileGroup,
700
+ onLoadMoreProfileSessions,
701
+ profileLoadMorePending,
702
+ sessionProfileTotals
703
+ ])
704
+
705
+ const displayAgentSessions = agentSessions
706
+
707
+ // Pagination is scope-aware. In "All profiles" mode it tracks the global
708
+ // unified set. When scoped to one profile it must compare that profile's own
709
+ // loaded rows against that profile's total — otherwise a huge default profile
710
+ // keeps "Load more" stuck on while you browse a small one (the aggregator's
711
+ // total sums every profile). Per-profile totals come from the aggregator
712
+ // (children excluded); fall back to the global total / loaded count.
713
+ const loadedSessionCount = showAllProfiles ? sessions.length : visibleSessions.length
714
+ const scopedProfileTotal = showAllProfiles ? undefined : sessionProfileTotals[profileScope]
715
+
716
+ const knownSessionTotal = Math.max(
717
+ showAllProfiles ? sessionsTotal : (scopedProfileTotal ?? loadedSessionCount),
718
+ loadedSessionCount
719
+ )
720
+
721
+ const hasMoreSessions = knownSessionTotal > loadedSessionCount
722
+ const remainingSessionCount = Math.max(0, knownSessionTotal - loadedSessionCount)
723
+
724
+ const recentsMeta = countLabel(agentSessions.length, knownSessionTotal)
391
725
 
392
- if (oldIdx < 0 || newIdx < 0) {
726
+ const displayAgentGroups = showAllProfiles ? profileGroups : undefined
727
+
728
+ // The recents list owns its own (virtualized) scroll container only when it's a
729
+ // long flat list. In that case it must keep its scroller even in short mode, so
730
+ // we don't flatten it (flattening would defeat virtualization). Short flat lists
731
+ // and grouped views (profile groups or the worktree tree) flatten into the
732
+ // single outer scroll instead.
733
+ const recentsVirtualizes =
734
+ !displayAgentGroups?.length && !agentTree?.length && displayAgentSessions.length >= VIRTUALIZE_THRESHOLD
735
+
736
+ // Keep the persisted parent + worktree orders reconciled with what's on screen:
737
+ // freshly-seen repos/worktrees surface at the top, vanished ones drop out of
738
+ // the saved order.
739
+ useEffect(() => {
740
+ if (!agentTree?.length) {
393
741
  return
394
742
  }
395
743
 
396
- setAgentOrderIds(arrayMove(agentSessions, oldIdx, newIdx).map(s => s.id))
744
+ const nextParents = reconcileOrderIds(
745
+ agentTree.map(parent => parent.id),
746
+ workspaceParentOrderIds
747
+ )
748
+
749
+ if (!sameIds(nextParents, workspaceParentOrderIds)) {
750
+ setSidebarWorkspaceParentOrderIds(nextParents)
751
+ }
752
+
753
+ const nextWorktrees = reconcileOrderIds(
754
+ agentTree.flatMap(parent => parent.groups.map(group => group.id)),
755
+ workspaceOrderIds
756
+ )
757
+
758
+ if (!sameIds(nextWorktrees, workspaceOrderIds)) {
759
+ setSidebarWorkspaceOrderIds(nextWorktrees)
760
+ }
761
+ }, [agentTree, workspaceParentOrderIds, workspaceOrderIds])
762
+
763
+ const showSessionSkeletons = sessionsLoading && sortedSessions.length === 0
764
+
765
+ const showSessionSections = showSessionSkeletons || sortedSessions.length > 0
766
+
767
+ // Each reorderable list reports its OWN new id order; persisting is a direct,
768
+ // typed write — no id-prefix sniffing to figure out which level moved.
769
+ const reorderSessions = (ids: string[]) => {
770
+ setSidebarSessionOrderManual(true)
771
+ setSidebarSessionOrderIds(ids)
397
772
  }
398
773
 
774
+ const reorderParents = (ids: string[]) => setSidebarWorkspaceParentOrderIds(ids)
775
+
776
+ // Worktrees persist as one flat list (orderByIds applies it per parent), so a
777
+ // single parent's new worktree order is spliced back over its slice.
778
+ const reorderWorktree = (parentId: string, ids: string[]) =>
779
+ setSidebarWorkspaceOrderIds(
780
+ (agentTree ?? []).flatMap(parent => (parent.id === parentId ? ids : parent.groups.map(group => group.id)))
781
+ )
782
+
783
+ // Sortable rows carry live session ids; the pinned store is keyed by durable
784
+ // (lineage-root) ids, so translate before persisting the new order.
785
+ const reorderPinned = (ids: string[]) =>
786
+ setPinnedSessionOrder(
787
+ ids.map(id => {
788
+ const session = sessionByAnyId.get(id)
789
+
790
+ return session ? sessionPinId(session) : id
791
+ })
792
+ )
793
+
399
794
  return (
400
795
  <Sidebar
401
796
  className={cn(
402
- 'relative h-full min-w-0 overflow-hidden border-r border-t-0 border-b-0 border-l-0 text-foreground transition-none',
797
+ 'relative h-full min-w-0 overflow-hidden border-t-0 border-b-0 text-foreground transition-none',
798
+ panesFlipped ? 'border-l border-r-0' : 'border-r border-l-0',
403
799
  sidebarOpen
404
800
  ? 'border-(--sidebar-edge-border) bg-(--ui-sidebar-surface-background) opacity-100'
405
- : 'pointer-events-none border-transparent bg-transparent opacity-0'
801
+ : 'pointer-events-none border-transparent bg-transparent opacity-0',
802
+ // While floated by PaneShell's hover-reveal, force visible + interactive
803
+ // — on hover (group-hover/reveal) or when keyboard-pinned (data-forced).
804
+ 'in-data-[pane-hover-reveal=open]:pointer-events-auto in-data-[pane-hover-reveal=open]:border-(--sidebar-edge-border) in-data-[pane-hover-reveal=open]:bg-(--ui-sidebar-surface-background) in-data-[pane-hover-reveal=open]:opacity-100',
805
+ 'group-hover/reveal:pointer-events-auto group-hover/reveal:border-(--sidebar-edge-border) group-hover/reveal:bg-(--ui-sidebar-surface-background) group-hover/reveal:opacity-100'
406
806
  )}
407
807
  collapsible="none"
408
808
  >
@@ -418,27 +818,50 @@ export function ChatSidebar({
418
818
  (item.id === 'messaging' && currentView === 'messaging') ||
419
819
  (item.id === 'artifacts' && currentView === 'artifacts')
420
820
 
821
+ const isNewSession = item.id === 'new-session'
822
+
421
823
  return (
422
824
  <SidebarMenuItem key={item.id}>
423
825
  <SidebarMenuButton
424
826
  aria-disabled={!isInteractive}
425
827
  className={cn(
426
- 'flex h-7 w-full cursor-pointer justify-start gap-2 rounded-md border border-transparent px-2 text-left text-[0.8125rem] font-medium text-(--ui-text-secondary) transition-colors duration-100 ease-out hover:bg-(--ui-control-hover-background) hover:text-foreground hover:transition-none',
828
+ // no-drag: these rows sit directly under the titlebar's
829
+ // [-webkit-app-region:drag] strips (app-shell.tsx), with only
830
+ // 6px of clearance. Drag regions win hit-testing over DOM
831
+ // (pointer-events can't override), and on Linux/WSLg the
832
+ // resolved region has been observed to swallow clicks on the
833
+ // top rows. Same carve-out as USER_BUBBLE_BASE_CLASS in
834
+ // thread.tsx.
835
+ 'flex h-7 w-full justify-start gap-2 rounded-md border border-transparent px-2 text-left text-[0.8125rem] font-medium text-(--ui-text-secondary) transition-colors duration-100 ease-out [-webkit-app-region:no-drag] hover:bg-(--ui-control-hover-background) hover:text-foreground hover:transition-none',
427
836
  active &&
428
837
  'border-(--ui-stroke-tertiary) bg-(--ui-control-active-background) text-foreground shadow-none hover:border-(--ui-stroke-tertiary)!',
429
838
  !isInteractive &&
430
839
  'cursor-default hover:border-transparent hover:bg-transparent hover:text-inherit'
431
840
  )}
432
- onClick={() => onNavigate(item)}
433
- tooltip={item.label}
841
+ onClick={() => {
842
+ // A plain new session lands in whatever profile the live
843
+ // gateway is on (= the active switcher context). null →
844
+ // no swap. The switcher header is the single place to
845
+ // change which profile that is.
846
+ if (isNewSession) {
847
+ $newChatProfile.set(null)
848
+ }
849
+
850
+ onNavigate(item)
851
+ }}
852
+ tooltip={s.nav[item.id] ?? item.label}
434
853
  type="button"
435
854
  >
436
855
  <item.icon className="size-4 shrink-0 text-[color-mix(in_srgb,currentColor_72%,transparent)]" />
437
- {sidebarOpen && (
856
+ {contentVisible && (
438
857
  <>
439
- <span className="min-w-0 flex-1 truncate max-[46.25rem]:hidden">{item.label}</span>
440
- {item.id === 'new-session' && (
441
- <KbdGroup className="ml-auto max-[46.25rem]:hidden" keys={['⇧', 'N']} />
858
+ <span className="min-w-0 flex-1 truncate">{s.nav[item.id] ?? item.label}</span>
859
+ {isNewSession && (
860
+ <KbdGroup
861
+ className={cn('ml-auto opacity-55', newSessionKbdFlash && 'opacity-100!')}
862
+ keys={[...NEW_SESSION_KBD]}
863
+ size="sm"
864
+ />
442
865
  )}
443
866
  </>
444
867
  )}
@@ -450,130 +873,215 @@ export function ChatSidebar({
450
873
  </SidebarGroupContent>
451
874
  </SidebarGroup>
452
875
 
453
- {sidebarOpen && showSessionSections && (
454
- <div className="shrink-0 pb-1 pt-1">
455
- <div className="flex items-center gap-1.5 rounded-md border border-transparent bg-transparent px-2 transition-colors focus-within:border-(--ui-stroke-tertiary)">
456
- <Codicon className="shrink-0 text-(--ui-text-tertiary)" name="search" size="0.75rem" />
457
- <input
458
- aria-label="Search sessions"
459
- className="h-6 min-w-0 flex-1 bg-transparent text-[0.8125rem] text-foreground placeholder:text-(--ui-text-tertiary) focus:outline-none"
460
- onChange={event => setSearchQuery(event.target.value)}
461
- placeholder="Search sessions…"
462
- type="text"
463
- value={searchQuery}
464
- />
465
- {searchQuery && (
466
- <button
467
- aria-label="Clear search"
468
- className="grid size-4 shrink-0 cursor-pointer place-items-center rounded-sm text-(--ui-text-tertiary) hover:bg-(--ui-control-active-background) hover:text-foreground"
469
- onClick={() => setSearchQuery('')}
470
- type="button"
471
- >
472
- <Codicon name="close" size="0.75rem" />
473
- </button>
474
- )}
475
- </div>
876
+ {contentVisible && showSessionSections && (
877
+ <div className="shrink-0 px-2 pb-1 pt-1">
878
+ <SearchField
879
+ aria-label={s.searchAria}
880
+ inputRef={searchInputRef}
881
+ onChange={setSearchQuery}
882
+ placeholder={s.searchPlaceholder}
883
+ value={searchQuery}
884
+ />
476
885
  </div>
477
886
  )}
478
887
 
479
- {sidebarOpen && showSessionSections && trimmedQuery && (
480
- <SidebarSessionsSection
481
- activeSessionId={activeSidebarSessionId}
482
- contentClassName="flex min-h-0 flex-1 flex-col gap-px overflow-y-auto overscroll-contain pb-1.75"
483
- emptyState={
484
- <div className="grid min-h-24 place-items-center rounded-lg px-2 text-center text-xs text-(--ui-text-tertiary)">
485
- No sessions match “{trimmedQuery}”.
486
- </div>
487
- }
488
- label="Results"
489
- labelMeta={String(searchResults.length)}
490
- onArchiveSession={onArchiveSession}
491
- onDeleteSession={onDeleteSession}
492
- onResumeSession={onResumeSession}
493
- onToggle={() => undefined}
494
- onTogglePin={pinSession}
495
- open
496
- pinned={false}
497
- rootClassName="min-h-0 flex-1 p-0"
498
- sessions={searchResults}
499
- workingSessionIdSet={workingSessionIdSet}
500
- />
501
- )}
888
+ {contentVisible && showSessionSections && (
889
+ <div className={cn('flex min-h-0 flex-1 flex-col pb-1.75', SCROLL_Y)}>
890
+ {trimmedQuery && (
891
+ <SidebarSessionsSection
892
+ activeSessionId={activeSidebarSessionId}
893
+ contentClassName={cn('flex min-h-0 flex-1 flex-col gap-px pb-1.75', SCROLL_Y)}
894
+ emptyState={
895
+ <div className="grid min-h-24 place-items-center rounded-lg px-2 text-center text-xs text-(--ui-text-tertiary)">
896
+ {s.noMatch(trimmedQuery)}
897
+ </div>
898
+ }
899
+ label={s.results}
900
+ labelMeta={String(searchResults.length)}
901
+ onArchiveSession={onArchiveSession}
902
+ onDeleteSession={onDeleteSession}
903
+ onResumeSession={onResumeSession}
904
+ onToggle={() => undefined}
905
+ onTogglePin={pinSession}
906
+ open
907
+ pinned={false}
908
+ rootClassName="min-h-32 flex-1 overflow-hidden p-0"
909
+ sessions={searchResults}
910
+ workingSessionIdSet={workingSessionIdSet}
911
+ />
912
+ )}
913
+
914
+ {!trimmedQuery && (
915
+ <SidebarSessionsSection
916
+ activeSessionId={activeSidebarSessionId}
917
+ contentClassName={cn('flex max-h-44 flex-col gap-px rounded-lg pb-2 pt-1', GROUP_BODY)}
918
+ dndSensors={dndSensors}
919
+ emptyState={<SidebarPinnedEmptyState />}
920
+ label={s.pinned}
921
+ onArchiveSession={onArchiveSession}
922
+ onDeleteSession={onDeleteSession}
923
+ onReorderSessions={reorderPinned}
924
+ onResumeSession={onResumeSession}
925
+ onToggle={() => setSidebarPinsOpen(!pinsOpen)}
926
+ onTogglePin={unpinSession}
927
+ open={pinsOpen}
928
+ pinned
929
+ rootClassName="shrink-0 p-0 pb-1"
930
+ sessions={pinnedSessions}
931
+ sortable={pinnedSessions.length > 1}
932
+ workingSessionIdSet={workingSessionIdSet}
933
+ />
934
+ )}
935
+
936
+ {!trimmedQuery && (
937
+ <SidebarSessionsSection
938
+ activeSessionId={activeSidebarSessionId}
939
+ contentClassName={cn(
940
+ 'flex min-h-0 flex-1 flex-col pb-1.75',
941
+ SCROLL_Y,
942
+ // Separate profile sections clearly in the ALL view; rows inside
943
+ // each group keep their own tight gap-px rhythm.
944
+ showAllProfiles ? 'gap-3' : 'gap-px',
945
+ // Flatten into the single scroll when compact — unless this is the
946
+ // virtualized long list, which must keep its own scroller.
947
+ !recentsVirtualizes && COMPACT_FLAT
948
+ )}
949
+ dndSensors={dndSensors}
950
+ emptyState={showSessionSkeletons ? <SidebarSessionSkeletons /> : <SidebarAllPinnedState />}
951
+ footer={
952
+ // Hide "load more" only when workspace-grouped (those groups page
953
+ // themselves). ALL-profiles now pages per-profile from each profile
954
+ // header; the global footer only applies to non-ALL views.
955
+ !showAllProfiles && !agentsGrouped && !showSessionSkeletons && hasMoreSessions ? (
956
+ <SidebarLoadMoreRow
957
+ loading={sessionsLoading}
958
+ onClick={onLoadMoreSessions}
959
+ step={Math.min(SIDEBAR_SESSIONS_PAGE_SIZE, remainingSessionCount)}
960
+ />
961
+ ) : null
962
+ }
963
+ forceEmptyState={showSessionSkeletons}
964
+ groups={displayAgentGroups}
965
+ headerAction={
966
+ // Always reserve the icon-xs (size-6) slot so the header keeps the
967
+ // same height whether or not the toggle renders — otherwise the
968
+ // "Sessions" label jumps when switching to the ALL-profiles view.
969
+ // Grouping operates on unpinned recents; if everything is pinned
970
+ // the toggle does nothing, and it's irrelevant in the ALL-profiles
971
+ // view (always grouped by profile), so hide the button (not the slot).
972
+ <div className="grid size-6 shrink-0 place-items-center">
973
+ {!showAllProfiles && agentSessions.length > 0 ? (
974
+ <Tip label={agentsGrouped ? s.groupTitleGrouped : s.groupTitleUngrouped}>
975
+ <Button
976
+ aria-label={agentsGrouped ? s.groupAriaGrouped : s.groupAriaUngrouped}
977
+ className={cn(
978
+ 'text-(--ui-text-tertiary) opacity-70 hover:bg-(--ui-control-hover-background) hover:text-foreground hover:opacity-100 focus-visible:opacity-100',
979
+ agentsGrouped && 'bg-(--ui-control-active-background) text-foreground opacity-100'
980
+ )}
981
+ onClick={event => {
982
+ event.stopPropagation()
983
+ setSidebarRecentsOpen(true)
984
+ setSidebarAgentsGrouped(!agentsGrouped)
985
+ }}
986
+ size="icon-xs"
987
+ variant="ghost"
988
+ >
989
+ <Codicon name={agentsGrouped ? 'list-unordered' : 'root-folder'} size="0.75rem" />
990
+ </Button>
991
+ </Tip>
992
+ ) : null}
993
+ </div>
994
+ }
995
+ label={s.sessions}
996
+ labelMeta={recentsMeta}
997
+ onArchiveSession={onArchiveSession}
998
+ onDeleteSession={onDeleteSession}
999
+ onNewSessionInWorkspace={showAllProfiles ? undefined : onNewSessionInWorkspace}
1000
+ onReorderParents={showAllProfiles ? undefined : reorderParents}
1001
+ onReorderSessions={showAllProfiles ? undefined : reorderSessions}
1002
+ onReorderWorktree={showAllProfiles ? undefined : reorderWorktree}
1003
+ onResumeSession={onResumeSession}
1004
+ onToggle={() => setSidebarRecentsOpen(!agentsOpen)}
1005
+ onTogglePin={pinSession}
1006
+ open={agentsOpen}
1007
+ pinned={false}
1008
+ rootClassName={cn(
1009
+ 'min-h-32 flex-1 overflow-hidden p-0',
1010
+ !recentsVirtualizes && 'compact:min-h-0 compact:flex-none compact:overflow-visible'
1011
+ )}
1012
+ sessions={displayAgentSessions}
1013
+ sortable={!showAllProfiles && agentSessions.length > 1}
1014
+ tree={agentTree}
1015
+ workingSessionIdSet={workingSessionIdSet}
1016
+ />
1017
+ )}
502
1018
 
503
- {sidebarOpen && showSessionSections && !trimmedQuery && (
504
- <SidebarSessionsSection
505
- activeSessionId={activeSidebarSessionId}
506
- contentClassName="flex min-h-10 shrink-0 flex-col gap-px rounded-lg pb-2 pt-1"
507
- dndSensors={dndSensors}
508
- emptyState={<SidebarPinnedEmptyState />}
509
- label="Pinned"
510
- onArchiveSession={onArchiveSession}
511
- onDeleteSession={onDeleteSession}
512
- onReorder={handlePinnedDragEnd}
513
- onResumeSession={onResumeSession}
514
- onToggle={() => setSidebarPinsOpen(!pinsOpen)}
515
- onTogglePin={unpinSession}
516
- open={pinsOpen}
517
- pinned
518
- rootClassName="shrink-0 p-0 pb-1"
519
- sessions={pinnedSessions}
520
- sortable={pinnedSessions.length > 1}
521
- workingSessionIdSet={workingSessionIdSet}
522
- />
1019
+ {!trimmedQuery &&
1020
+ messagingGroups.map(group => {
1021
+ const visible = messagingVisible[group.sourceId] ?? NON_SESSION_INITIAL_ROWS
1022
+ const shownSessions = group.sessions.slice(0, visible)
1023
+ // More to show if rows are hidden behind the cap, or the backend
1024
+ // still has older threads on disk.
1025
+ const canRevealMore = visible < group.sessions.length || group.hasMore
1026
+
1027
+ return (
1028
+ <SidebarSessionsSection
1029
+ activeSessionId={activeSidebarSessionId}
1030
+ contentClassName={cn('flex max-h-56 flex-col gap-px pb-1.75', GROUP_BODY)}
1031
+ emptyState={null}
1032
+ footer={
1033
+ canRevealMore ? (
1034
+ <SidebarLoadMoreRow
1035
+ loading={Boolean(messagingLoadMorePending[group.sourceId])}
1036
+ onClick={() => revealMoreMessaging(group.sourceId, group.sessions.length, group.hasMore)}
1037
+ step={Math.min(NON_SESSION_LOAD_STEP, Math.max(0, group.total - shownSessions.length))}
1038
+ />
1039
+ ) : null
1040
+ }
1041
+ key={group.sourceId}
1042
+ label={group.label}
1043
+ labelIcon={
1044
+ <PlatformAvatar
1045
+ className="size-4 rounded-[4px] text-[0.5625rem] [&_svg]:size-3"
1046
+ platformId={group.sourceId}
1047
+ platformName={group.label}
1048
+ />
1049
+ }
1050
+ labelMeta={countLabel(group.sessions.length, group.total)}
1051
+ onArchiveSession={onArchiveSession}
1052
+ onDeleteSession={onDeleteSession}
1053
+ onResumeSession={onResumeSession}
1054
+ onToggle={() => toggleSidebarMessagingOpen(group.sourceId)}
1055
+ onTogglePin={pinSession}
1056
+ open={messagingOpenIds.includes(group.sourceId)}
1057
+ pinned={false}
1058
+ rootClassName="shrink-0 p-0"
1059
+ sessions={shownSessions}
1060
+ workingSessionIdSet={workingSessionIdSet}
1061
+ />
1062
+ )
1063
+ })}
1064
+
1065
+ {!trimmedQuery && cronJobs.length > 0 && (
1066
+ <SidebarCronJobsSection
1067
+ jobs={cronJobs}
1068
+ label={s.cronJobs}
1069
+ onManageJob={onManageCronJob}
1070
+ onOpenRun={onResumeSession}
1071
+ onToggle={() => setSidebarCronOpen(!cronOpen)}
1072
+ onTriggerJob={onTriggerCronJob}
1073
+ open={cronOpen}
1074
+ />
1075
+ )}
1076
+ </div>
523
1077
  )}
524
1078
 
525
- {sidebarOpen && showSessionSections && !trimmedQuery && (
526
- <SidebarSessionsSection
527
- activeSessionId={activeSidebarSessionId}
528
- contentClassName="flex min-h-0 flex-1 flex-col gap-px overflow-y-auto overscroll-contain pb-1.75"
529
- dndSensors={dndSensors}
530
- emptyState={showSessionSkeletons ? <SidebarSessionSkeletons /> : <SidebarAllPinnedState />}
531
- footer={
532
- !agentsGrouped && !showSessionSkeletons && hasMoreSessions ? (
533
- <SidebarLoadMoreRow
534
- loading={sessionsLoading}
535
- onClick={onLoadMoreSessions}
536
- step={Math.min(SIDEBAR_SESSIONS_PAGE_SIZE, remainingSessionCount)}
537
- />
538
- ) : null
539
- }
540
- forceEmptyState={showSessionSkeletons}
541
- groups={agentsGrouped ? agentGroups : undefined}
542
- headerAction={
543
- <Button
544
- aria-label={agentsGrouped ? 'Show sessions as a single list' : 'Group sessions by workspace'}
545
- className={cn(
546
- 'cursor-pointer text-(--ui-text-tertiary) opacity-70 hover:bg-(--ui-control-hover-background) hover:text-foreground hover:opacity-100 focus-visible:opacity-100',
547
- agentsGrouped && 'bg-(--ui-control-active-background) text-foreground opacity-100'
548
- )}
549
- onClick={event => {
550
- event.stopPropagation()
551
- setSidebarRecentsOpen(true)
552
- setSidebarAgentsGrouped(!agentsGrouped)
553
- }}
554
- size="icon-xs"
555
- title={agentsGrouped ? 'Ungroup sessions' : 'Group by workspace'}
556
- variant="ghost"
557
- >
558
- <Codicon name={agentsGrouped ? 'list-unordered' : 'root-folder'} size="0.75rem" />
559
- </Button>
560
- }
561
- label="Sessions"
562
- labelMeta={countLabel(agentSessions.length, knownSessionTotal)}
563
- onArchiveSession={onArchiveSession}
564
- onDeleteSession={onDeleteSession}
565
- onNewSessionInWorkspace={onNewSessionInWorkspace}
566
- onReorder={handleAgentDragEnd}
567
- onResumeSession={onResumeSession}
568
- onToggle={() => setSidebarRecentsOpen(!agentsOpen)}
569
- onTogglePin={pinSession}
570
- open={agentsOpen}
571
- pinned={false}
572
- rootClassName="min-h-0 flex-1 p-0"
573
- sessions={agentSessions}
574
- sortable={agentSessions.length > 1}
575
- workingSessionIdSet={workingSessionIdSet}
576
- />
1079
+ {contentVisible && !showSessionSections && <div className="min-h-0 flex-1" />}
1080
+
1081
+ {contentVisible && (
1082
+ <div className="shrink-0 px-0.5 pb-1 pt-0.5">
1083
+ <ProfileRail />
1084
+ </div>
577
1085
  )}
578
1086
  </SidebarContent>
579
1087
  </Sidebar>
@@ -586,16 +1094,18 @@ interface SidebarSectionHeaderProps {
586
1094
  onToggle: () => void
587
1095
  action?: React.ReactNode
588
1096
  meta?: React.ReactNode
1097
+ icon?: React.ReactNode
589
1098
  }
590
1099
 
591
- function SidebarSectionHeader({ label, open, onToggle, action, meta }: SidebarSectionHeaderProps) {
1100
+ function SidebarSectionHeader({ label, open, onToggle, action, meta, icon }: SidebarSectionHeaderProps) {
592
1101
  return (
593
1102
  <div className="group/section flex shrink-0 items-center justify-between pb-1 pt-1.5">
594
1103
  <button
595
- className="group/section-label flex w-fit cursor-pointer items-center gap-1 bg-transparent text-left leading-none"
1104
+ className="group/section-label flex w-fit items-center gap-1 bg-transparent text-left leading-none"
596
1105
  onClick={onToggle}
597
1106
  type="button"
598
1107
  >
1108
+ {icon}
599
1109
  <SidebarPanelLabel>{label}</SidebarPanelLabel>
600
1110
  {meta && <SidebarCount>{meta}</SidebarCount>}
601
1111
  <DisclosureCaret
@@ -621,28 +1131,35 @@ function SidebarSessionSkeletons() {
621
1131
  )
622
1132
  }
623
1133
 
624
- const SidebarAllPinnedState = () => (
625
- <div className="grid min-h-24 place-items-center rounded-lg text-center text-xs text-(--ui-text-tertiary)">
626
- Everything here is pinned. Unpin a chat to show it in recents.
627
- </div>
628
- )
1134
+ function SidebarAllPinnedState() {
1135
+ const { t } = useI18n()
1136
+
1137
+ return (
1138
+ <div className="grid min-h-24 place-items-center rounded-lg text-center text-xs text-(--ui-text-tertiary)">
1139
+ {t.sidebar.allPinned}
1140
+ </div>
1141
+ )
1142
+ }
629
1143
 
630
1144
  function SidebarPinnedEmptyState() {
1145
+ const { t } = useI18n()
1146
+
631
1147
  return (
632
1148
  <div className="flex min-h-7 items-center gap-1.5 rounded-lg pl-2 text-[0.75rem] text-(--ui-text-tertiary)">
633
1149
  <span className="grid w-3.5 shrink-0 place-items-center text-(--ui-text-quaternary)">
634
1150
  <Codicon name="pin" size="0.75rem" />
635
1151
  </span>
636
- <span>Shift click to pin a chat</span>
1152
+ <span>{t.sidebar.shiftClickHint}</span>
637
1153
  </div>
638
1154
  )
639
1155
  }
640
1156
 
641
- interface SidebarSessionGroup {
642
- id: string
1157
+ interface MessagingSection {
1158
+ sourceId: string
643
1159
  label: string
644
- path: null | string
645
1160
  sessions: SessionInfo[]
1161
+ total: number
1162
+ hasMore: boolean
646
1163
  }
647
1164
 
648
1165
  interface SidebarSessionsSectionProps {
@@ -665,9 +1182,16 @@ interface SidebarSessionsSectionProps {
665
1182
  headerAction?: React.ReactNode
666
1183
  footer?: React.ReactNode
667
1184
  groups?: SidebarSessionGroup[]
1185
+ tree?: SidebarWorkspaceTree[]
668
1186
  labelMeta?: React.ReactNode
1187
+ labelIcon?: React.ReactNode
669
1188
  sortable?: boolean
670
- onReorder?: (event: DragEndEvent) => void
1189
+ // Per-level reorder callbacks. Each is optional; a list is draggable iff its
1190
+ // callback is supplied. The flat session list, the repo parents, and a parent's
1191
+ // worktrees each own an independent ReorderableList, so nothing collides.
1192
+ onReorderSessions?: (ids: string[]) => void
1193
+ onReorderParents?: (ids: string[]) => void
1194
+ onReorderWorktree?: (parentId: string, ids: string[]) => void
671
1195
  dndSensors?: ReturnType<typeof useSensors>
672
1196
  }
673
1197
 
@@ -691,15 +1215,23 @@ function SidebarSessionsSection({
691
1215
  headerAction,
692
1216
  footer,
693
1217
  groups,
1218
+ tree,
694
1219
  labelMeta,
1220
+ labelIcon,
695
1221
  sortable = false,
696
- onReorder,
1222
+ onReorderSessions,
1223
+ onReorderParents,
1224
+ onReorderWorktree,
697
1225
  dndSensors
698
1226
  }: SidebarSessionsSectionProps) {
699
- const showEmptyState = forceEmptyState || sessions.length === 0
700
- const dndActive = sortable && !!onReorder
701
-
702
- const renderRow = (session: SessionInfo) => {
1227
+ const hasTreeSessions = Boolean(tree?.some(parent => parent.sessionCount > 0))
1228
+ const hasGroupedSessions = Boolean(groups?.some(group => group.sessions.length > 0))
1229
+ const showEmptyState = forceEmptyState || (!hasGroupedSessions && !hasTreeSessions && sessions.length === 0)
1230
+ // The flat recents/pinned list is the only place sessions reorder by hand;
1231
+ // grouped/tree views always sort by creation date and never drag.
1232
+ const sessionsDraggable = sortable && !!onReorderSessions
1233
+
1234
+ const renderRow = (session: SessionInfo, draggable: boolean) => {
703
1235
  const rowProps = {
704
1236
  isPinned: pinned,
705
1237
  isSelected: session.id === activeSessionId,
@@ -711,82 +1243,89 @@ function SidebarSessionsSection({
711
1243
  session
712
1244
  }
713
1245
 
714
- return sortable ? (
1246
+ return draggable ? (
715
1247
  <SortableSidebarSessionRow key={session.id} {...rowProps} />
716
1248
  ) : (
717
1249
  <SidebarSessionRow key={session.id} {...rowProps} />
718
1250
  )
719
1251
  }
720
1252
 
721
- const renderRows = (items: SessionInfo[]) => items.map(renderRow)
722
-
723
- const renderSessionList = (items: SessionInfo[]) =>
724
- dndActive ? (
725
- <SortableContext items={items.map(s => s.id)} strategy={verticalListSortingStrategy}>
726
- {renderRows(items)}
727
- </SortableContext>
728
- ) : (
729
- renderRows(items)
730
- )
1253
+ // Sessions inside repos/worktrees are date-ordered and static.
1254
+ const renderRows = (items: SessionInfo[]) => items.map(session => renderRow(session, false))
731
1255
 
732
- const flatVirtualized = !showEmptyState && !groups?.length && sessions.length >= VIRTUALIZE_THRESHOLD
1256
+ const flatVirtualized =
1257
+ !showEmptyState && !groups?.length && !tree?.length && sessions.length >= VIRTUALIZE_THRESHOLD
733
1258
 
734
1259
  let inner: React.ReactNode
735
1260
 
736
1261
  if (showEmptyState) {
737
1262
  inner = emptyState
738
- } else if (groups?.length) {
739
- const groupNodes = groups.map(group =>
740
- dndActive ? (
741
- <SortableSidebarWorkspaceGroup
742
- group={group}
743
- key={group.id}
1263
+ } else if (tree?.length) {
1264
+ const parentNodes = tree.map(parent =>
1265
+ onReorderParents ? (
1266
+ <SortableSidebarWorkspaceParent
1267
+ dndSensors={dndSensors}
1268
+ key={parent.id}
744
1269
  onNewSession={onNewSessionInWorkspace}
745
- renderRows={renderSessionList}
1270
+ onReorderWorktree={onReorderWorktree}
1271
+ parent={parent}
1272
+ renderRows={renderRows}
746
1273
  />
747
1274
  ) : (
748
- <SidebarWorkspaceGroup
749
- group={group}
750
- key={group.id}
1275
+ <SidebarWorkspaceParent
1276
+ key={parent.id}
751
1277
  onNewSession={onNewSessionInWorkspace}
752
- renderRows={renderSessionList}
1278
+ parent={parent}
1279
+ renderRows={renderRows}
753
1280
  />
754
1281
  )
755
1282
  )
756
1283
 
757
- inner = dndActive ? (
758
- <SortableContext items={groups.map(g => wsId(g.id))} strategy={verticalListSortingStrategy}>
759
- {groupNodes}
760
- </SortableContext>
1284
+ inner = onReorderParents ? (
1285
+ <ReorderableList ids={tree.map(parent => parent.id)} onReorder={onReorderParents} sensors={dndSensors}>
1286
+ {parentNodes}
1287
+ </ReorderableList>
761
1288
  ) : (
762
- groupNodes
1289
+ parentNodes
763
1290
  )
1291
+ } else if (groups?.length) {
1292
+ // Profile/source groups never reorder; render them flat with static rows.
1293
+ inner = groups.map(group => (
1294
+ <SidebarWorkspaceGroup group={group} key={group.id} onNewSession={onNewSessionInWorkspace} renderRows={renderRows} />
1295
+ ))
764
1296
  } else if (flatVirtualized) {
765
- inner = (
1297
+ const virtual = (
766
1298
  <VirtualSessionList
767
1299
  activeSessionId={activeSessionId}
1300
+ className={contentClassName}
768
1301
  onArchiveSession={onArchiveSession}
769
1302
  onDeleteSession={onDeleteSession}
770
1303
  onResumeSession={onResumeSession}
771
1304
  onTogglePin={onTogglePin}
772
1305
  pinned={pinned}
773
1306
  sessions={sessions}
774
- sortable={sortable}
1307
+ sortable={sessionsDraggable}
775
1308
  workingSessionIdSet={workingSessionIdSet}
776
1309
  />
777
1310
  )
778
- } else {
779
- inner = renderSessionList(sessions)
780
- }
781
1311
 
782
- const body =
783
- dndActive && !showEmptyState ? (
784
- <DndContext collisionDetection={closestCenter} onDragEnd={onReorder} sensors={dndSensors}>
785
- {inner}
786
- </DndContext>
787
- ) : (
788
- inner
1312
+ inner =
1313
+ sessionsDraggable && onReorderSessions ? (
1314
+ <ReorderableList ids={sessions.map(s => s.id)} onReorder={onReorderSessions} sensors={dndSensors}>
1315
+ {virtual}
1316
+ </ReorderableList>
1317
+ ) : (
1318
+ virtual
1319
+ )
1320
+ } else if (sessionsDraggable && onReorderSessions) {
1321
+ inner = (
1322
+ <ReorderableList ids={sessions.map(s => s.id)} onReorder={onReorderSessions} sensors={dndSensors}>
1323
+ {sessions.map(session => renderRow(session, true))}
1324
+ </ReorderableList>
789
1325
  )
1326
+ } else {
1327
+ inner = renderRows(sessions)
1328
+ }
790
1329
 
791
1330
  // The virtualizer owns its own scroller, so suppress the wrapper's overflow
792
1331
  // to avoid a double scroll container.
@@ -794,10 +1333,17 @@ function SidebarSessionsSection({
794
1333
 
795
1334
  return (
796
1335
  <SidebarGroup className={rootClassName}>
797
- <SidebarSectionHeader action={headerAction} label={label} meta={labelMeta} onToggle={onToggle} open={open} />
1336
+ <SidebarSectionHeader
1337
+ action={headerAction}
1338
+ icon={labelIcon}
1339
+ label={label}
1340
+ meta={labelMeta}
1341
+ onToggle={onToggle}
1342
+ open={open}
1343
+ />
798
1344
  {open && (
799
1345
  <SidebarGroupContent className={resolvedContentClassName}>
800
- {body}
1346
+ {inner}
801
1347
  {footer}
802
1348
  </SidebarGroupContent>
803
1349
  )}
@@ -826,71 +1372,104 @@ function SidebarWorkspaceGroup({
826
1372
  ref,
827
1373
  ...rest
828
1374
  }: SidebarWorkspaceGroupProps) {
1375
+ const { t } = useI18n()
1376
+ const s = t.sidebar
1377
+ const isProfileGroup = group.mode === 'profile'
1378
+ const isSourceGroup = group.mode === 'source'
1379
+ const pageStep = isProfileGroup ? PROFILE_INITIAL_PAGE : WORKSPACE_PAGE
829
1380
  const [open, setOpen] = useState(true)
830
- const [visibleCount, setVisibleCount] = useState(WORKSPACE_PAGE)
1381
+ const [visibleCount, setVisibleCount] = useState(pageStep)
1382
+
1383
+ const loadedCount = group.sessions.length
1384
+ // Profile groups know their on-disk total (children excluded); workspace
1385
+ // groups only ever page within what's already loaded.
1386
+ const totalCount = isProfileGroup ? Math.max(group.totalCount ?? loadedCount, loadedCount) : loadedCount
831
1387
  const visibleSessions = group.sessions.slice(0, visibleCount)
832
- const hiddenCount = Math.max(0, group.sessions.length - visibleSessions.length)
833
- const nextCount = Math.min(WORKSPACE_PAGE, hiddenCount)
1388
+ const hiddenCount = Math.max(0, totalCount - visibleSessions.length)
1389
+ const nextCount = Math.min(pageStep, hiddenCount)
1390
+
1391
+ // Leading glyph: profile color dot, platform avatar, or a branch mark for a
1392
+ // worktree. When reorderable it doubles as the drag handle (icon ↔ grabber).
1393
+ const leadingIcon = group.color ? (
1394
+ <span aria-hidden="true" className="size-2 shrink-0 rounded-full" style={{ backgroundColor: group.color }} />
1395
+ ) : isSourceGroup && group.sourceId ? (
1396
+ <PlatformAvatar
1397
+ className="size-4 rounded-[4px] text-[0.5625rem] [&_svg]:size-3"
1398
+ platformId={group.sourceId}
1399
+ platformName={group.label}
1400
+ />
1401
+ ) : (
1402
+ <Codicon className="shrink-0 text-(--ui-text-tertiary)" name="git-branch" size="0.75rem" />
1403
+ )
1404
+
1405
+ // Reveal already-loaded rows first; only hit the backend when the next page
1406
+ // crosses what's been fetched for this profile.
1407
+ const handleProfileLoadMore = () => {
1408
+ const target = visibleCount + pageStep
1409
+
1410
+ setVisibleCount(target)
1411
+
1412
+ if (target > loadedCount && loadedCount < totalCount) {
1413
+ group.onLoadMore?.()
1414
+ }
1415
+ }
834
1416
 
835
1417
  return (
836
- <div className={cn('grid gap-px', dragging && 'z-10 opacity-60', className)} ref={ref} style={style} {...rest}>
837
- <div className="group/workspace flex min-h-6 items-center gap-1 px-2 pt-1 text-[0.6875rem] font-medium text-(--ui-text-tertiary)">
838
- <button
839
- className="flex min-w-0 cursor-pointer items-center gap-1 bg-transparent text-left hover:text-(--ui-text-secondary)"
840
- onClick={() => setOpen(value => !value)}
841
- title={group.path ?? undefined}
842
- type="button"
843
- >
844
- <span className="truncate">{group.label}</span>
845
- <SidebarCount>{group.sessions.length}</SidebarCount>
846
- <DisclosureCaret
847
- className="text-(--ui-text-tertiary) opacity-0 transition group-hover/workspace:opacity-100"
848
- open={open}
849
- />
850
- </button>
851
- {onNewSession && (
852
- <button
853
- aria-label={`New session in ${group.label}`}
854
- className="grid size-4 shrink-0 cursor-pointer place-items-center rounded-sm bg-transparent text-(--ui-text-quaternary) opacity-0 transition-opacity hover:bg-(--ui-control-hover-background) hover:text-foreground group-hover/workspace:opacity-100"
855
- onClick={() => onNewSession(group.path)}
856
- title={`New session in ${group.label}`}
857
- type="button"
858
- >
859
- <Codicon name="add" size="0.75rem" />
860
- </button>
861
- )}
862
- {reorderable && (
863
- <span
864
- {...dragHandleProps}
865
- aria-label={`Reorder workspace ${group.label}`}
866
- className="ml-auto -my-0.5 grid w-4 shrink-0 cursor-grab touch-none place-items-center self-stretch overflow-hidden active:cursor-grabbing"
867
- onClick={event => event.stopPropagation()}
868
- >
869
- <Codicon
870
- className={cn(
871
- 'text-(--ui-text-quaternary) opacity-0 transition-opacity group-hover/workspace:opacity-80 hover:text-(--ui-text-secondary)',
872
- dragging && 'text-(--ui-text-secondary) opacity-100'
873
- )}
874
- name="grabber"
875
- size="0.75rem"
1418
+ <div
1419
+ className={cn(
1420
+ // While lifted, paint the opaque sidebar surface so the dragged group
1421
+ // erases the rows it floats over instead of ghosting them through a
1422
+ // translucent body.
1423
+ // minmax(0,1fr): pin the single column to the rail width. A bare `grid`
1424
+ // auto column sizes to the widest child's MAX-content (the full,
1425
+ // untruncated label), overflowing the rail so overflow-x-hidden clips the
1426
+ // +/grabber off-screen — the inner truncate never gets a bounded width.
1427
+ 'grid grid-cols-[minmax(0,1fr)] gap-px data-[dragging=true]:z-10 data-[dragging=true]:rounded-md data-[dragging=true]:bg-(--ui-sidebar-surface-background) data-[dragging=true]:will-change-transform',
1428
+ className
1429
+ )}
1430
+ data-dragging={dragging ? 'true' : undefined}
1431
+ ref={ref}
1432
+ style={style}
1433
+ {...rest}
1434
+ >
1435
+ <WorkspaceHeader
1436
+ action={
1437
+ (onNewSession || isProfileGroup) && (
1438
+ <WorkspaceAddButton
1439
+ label={s.newSessionIn(group.label)}
1440
+ // Profile groups start a fresh session in that profile but keep the
1441
+ // all-profiles browse view (newSessionInProfile leaves the scope
1442
+ // alone); workspace groups seed the new session's cwd from the path.
1443
+ onClick={() => (isProfileGroup ? newSessionInProfile(group.id) : onNewSession?.(group.path))}
876
1444
  />
877
- </span>
878
- )}
879
- </div>
1445
+ )
1446
+ }
1447
+ count={isProfileGroup ? countLabel(visibleSessions.length, totalCount) : group.sessions.length}
1448
+ dragging={dragging}
1449
+ dragHandleProps={dragHandleProps}
1450
+ icon={leadingIcon}
1451
+ label={group.label}
1452
+ onToggle={() => setOpen(value => !value)}
1453
+ open={open}
1454
+ reorderable={reorderable}
1455
+ />
880
1456
  {open && (
881
1457
  <>
882
1458
  {renderRows(visibleSessions)}
883
- {hiddenCount > 0 && (
884
- <button
885
- aria-label={`Show ${nextCount} more in ${group.label}`}
886
- className="ml-auto grid size-5 cursor-pointer place-items-center rounded-sm bg-transparent text-(--ui-text-tertiary) transition-colors hover:bg-(--ui-control-hover-background) hover:text-foreground"
887
- onClick={() => setVisibleCount(count => count + WORKSPACE_PAGE)}
888
- title={`Show ${nextCount} more in ${group.label}`}
889
- type="button"
890
- >
891
- <Codicon name="ellipsis" size="0.75rem" />
892
- </button>
893
- )}
1459
+ {hiddenCount > 0 &&
1460
+ (isProfileGroup ? (
1461
+ <SidebarLoadMoreRow
1462
+ loading={Boolean(group.loadingMore)}
1463
+ onClick={handleProfileLoadMore}
1464
+ step={nextCount}
1465
+ />
1466
+ ) : (
1467
+ <WorkspaceShowMoreButton
1468
+ count={nextCount}
1469
+ label={group.label}
1470
+ onClick={() => setVisibleCount(count => count + WORKSPACE_PAGE)}
1471
+ />
1472
+ ))}
894
1473
  </>
895
1474
  )}
896
1475
  </div>
@@ -904,13 +1483,281 @@ interface SortableWorkspaceProps {
904
1483
  }
905
1484
 
906
1485
  function SortableSidebarWorkspaceGroup(props: SortableWorkspaceProps) {
907
- return <SidebarWorkspaceGroup {...props} {...useSortableBindings(wsId(props.group.id))} />
1486
+ return <SidebarWorkspaceGroup {...props} {...useSortableBindings(props.group.id)} />
1487
+ }
1488
+
1489
+ interface SidebarWorkspaceParentProps extends React.ComponentProps<'div'> {
1490
+ parent: SidebarWorkspaceTree
1491
+ renderRows: (sessions: SessionInfo[]) => React.ReactNode
1492
+ onNewSession?: (path: null | string) => void
1493
+ // When set, this parent's worktrees reorder inside their OWN ReorderableList, so a
1494
+ // worktree drag only ever collides with its siblings — never the repos around it.
1495
+ onReorderWorktree?: (parentId: string, ids: string[]) => void
1496
+ dndSensors?: ReturnType<typeof useSensors>
1497
+ // Whether this parent itself is draggable (set by useSortableBindings).
1498
+ reorderable?: boolean
1499
+ dragging?: boolean
1500
+ dragHandleProps?: React.HTMLAttributes<HTMLElement>
1501
+ }
1502
+
1503
+ // Top level of the worktree tree: a repo header whose body is the repo's
1504
+ // worktrees (each a SidebarWorkspaceGroup), indented one step.
1505
+ function SidebarWorkspaceParent({
1506
+ parent,
1507
+ renderRows,
1508
+ onNewSession,
1509
+ onReorderWorktree,
1510
+ dndSensors,
1511
+ reorderable = false,
1512
+ dragging = false,
1513
+ dragHandleProps,
1514
+ className,
1515
+ style,
1516
+ ref,
1517
+ ...rest
1518
+ }: SidebarWorkspaceParentProps) {
1519
+ const { t } = useI18n()
1520
+ const s = t.sidebar
1521
+ const [open, setOpen] = useState(true)
1522
+ const [visibleCount, setVisibleCount] = useState(WORKSPACE_PAGE)
1523
+
1524
+ // A repo with a single worktree has no second level worth showing: collapse it
1525
+ // to one row (repo header → its sessions directly), only nesting when there
1526
+ // are 2+ worktrees to choose between.
1527
+ const soleWorktree = parent.groups.length === 1 ? parent.groups[0] : null
1528
+ const newSessionPath = soleWorktree ? soleWorktree.path : parent.path
1529
+ const visibleSessions = soleWorktree ? soleWorktree.sessions.slice(0, visibleCount) : []
1530
+ const hiddenCount = soleWorktree ? Math.max(0, soleWorktree.sessions.length - visibleSessions.length) : 0
1531
+
1532
+ const groupNodes = parent.groups.map(group =>
1533
+ onReorderWorktree ? (
1534
+ <SortableSidebarWorkspaceGroup group={group} key={group.id} onNewSession={onNewSession} renderRows={renderRows} />
1535
+ ) : (
1536
+ <SidebarWorkspaceGroup group={group} key={group.id} onNewSession={onNewSession} renderRows={renderRows} />
1537
+ )
1538
+ )
1539
+
1540
+ return (
1541
+ <div
1542
+ className={cn(
1543
+ 'grid grid-cols-[minmax(0,1fr)] gap-px data-[dragging=true]:z-10 data-[dragging=true]:rounded-md data-[dragging=true]:bg-(--ui-sidebar-surface-background) data-[dragging=true]:will-change-transform',
1544
+ className
1545
+ )}
1546
+ data-dragging={dragging ? 'true' : undefined}
1547
+ ref={ref}
1548
+ style={style}
1549
+ {...rest}
1550
+ >
1551
+ <WorkspaceHeader
1552
+ action={
1553
+ onNewSession && (newSessionPath || soleWorktree) && (
1554
+ <WorkspaceAddButton label={s.newSessionIn(parent.label)} onClick={() => onNewSession?.(newSessionPath)} />
1555
+ )
1556
+ }
1557
+ count={parent.sessionCount}
1558
+ dragging={dragging}
1559
+ dragHandleProps={dragHandleProps}
1560
+ emphasis
1561
+ icon={<Codicon className="shrink-0 text-(--ui-text-tertiary)" name="repo" size="0.75rem" />}
1562
+ label={parent.label}
1563
+ onToggle={() => setOpen(value => !value)}
1564
+ open={open}
1565
+ reorderable={reorderable}
1566
+ />
1567
+ {open &&
1568
+ (soleWorktree ? (
1569
+ // Collapsed: the repo's sessions hang straight off the header.
1570
+ <>
1571
+ {renderRows(visibleSessions)}
1572
+ {hiddenCount > 0 && (
1573
+ <WorkspaceShowMoreButton
1574
+ count={Math.min(WORKSPACE_PAGE, hiddenCount)}
1575
+ label={parent.label}
1576
+ onClick={() => setVisibleCount(count => count + WORKSPACE_PAGE)}
1577
+ />
1578
+ )}
1579
+ </>
1580
+ ) : (
1581
+ // Indent the worktrees under their repo; keep the column pinned to the
1582
+ // rail so long branch labels truncate instead of shoving controls off.
1583
+ <div className="grid grid-cols-[minmax(0,1fr)] gap-px pl-2.5">
1584
+ {onReorderWorktree ? (
1585
+ <ReorderableList
1586
+ ids={parent.groups.map(group => group.id)}
1587
+ onReorder={ids => onReorderWorktree(parent.id, ids)}
1588
+ sensors={dndSensors}
1589
+ >
1590
+ {groupNodes}
1591
+ </ReorderableList>
1592
+ ) : (
1593
+ groupNodes
1594
+ )}
1595
+ </div>
1596
+ ))}
1597
+ </div>
1598
+ )
1599
+ }
1600
+
1601
+ interface SortableWorkspaceParentProps {
1602
+ parent: SidebarWorkspaceTree
1603
+ renderRows: (sessions: SessionInfo[]) => React.ReactNode
1604
+ onNewSession?: (path: null | string) => void
1605
+ onReorderWorktree?: (parentId: string, ids: string[]) => void
1606
+ dndSensors?: ReturnType<typeof useSensors>
1607
+ }
1608
+
1609
+ function SortableSidebarWorkspaceParent(props: SortableWorkspaceParentProps) {
1610
+ return <SidebarWorkspaceParent {...props} {...useSortableBindings(props.parent.id)} />
908
1611
  }
909
1612
 
910
1613
  function SidebarCount({ children }: { children: React.ReactNode }) {
911
1614
  return <span className="text-[0.6875rem] font-medium text-(--ui-text-quaternary)">{children}</span>
912
1615
  }
913
1616
 
1617
+ // Reveals the next page of already-loaded rows within a workspace/worktree.
1618
+ function WorkspaceShowMoreButton({ count, label, onClick }: { count: number; label: string; onClick: () => void }) {
1619
+ const { t } = useI18n()
1620
+ const text = t.sidebar.showMoreIn(count, label)
1621
+
1622
+ return (
1623
+ <Tip label={text}>
1624
+ <button
1625
+ aria-label={text}
1626
+ className="ml-auto grid size-5 place-items-center rounded-sm bg-transparent text-(--ui-text-tertiary) transition-colors hover:bg-(--ui-control-hover-background) hover:text-foreground"
1627
+ onClick={onClick}
1628
+ type="button"
1629
+ >
1630
+ <Codicon name="ellipsis" size="0.75rem" />
1631
+ </button>
1632
+ </Tip>
1633
+ )
1634
+ }
1635
+
1636
+ // Reorder handle that lives in the header's leading-icon slot: the resting icon
1637
+ // fades out and a grabber fades in on hover/drag (same swap as the session row),
1638
+ // so the drag affordance never eats header width on the right.
1639
+ function WorkspaceReorderHandle({
1640
+ dragHandleProps,
1641
+ dragging,
1642
+ icon,
1643
+ label
1644
+ }: {
1645
+ dragHandleProps?: React.HTMLAttributes<HTMLElement>
1646
+ dragging: boolean
1647
+ icon: React.ReactNode
1648
+ label: string
1649
+ }) {
1650
+ return (
1651
+ <span
1652
+ {...dragHandleProps}
1653
+ aria-label={label}
1654
+ className="group/handle relative -my-0.5 grid size-4 shrink-0 cursor-grab touch-none place-items-center self-stretch overflow-hidden active:cursor-grabbing"
1655
+ data-reorder-handle
1656
+ onClick={event => event.stopPropagation()}
1657
+ >
1658
+ <span
1659
+ className={cn(
1660
+ 'grid place-items-center transition-opacity group-hover/handle:opacity-0 group-focus-within/handle:opacity-0',
1661
+ dragging && 'opacity-0'
1662
+ )}
1663
+ >
1664
+ {icon}
1665
+ </span>
1666
+ <Codicon
1667
+ className={cn(
1668
+ 'absolute text-(--ui-text-quaternary) opacity-0 transition-opacity group-hover/handle:opacity-80 group-focus-within/handle:opacity-80 hover:text-(--ui-text-secondary)',
1669
+ dragging && 'text-(--ui-text-secondary) opacity-100'
1670
+ )}
1671
+ name="grabber"
1672
+ size="0.75rem"
1673
+ />
1674
+ </span>
1675
+ )
1676
+ }
1677
+
1678
+ // "+" affordance shared by repo and worktree headers — reveals on header hover.
1679
+ function WorkspaceAddButton({ label, onClick }: { label: string; onClick: () => void }) {
1680
+ return (
1681
+ <Tip label={label}>
1682
+ <button
1683
+ aria-label={label}
1684
+ className="grid size-4 shrink-0 place-items-center rounded-sm bg-transparent text-(--ui-text-quaternary) opacity-0 transition-opacity hover:bg-(--ui-control-hover-background) hover:text-foreground group-hover/workspace:opacity-100"
1685
+ onClick={onClick}
1686
+ type="button"
1687
+ >
1688
+ <Codicon name="add" size="0.75rem" />
1689
+ </button>
1690
+ </Tip>
1691
+ )
1692
+ }
1693
+
1694
+ // Collapsible header shared by the repo (emphasis) and worktree levels: a
1695
+ // toggle button whose leading glyph doubles as the reorder handle, plus an
1696
+ // optional trailing action (the +).
1697
+ function WorkspaceHeader({
1698
+ action,
1699
+ count,
1700
+ dragHandleProps,
1701
+ dragging = false,
1702
+ emphasis = false,
1703
+ icon,
1704
+ label,
1705
+ onToggle,
1706
+ open,
1707
+ reorderable = false
1708
+ }: {
1709
+ action?: React.ReactNode
1710
+ count: React.ReactNode
1711
+ dragHandleProps?: React.HTMLAttributes<HTMLElement>
1712
+ dragging?: boolean
1713
+ emphasis?: boolean
1714
+ icon: React.ReactNode
1715
+ label: string
1716
+ onToggle: () => void
1717
+ open: boolean
1718
+ reorderable?: boolean
1719
+ }) {
1720
+ const { t } = useI18n()
1721
+
1722
+ return (
1723
+ <div
1724
+ className={cn(
1725
+ 'group/workspace flex min-h-6 items-center gap-1 px-2 pt-1 text-[0.6875rem]',
1726
+ emphasis ? 'font-semibold text-(--ui-text-secondary)' : 'font-medium text-(--ui-text-tertiary)'
1727
+ )}
1728
+ >
1729
+ <button
1730
+ className={cn(
1731
+ 'flex min-w-0 flex-1 items-center gap-1.5 bg-transparent text-left',
1732
+ emphasis ? 'hover:text-foreground' : 'hover:text-(--ui-text-secondary)'
1733
+ )}
1734
+ onClick={onToggle}
1735
+ type="button"
1736
+ >
1737
+ {reorderable ? (
1738
+ <WorkspaceReorderHandle
1739
+ dragging={dragging}
1740
+ dragHandleProps={dragHandleProps}
1741
+ icon={icon}
1742
+ label={t.sidebar.reorderWorkspace(label)}
1743
+ />
1744
+ ) : (
1745
+ icon
1746
+ )}
1747
+ <span className="min-w-0 truncate">{label}</span>
1748
+ <span className="shrink-0">
1749
+ <SidebarCount>{count}</SidebarCount>
1750
+ </span>
1751
+ <DisclosureCaret
1752
+ className="shrink-0 text-(--ui-text-tertiary) opacity-0 transition group-hover/workspace:opacity-100"
1753
+ open={open}
1754
+ />
1755
+ </button>
1756
+ {action}
1757
+ </div>
1758
+ )
1759
+ }
1760
+
914
1761
  interface SortableSessionRowProps {
915
1762
  session: SessionInfo
916
1763
  isPinned: boolean
@@ -925,25 +1772,3 @@ interface SortableSessionRowProps {
925
1772
  function SortableSidebarSessionRow(props: SortableSessionRowProps) {
926
1773
  return <SidebarSessionRow {...props} {...useSortableBindings(props.session.id)} />
927
1774
  }
928
-
929
- interface SidebarLoadMoreRowProps {
930
- loading: boolean
931
- onClick: () => void
932
- step: number
933
- }
934
-
935
- function SidebarLoadMoreRow({ loading, onClick, step }: SidebarLoadMoreRowProps) {
936
- const label = loading ? 'Loading…' : step > 0 ? `Load ${step} more` : 'Load more'
937
-
938
- return (
939
- <button
940
- className="flex min-h-5 cursor-pointer items-center gap-1 self-start bg-transparent pl-2 text-left text-[0.6875rem] text-(--ui-text-tertiary) transition-colors duration-100 ease-out hover:text-foreground hover:transition-none disabled:cursor-default disabled:opacity-60 disabled:hover:text-(--ui-text-tertiary)"
941
- disabled={loading}
942
- onClick={onClick}
943
- type="button"
944
- >
945
- <Codicon className="opacity-70" name={loading ? 'loading' : 'chevron-down'} size="0.75rem" spinning={loading} />
946
- <span>{label}</span>
947
- </button>
948
- )
949
- }