@brimveyn/aimux 1.21.0 → 1.22.2

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 (220) hide show
  1. package/README.md +27 -25
  2. package/package.json +2 -2
  3. package/src/app-runtime/auto-commit-driver.ts +22 -27
  4. package/src/app-runtime/backend-attach-runtime.ts +43 -36
  5. package/src/app-runtime/backend-runtime-events.ts +107 -88
  6. package/src/app-runtime/editor-actions.ts +201 -0
  7. package/src/app-runtime/git-actions.ts +197 -0
  8. package/src/app-runtime/measure-ref.ts +19 -0
  9. package/src/app-runtime/navigation-actions.ts +213 -0
  10. package/src/app-runtime/project-actions.ts +188 -0
  11. package/src/app-runtime/prompt-injection.ts +123 -0
  12. package/src/app-runtime/selection.ts +29 -0
  13. package/src/app-runtime/settings-actions.ts +156 -0
  14. package/src/app-runtime/setup-actions.ts +237 -0
  15. package/src/app-runtime/side-effect-context.ts +33 -0
  16. package/src/app-runtime/side-effects.ts +302 -1541
  17. package/src/app-runtime/snippet-actions.ts +3 -2
  18. package/src/app-runtime/tab-actions.ts +218 -0
  19. package/src/app-runtime/tab-runtime-timeouts.ts +1 -1
  20. package/src/app-runtime/use-auto-commit-driver.ts +14 -13
  21. package/src/app-runtime/use-backend-runtime.ts +12 -11
  22. package/src/app-runtime/use-directory-search.ts +5 -4
  23. package/src/app-runtime/use-mouse-handlers.ts +2 -1
  24. package/src/app-runtime/{use-workspace-autosave.ts → use-project-autosave.ts} +4 -4
  25. package/src/app-runtime/use-renderer-bindings.ts +2 -1
  26. package/src/app-runtime/use-setup-runner.ts +96 -0
  27. package/src/app-runtime/use-terminal-resize.ts +18 -14
  28. package/src/app-runtime/workspace-actions.ts +366 -0
  29. package/src/app-runtime/workspace-activity.ts +138 -0
  30. package/src/app-runtime/workspace-naming.ts +90 -0
  31. package/src/app.tsx +93 -48
  32. package/src/assets/sounds/bell.wav +0 -0
  33. package/src/assets/sounds/ding.wav +0 -0
  34. package/src/assets/sounds/plane.wav +0 -0
  35. package/src/cli/client/daemon-client.ts +2 -2
  36. package/src/cli/client/project-resolver.ts +112 -0
  37. package/src/cli/commands/project/close.ts +32 -0
  38. package/src/cli/commands/project/create.ts +93 -0
  39. package/src/cli/commands/project/list.ts +25 -0
  40. package/src/cli/commands/project/show.ts +32 -0
  41. package/src/cli/commands/{workspace → project}/switch.ts +19 -19
  42. package/src/cli/commands/tab/await.ts +2 -2
  43. package/src/cli/commands/tab/close.ts +3 -3
  44. package/src/cli/commands/tab/create.ts +74 -73
  45. package/src/cli/commands/tab/focus.ts +3 -3
  46. package/src/cli/commands/tab/list.ts +6 -6
  47. package/src/cli/commands/tab/run.ts +2 -2
  48. package/src/cli/commands/tab/send.ts +2 -2
  49. package/src/cli/commands/tab/snapshot.ts +2 -2
  50. package/src/cli/commands/tab/tail.ts +2 -2
  51. package/src/cli/commands/tab/wait.ts +2 -2
  52. package/src/cli/commands/worker/await.ts +3 -3
  53. package/src/cli/commands/worker/doctor.ts +30 -30
  54. package/src/cli/commands/worker/list.ts +15 -15
  55. package/src/cli/commands/worker/prompt.ts +3 -3
  56. package/src/cli/commands/worker/run.ts +23 -23
  57. package/src/cli/commands/worker/shared.ts +53 -59
  58. package/src/cli/commands/worker/stop.ts +29 -29
  59. package/src/cli/commands/worker/submit.ts +3 -3
  60. package/src/cli/commands/{worktree → workspace}/create-core.ts +26 -26
  61. package/src/cli/commands/workspace/create.ts +30 -68
  62. package/src/cli/commands/workspace/list.ts +52 -9
  63. package/src/cli/commands/workspace/remove.ts +81 -0
  64. package/src/cli/completion/plan.ts +2 -2
  65. package/src/cli/completion/sources.ts +18 -18
  66. package/src/cli/context.ts +13 -13
  67. package/src/cli/flags.ts +30 -10
  68. package/src/cli/index.ts +26 -16
  69. package/src/cli/output.ts +1 -1
  70. package/src/cli/registry.ts +12 -12
  71. package/src/config/loader.ts +16 -5
  72. package/src/config.ts +70 -112
  73. package/src/daemon/catalog-writer.ts +67 -67
  74. package/src/daemon/daemon.ts +205 -201
  75. package/src/daemon/session-manager.ts +39 -39
  76. package/src/daemon/session-registry.ts +14 -14
  77. package/src/git/divergence.ts +33 -2
  78. package/src/git/git-poller.ts +1 -1
  79. package/src/git/git-status.ts +2 -2
  80. package/src/git/{move-worktree.ts → move-workspace.ts} +5 -5
  81. package/src/git/pr-status.ts +1 -1
  82. package/src/git/repo-discovery.ts +1 -1
  83. package/src/git/use-repo-discovery.ts +1 -1
  84. package/src/git/workspace-branch-poller.ts +54 -0
  85. package/src/git/workspace-divergence-poller.ts +65 -0
  86. package/src/git/worktree.ts +29 -0
  87. package/src/index.tsx +4 -4
  88. package/src/input/keymap/help-entries.ts +7 -5
  89. package/src/input/modes/bridge.ts +13 -12
  90. package/src/input/modes/handlers/shared.ts +1 -1
  91. package/src/input/modes/transitions.ts +34 -21
  92. package/src/input/modes/types.ts +48 -31
  93. package/src/ipc/manager-protocol.ts +44 -44
  94. package/src/ipc/protocol.ts +192 -166
  95. package/src/platform/play-sound.ts +184 -0
  96. package/src/platform/project-search.ts +8 -8
  97. package/src/platform/worktree-paths.ts +8 -6
  98. package/src/pty/assistant-question-extractor.ts +1 -1
  99. package/src/pty/assistant-status-detection-loop.ts +79 -55
  100. package/src/pty/assistant-status-detector.ts +40 -15
  101. package/src/pty/command-registry.ts +81 -1
  102. package/src/restart-terminal-manager.ts +1 -1
  103. package/src/services/ai-usage/provider.ts +9 -2
  104. package/src/services/ai-usage/spawn.ts +19 -6
  105. package/src/session-backend/bootstrap.ts +4 -4
  106. package/src/session-backend/local-session-backend.ts +78 -75
  107. package/src/session-backend/remote-session-backend.ts +64 -52
  108. package/src/session-backend/types.ts +43 -32
  109. package/src/settings/live.ts +90 -0
  110. package/src/settings/search.ts +43 -0
  111. package/src/settings/sections/about.ts +49 -0
  112. package/src/settings/sections/appearance.ts +54 -0
  113. package/src/settings/sections/automation.ts +105 -0
  114. package/src/settings/sections/commands.ts +72 -0
  115. package/src/settings/sections/editor.ts +56 -0
  116. package/src/settings/sections/experimental.ts +57 -0
  117. package/src/settings/sections/git.ts +112 -0
  118. package/src/settings/sections/index.ts +95 -0
  119. package/src/settings/sections/integrations.ts +23 -0
  120. package/src/settings/sections/layout.ts +72 -0
  121. package/src/settings/sections/notifications.ts +99 -0
  122. package/src/settings/sections/setup.ts +54 -0
  123. package/src/settings/sections/status-bar.ts +63 -0
  124. package/src/settings/settings-store.ts +207 -0
  125. package/src/settings/types.ts +108 -0
  126. package/src/snippets/run-shell-var.ts +48 -22
  127. package/src/state/actions.ts +342 -0
  128. package/src/state/app-store.ts +2 -1
  129. package/src/state/bars.ts +1 -1
  130. package/src/state/dispatch-ref.ts +1 -1
  131. package/src/state/layout-resize.ts +2 -2
  132. package/src/state/project-catalog.ts +275 -0
  133. package/src/state/project-data.ts +143 -0
  134. package/src/state/{session-persistence.ts → project-persistence.ts} +74 -68
  135. package/src/state/project-save.ts +46 -0
  136. package/src/state/project-workspaces.ts +392 -0
  137. package/src/state/reducers/auto-commit-state.ts +11 -10
  138. package/src/state/reducers/git-commit-modal-state.ts +122 -0
  139. package/src/state/reducers/git-mode-state.ts +1 -1
  140. package/src/state/reducers/git-panel-state.ts +10 -6
  141. package/src/state/reducers/modal-state.ts +290 -681
  142. package/src/state/reducers/multi-repo-state.ts +3 -6
  143. package/src/state/reducers/project-state.ts +312 -0
  144. package/src/state/reducers/settings-state.ts +80 -0
  145. package/src/state/reducers/tab-state.ts +107 -60
  146. package/src/state/reducers/ui-state.ts +4 -3
  147. package/src/state/selectors.ts +49 -37
  148. package/src/state/store.ts +57 -24
  149. package/src/state/types.ts +244 -405
  150. package/src/state/validation.ts +14 -12
  151. package/src/terminal-manager/manager-client.ts +32 -32
  152. package/src/terminal-manager/terminal-manager.ts +24 -24
  153. package/src/ui/components/git/git-panel.tsx +2 -2
  154. package/src/ui/components/git/git-view.tsx +12 -12
  155. package/src/ui/components/git/image-diff/image-diff-view.tsx +1 -1
  156. package/src/ui/components/git/pane/git-pane-widget.tsx +7 -7
  157. package/src/ui/components/layout/sidebar/project-list.tsx +444 -0
  158. package/src/ui/components/layout/sidebar/tab-item.tsx +27 -12
  159. package/src/ui/components/layout/sidebar/use-sidebar-auto-scroll.ts +1 -1
  160. package/src/ui/components/layout/sidebar/workspace-row.tsx +253 -0
  161. package/src/ui/components/layout/status-bar.tsx +6 -2
  162. package/src/ui/components/layout/terminal-pane.tsx +34 -29
  163. package/src/ui/components/layout/top-tab-bar.tsx +46 -36
  164. package/src/ui/components/modals/git/git-commit-modal.tsx +8 -8
  165. package/src/ui/components/modals/{sessions/create-session-modal.tsx → projects/create-project-modal.tsx} +12 -12
  166. package/src/ui/components/modals/{sessions/session-name-modal.tsx → projects/project-name-modal.tsx} +2 -2
  167. package/src/ui/components/modals/{sessions/session-picker-modal.tsx → projects/project-picker-modal.tsx} +33 -35
  168. package/src/ui/components/modals/settings/settings-search-modal.tsx +70 -0
  169. package/src/ui/components/modals/shared/form.tsx +34 -22
  170. package/src/ui/components/modals/shared/picker.tsx +10 -10
  171. package/src/ui/components/modals/shared/{worktree-delete-confirm.tsx → workspace-delete-confirm.tsx} +9 -9
  172. package/src/ui/components/modals/tabs/new-tab-modal.tsx +30 -324
  173. package/src/ui/components/modals/workspace/create-workspace-modal.tsx +101 -0
  174. package/src/ui/components/modals/{worktree/worktree-move-confirm-modal.tsx → workspace/workspace-move-confirm-modal.tsx} +5 -5
  175. package/src/ui/components/modals/{worktree/worktree-move-modal.tsx → workspace/workspace-move-modal.tsx} +29 -27
  176. package/src/ui/components/primitives/input-field.tsx +7 -3
  177. package/src/ui/components/primitives/surface.tsx +3 -0
  178. package/src/ui/components/settings/row-value.tsx +74 -0
  179. package/src/ui/components/settings/settings-row.tsx +78 -0
  180. package/src/ui/components/settings/settings-view.tsx +169 -0
  181. package/src/ui/components/setup/setup-widget.tsx +152 -0
  182. package/src/ui/flash/build-labels.ts +27 -27
  183. package/src/ui/hooks/use-activity-sprite.ts +73 -0
  184. package/src/ui/hooks/use-scroll-active-into-view.ts +26 -0
  185. package/src/ui/{session-ordering.ts → project-ordering.ts} +4 -4
  186. package/src/ui/root.tsx +139 -121
  187. package/src/ui/status-bar-model.ts +48 -34
  188. package/src/ui/terminal-graphics/kitty.ts +28 -2
  189. package/src/ui/terminal-graphics/sprites/done@330/0.png +0 -0
  190. package/src/ui/terminal-graphics/sprites/done@330/1.png +0 -0
  191. package/src/ui/terminal-graphics/sprites/idle@1000/0.png +0 -0
  192. package/src/ui/terminal-graphics/sprites/idle@1000/1.png +0 -0
  193. package/src/ui/terminal-graphics/sprites/waiting@170/0.png +0 -0
  194. package/src/ui/terminal-graphics/sprites/waiting@170/1.png +0 -0
  195. package/src/ui/terminal-graphics/sprites/working@150/0.png +0 -0
  196. package/src/ui/terminal-graphics/sprites/working@150/1.png +0 -0
  197. package/src/ui/terminal-graphics/sprites/working@150/2.png +0 -0
  198. package/src/ui/terminal-graphics/sprites/working@150/3.png +0 -0
  199. package/src/ui/terminal-graphics/sprites.ts +246 -0
  200. package/src/ui/truncate.ts +7 -0
  201. package/src/ui/widgets/registry.tsx +6 -3
  202. package/src/ui/widgets/widget-context-menu.ts +26 -10
  203. package/src/update.ts +2 -2
  204. package/src/app-runtime/session-actions.ts +0 -187
  205. package/src/cli/client/workspace-resolver.ts +0 -110
  206. package/src/cli/commands/workspace/close.ts +0 -32
  207. package/src/cli/commands/workspace/show.ts +0 -32
  208. package/src/cli/commands/worktree/create.ts +0 -55
  209. package/src/cli/commands/worktree/list.ts +0 -68
  210. package/src/cli/commands/worktree/remove.ts +0 -81
  211. package/src/git/worktree-branch-poller.ts +0 -54
  212. package/src/git/worktree-divergence-poller.ts +0 -59
  213. package/src/state/reducers/session-state.ts +0 -262
  214. package/src/state/session-catalog.ts +0 -143
  215. package/src/state/session-worktrees.ts +0 -260
  216. package/src/state/workspace-save.ts +0 -43
  217. package/src/ui/components/layout/sidebar/use-top-tab-bar-auto-scroll.ts +0 -30
  218. package/src/ui/components/layout/sidebar/workspace-list.tsx +0 -418
  219. package/src/ui/components/layout/sidebar/worktree-row.tsx +0 -149
  220. /package/src/ui/{components/git/image-diff → terminal-graphics}/dimensions.ts +0 -0
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # aimux
2
2
 
3
3
  A terminal multiplexer for AI CLIs. Run Claude, Codex, OpenCode, and normal
4
- shell tabs side by side in one TUI with persistent workspaces, split panes,
4
+ shell tabs side by side in one TUI with persistent projects, split panes,
5
5
  snippets, themes, and fully configurable keymaps.
6
6
 
7
7
  ![Built with Bun](https://img.shields.io/badge/runtime-Bun-f9f1e1)
@@ -12,19 +12,19 @@ snippets, themes, and fully configurable keymaps.
12
12
 
13
13
  ## Features
14
14
 
15
- - multi-workspace workflow with a dedicated workspace picker
15
+ - multi-project workflow with a dedicated project picker
16
16
  - tabs for `claude`, `codex`, `opencode`, `grok`, `kimi`, and `terminal`
17
17
  - split panes with pane focus and resize shortcuts
18
- - persistent workspaces with saved layout and tab state
18
+ - persistent projects with saved layout and tab state
19
19
  - profile-isolated config, catalogs, daemon sockets, and runtime state
20
20
  - typed keymap customization through `@brimveyn/aimux-config`
21
21
  - snippets catalog and snippet picker
22
22
  - configurable git pane (embedded in the sidebar or as a standalone pane) and a
23
23
  dedicated [git mode](docs/guide/git-mode.md) for review / stage / commit /
24
24
  push with a split or stacked diff view and shiki-powered highlighting
25
- - [git worktrees](docs/guide/worktrees.md) for running agents on parallel
26
- branches — create per-branch worktrees, review each against its base, and
27
- squash-move a worktree's work into another
25
+ - [git worktrees](docs/guide/workspaces.md) for running agents on parallel
26
+ branches — create per-branch workspaces, review each against its base, and
27
+ squash-move a workspace's work into another
28
28
  - built-in help generated from the resolved keymap
29
29
  - theme picker with 67 built-in themes (shiki catalog + aimux house themes) and a `/` filter
30
30
 
@@ -59,18 +59,18 @@ Create `~/.config/aimux/default/aimux.config.ts`:
59
59
  import { defineConfig, actions } from '@brimveyn/aimux-config'
60
60
 
61
61
  export default defineConfig({
62
- sessionBar: {
62
+ projectBar: {
63
63
  initialPosition: 'top',
64
64
  initialVisible: true,
65
65
  },
66
66
 
67
67
  keymaps: (k) =>
68
- k.mode('navigation', (m) => m.map('<C-p>', actions.sessionPicker, 'Workspace picker')),
68
+ k.mode('navigation', (m) => m.map('<C-g>', actions.projectPicker, 'Project picker')),
69
69
  })
70
70
  ```
71
71
 
72
72
  Typed config is startup intent, not app-managed persisted state. Fields like
73
- `sessionBar.initialVisible` and `sessionBar.initialPosition` are reapplied on
73
+ `projectBar.initialVisible` and `projectBar.initialPosition` are reapplied on
74
74
  every launch, so runtime UI changes for those fields do not stick while the
75
75
  config entry remains set.
76
76
 
@@ -80,7 +80,7 @@ Then start the app:
80
80
  aimux
81
81
  ```
82
82
 
83
- On first launch, use the workspace picker flow to create your first workspace.
83
+ On first launch, use the project picker flow to create your first project.
84
84
 
85
85
  For the full setup path, see [`docs/getting-started.md`](docs/getting-started.md).
86
86
 
@@ -109,7 +109,7 @@ profile. See [`docs/concepts/profiles.md`](docs/concepts/profiles.md).
109
109
 
110
110
  - `aimux.config.ts` or `aimux.config.js` - typed user config
111
111
  - `aimux.json` - app-managed preferences and runtime state
112
- - `aimux-sessions.json` - workspace catalog and workspace snapshots
112
+ - `aimux-projects.json` - project catalog and project snapshots
113
113
  - `aimux-snippets.json` - snippet catalog
114
114
 
115
115
  Rule of thumb:
@@ -120,17 +120,19 @@ Rule of thumb:
120
120
 
121
121
  See [`docs/concepts/config-and-state.md`](docs/concepts/config-and-state.md).
122
122
 
123
- ### Workspaces
123
+ ### Projects
124
124
 
125
- Workspaces are the top-level user-facing concept in `aimux`. Internally, the
126
- runtime still uses `session` naming for compatibility. A workspace can have:
125
+ A project is a repository you add by picking a folder. Inside it live
126
+ workspaces (git worktrees), and inside those live tabs one creation action
127
+ per level: `Ctrl+G` for a project, `Ctrl+P` for a workspace, `Ctrl+N` for a
128
+ tab. A project can have:
127
129
 
128
130
  - a name
129
131
  - an optional project directory
130
- - a persisted workspace snapshot
131
- - an order in the workspace bar and workspace picker
132
+ - a persisted project snapshot
133
+ - an order in the project bar and project picker
132
134
 
133
- See [`docs/guide/sessions.md`](docs/guide/sessions.md).
135
+ See [`docs/guide/projects.md`](docs/guide/projects.md).
134
136
 
135
137
  ### Keymaps
136
138
 
@@ -149,13 +151,13 @@ See [`docs/guide/keymaps.md`](docs/guide/keymaps.md).
149
151
  - `i` - focus terminal
150
152
  - `Ctrl+Z` - leave terminal-input mode
151
153
  - `Ctrl+N` - open new-tab modal
152
- - `Ctrl+G` - open workspace picker
154
+ - `Ctrl+G` - open project picker
153
155
  - `Ctrl+S` - open snippet picker
154
156
  - `Ctrl+T` - open theme picker
155
157
  - `Ctrl+B` - toggle sidebar
156
158
  - `Ctrl+D` - enter git mode
157
- - `Ctrl+W b` - toggle workspace bar
158
- - `Ctrl+W 1` through `Ctrl+W 9` - switch workspaces by index
159
+ - `Ctrl+W b` - toggle project bar
160
+ - `Ctrl+W 1` through `Ctrl+W 9` - switch projects by index
159
161
 
160
162
  The help modal reflects the resolved keymap, so it includes your overrides.
161
163
 
@@ -175,10 +177,10 @@ aimux restart-terminal-manager
175
177
  See [`docs/reference/cli.md`](docs/reference/cli.md) for behavior details.
176
178
 
177
179
  For agent orchestration, prefer the named `aimux worker` commands. They combine
178
- isolated worktree creation, prompt dispatch, authoritative turn waiting, fleet
180
+ isolated workspace creation, prompt dispatch, authoritative turn waiting, fleet
179
181
  inspection, and guarded cleanup without shell wrappers or `jq`. Pin the target
180
- with `--workspace` (or `AIMUX_WORKSPACE`) for anything long-running: the default
181
- follows whichever workspace the UI opened last.
182
+ with `--project` (or `AIMUX_PROJECT`) for anything long-running: the default
183
+ follows whichever project the UI opened last.
182
184
 
183
185
  ## Runtime Model
184
186
 
@@ -198,7 +200,7 @@ See [`docs/developer/architecture.md`](docs/developer/architecture.md).
198
200
  - [`docs/getting-started.md`](docs/getting-started.md)
199
201
  - [`docs/concepts/config-and-state.md`](docs/concepts/config-and-state.md)
200
202
  - [`docs/concepts/profiles.md`](docs/concepts/profiles.md)
201
- - [`docs/guide/sessions.md`](docs/guide/sessions.md)
203
+ - [`docs/guide/projects.md`](docs/guide/projects.md)
202
204
  - [`docs/guide/keymaps.md`](docs/guide/keymaps.md)
203
205
  - [`docs/guide/themes.md`](docs/guide/themes.md)
204
206
  - [`docs/reference/cli.md`](docs/reference/cli.md)
@@ -233,7 +235,7 @@ not collide with a globally installed `aimux` instance.
233
235
  and the source of the bundled theme catalog.
234
236
  - [herdr](https://github.com/ogulcancelik/herdr) by @ogulcancelik — the
235
237
  per-CLI assistant status heuristics (working / waiting-input / idle)
236
- used in the workspace bar are adapted from herdr's `detect.rs` rule tables.
238
+ used in the project bar are adapted from herdr's `detect.rs` rule tables.
237
239
 
238
240
  ## License
239
241
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brimveyn/aimux",
3
- "version": "1.21.0",
3
+ "version": "1.22.2",
4
4
  "description": "A terminal multiplexer for AI CLIs. Run Claude, Codex, OpenCode, Kimi side-by-side with tabbed navigation, split panes, and persistent sessions.",
5
5
  "keywords": [
6
6
  "ai",
@@ -66,7 +66,7 @@
66
66
  "bump:terminal_manager": "bun run scripts/bump-protocol.ts terminal-manager"
67
67
  },
68
68
  "dependencies": {
69
- "@brimveyn/aimux-config": "0.8.6",
69
+ "@brimveyn/aimux-config": "0.10.0",
70
70
  "@opentui/core": "^0.1.90",
71
71
  "@opentui/react": "^0.1.90",
72
72
  "@resvg/resvg-wasm": "^2.6.2",
@@ -1,12 +1,7 @@
1
1
  import { $ } from 'bun'
2
2
 
3
- import type {
4
- AppAction,
5
- AppState,
6
- AssistantId,
7
- AutoCommitState,
8
- GitRefreshPayload,
9
- } from '../state/types'
3
+ import type { AppAction } from '../state/actions'
4
+ import type { AppState, AssistantId, AutoCommitState, GitRefreshPayload } from '../state/types'
10
5
 
11
6
  import { buildHeadlessInvocation, isSupportedProvider } from '../auto-commit/headless-commands'
12
7
  import { composePromptFromTemplate, loadBriefingTemplate } from '../auto-commit/prompt-loader'
@@ -22,7 +17,7 @@ export interface TriggerInput {
22
17
  assistant: AssistantId
23
18
  hasProjectPath: boolean
24
19
  git: GitRefreshPayload
25
- sessionId: string
20
+ projectId: string
26
21
  state: AutoCommitState
27
22
  currentHash: string
28
23
  }
@@ -33,7 +28,7 @@ export function shouldTriggerAutoCommit(input: TriggerInput): boolean {
33
28
  if (!input.hasProjectPath) return false
34
29
  if (input.git.files.length === 0) return false
35
30
 
36
- const existing = input.state.bySession[input.sessionId]
31
+ const existing = input.state.byProject[input.projectId]
37
32
  if (!existing || existing.kind === 'idle') return true
38
33
  if (existing.workingTreeHash === input.currentHash) return false
39
34
  return true
@@ -61,14 +56,14 @@ async function getTemplate(deps: DriverDeps): Promise<string> {
61
56
  }
62
57
 
63
58
  const UNTRACKED_FILE_BYTES_CAP = 8_000
64
- const SESSION_TAIL_BYTES_CAP = 8_000
59
+ const PROJECT_TAIL_BYTES_CAP = 8_000
65
60
 
66
61
  export function extractSessionTail(buffer: string | undefined): string {
67
62
  if (!(buffer != null && buffer !== '')) return '[no session tail available]'
68
63
  const stripped = stripAnsi(buffer).trimEnd()
69
64
  if (stripped.length === 0) return '[no session tail available]'
70
- return stripped.length > SESSION_TAIL_BYTES_CAP
71
- ? stripped.slice(stripped.length - SESSION_TAIL_BYTES_CAP)
65
+ return stripped.length > PROJECT_TAIL_BYTES_CAP
66
+ ? stripped.slice(stripped.length - PROJECT_TAIL_BYTES_CAP)
72
67
  : stripped
73
68
  }
74
69
 
@@ -140,7 +135,7 @@ async function gatherContext(cwd: string, stagedOnly: boolean): Promise<Gathered
140
135
  }
141
136
 
142
137
  export interface ActivityTransitionArgs {
143
- sessionId: string
138
+ projectId: string
144
139
  tabId: string
145
140
  assistant: AssistantId
146
141
  projectPath: string | undefined
@@ -163,7 +158,7 @@ export async function onActivityTransition(
163
158
  enabled: config.enabled,
164
159
  git: args.git,
165
160
  hasProjectPath: !!(args.projectPath != null && args.projectPath !== ''),
166
- sessionId: args.sessionId,
161
+ projectId: args.projectId,
167
162
  state: state.autoCommit,
168
163
  })
169
164
  if (!should) return
@@ -180,15 +175,15 @@ export async function onManualTrigger(
180
175
  // Manual auto-commit triggers can fire outside git mode, so surface the
181
176
  // reason as a toast rather than an inline git-pane message that's unseen.
182
177
  toast.warning(`Auto-commit: ${reason}`)
183
- deps.dispatch({ sessionId: args.sessionId, type: 'auto-commit-clear' })
178
+ deps.dispatch({ projectId: args.projectId, type: 'auto-commit-clear' })
184
179
  }
185
180
  if (!config.enabled) return fail('disabled in config')
186
181
  if (!args.git || args.git.files.length === 0) return fail('no changes to summarise')
187
182
  if (!(args.projectPath != null && args.projectPath !== ''))
188
- return fail('no project path for current session')
183
+ return fail('no project path for current project')
189
184
 
190
185
  const currentHash = workingTreeHash(args.git)
191
- const existing = deps.getState().autoCommit.bySession[args.sessionId]
186
+ const existing = deps.getState().autoCommit.byProject[args.projectId]
192
187
  // An in-flight generation will dispatch ready/clear; just wait for it.
193
188
  if (existing && existing.kind === 'generating') return
194
189
  // Manual trigger is always user-intent to regenerate, even if a ready
@@ -213,11 +208,11 @@ async function runGeneration(
213
208
  if (!probe) return
214
209
  if (!isCommandAvailable(probe.executable)) return
215
210
 
216
- // Supersede any in-flight generation for this session. Without this, a
211
+ // Supersede any in-flight generation for this project. Without this, a
217
212
  // rapid sequence of activity transitions (each with a different working
218
213
  // tree hash) would leak concurrent headless subprocesses until each hit
219
214
  // its 60 s timeout — burning real API credits.
220
- const existing = deps.getState().autoCommit.bySession[args.sessionId]
215
+ const existing = deps.getState().autoCommit.byProject[args.projectId]
221
216
  if (existing && existing.kind === 'generating') {
222
217
  try {
223
218
  existing.abortController.abort()
@@ -229,7 +224,7 @@ async function runGeneration(
229
224
  const controller = new AbortController()
230
225
  deps.dispatch({
231
226
  abortController: controller,
232
- sessionId: args.sessionId,
227
+ projectId: args.projectId,
233
228
  startedAt: Date.now(),
234
229
  tabId: args.tabId,
235
230
  type: 'auto-commit-generation-started',
@@ -240,7 +235,7 @@ async function runGeneration(
240
235
  const stagedOnly = args.git ? hasStagedFiles(args.git) : false
241
236
  const ctx = await gatherContext(args.projectPath as string, stagedOnly)
242
237
  if (!ctx) {
243
- deps.dispatch({ sessionId: args.sessionId, type: 'auto-commit-clear' })
238
+ deps.dispatch({ projectId: args.projectId, type: 'auto-commit-clear' })
244
239
  return
245
240
  }
246
241
  const tab = deps.getState().tabs.find((t) => t.id === args.tabId)
@@ -252,7 +247,7 @@ async function runGeneration(
252
247
  config.models[args.assistant]
253
248
  )
254
249
  if (!finalInvocation) {
255
- deps.dispatch({ sessionId: args.sessionId, type: 'auto-commit-clear' })
250
+ deps.dispatch({ projectId: args.projectId, type: 'auto-commit-clear' })
256
251
  return
257
252
  }
258
253
 
@@ -262,13 +257,13 @@ async function runGeneration(
262
257
  timeoutMs: config.timeoutMs,
263
258
  })
264
259
  if (!parsed) {
265
- deps.dispatch({ sessionId: args.sessionId, type: 'auto-commit-clear' })
260
+ deps.dispatch({ projectId: args.projectId, type: 'auto-commit-clear' })
266
261
  return
267
262
  }
268
263
  deps.dispatch({
269
264
  body: parsed.body,
270
265
  generatedAt: Date.now(),
271
- sessionId: args.sessionId,
266
+ projectId: args.projectId,
272
267
  title: parsed.title,
273
268
  type: 'auto-commit-generation-ready',
274
269
  workingTreeHash: currentHash,
@@ -277,14 +272,14 @@ async function runGeneration(
277
272
 
278
273
  export function onGitRefresh(
279
274
  deps: DriverDeps,
280
- sessionId: string,
275
+ projectId: string,
281
276
  payload: GitRefreshPayload
282
277
  ): void {
283
278
  const state = deps.getState()
284
- const current = state.autoCommit.bySession[sessionId]
279
+ const current = state.autoCommit.byProject[projectId]
285
280
  if (!current || current.kind === 'idle') return
286
281
  const newHash = workingTreeHash(payload)
287
282
  if (newHash !== current.workingTreeHash) {
288
- deps.dispatch({ sessionId, type: 'auto-commit-clear' })
283
+ deps.dispatch({ projectId, type: 'auto-commit-clear' })
289
284
  }
290
285
  }
@@ -1,7 +1,8 @@
1
1
  import type { MutableRefObject } from 'react'
2
2
 
3
3
  import type { SessionBackend } from '../session-backend/types'
4
- import type { AppAction, LayoutState, TabSession, WorkspaceSnapshotV1 } from '../state/types'
4
+ import type { AppAction } from '../state/actions'
5
+ import type { LayoutState, ProjectSnapshotV1, TabSession } from '../state/types'
5
6
 
6
7
  import { logInputDebug } from '../debug/input-log'
7
8
  import {
@@ -10,9 +11,10 @@ import {
10
11
  getSnapshotTrees,
11
12
  toTerminalContentSize,
12
13
  } from '../state/layout-resize'
14
+ import { seedTabActivity } from './workspace-activity'
13
15
 
14
16
  export function resizeSnapshotPanes(
15
- snapshot: WorkspaceSnapshotV1 | undefined,
17
+ snapshot: ProjectSnapshotV1 | undefined,
16
18
  layoutRef: MutableRefObject<LayoutState>,
17
19
  backend: SessionBackend
18
20
  ): void {
@@ -33,64 +35,69 @@ export function resizeSnapshotPanes(
33
35
 
34
36
  function mergeSnapshotTabMetadata(
35
37
  tabs: TabSession[],
36
- workspaceSnapshot: WorkspaceSnapshotV1 | undefined
38
+ projectSnapshot: ProjectSnapshotV1 | undefined
37
39
  ): TabSession[] {
38
- if (!workspaceSnapshot) return tabs
39
- const persistedById = new Map(workspaceSnapshot.tabs.map((tab) => [tab.id, tab]))
40
+ if (!projectSnapshot) return tabs
41
+ const persistedById = new Map(projectSnapshot.tabs.map((tab) => [tab.id, tab]))
40
42
  return tabs.map((tab) => {
41
43
  const persisted = persistedById.get(tab.id)
42
- return persisted?.worktreeId != null &&
43
- persisted?.worktreeId !== '' &&
44
- !(tab.worktreeId != null && tab.worktreeId !== '')
45
- ? { ...tab, worktreeId: persisted.worktreeId }
44
+ return persisted?.workspaceId != null &&
45
+ persisted?.workspaceId !== '' &&
46
+ !(tab.workspaceId != null && tab.workspaceId !== '')
47
+ ? { ...tab, workspaceId: persisted.workspaceId }
46
48
  : tab
47
49
  })
48
50
  }
49
51
 
50
52
  function hydrateAttachedSession(
51
53
  dispatch: (action: AppAction) => void,
52
- sessionId: string,
53
- workspaceSnapshot: WorkspaceSnapshotV1 | undefined,
54
+ projectId: string,
55
+ projectSnapshot: ProjectSnapshotV1 | undefined,
54
56
  result: Awaited<ReturnType<SessionBackend['attach']>>,
55
57
  layoutRef: MutableRefObject<LayoutState>,
56
58
  backend: SessionBackend
57
59
  ): void {
58
60
  if (result) {
61
+ const tabs = mergeSnapshotTabMetadata(result.tabs, projectSnapshot)
59
62
  dispatch({
60
63
  activeTabId: result.activeTabId,
61
- layoutTree: workspaceSnapshot?.layoutTree,
62
- layoutTrees: workspaceSnapshot?.layoutTrees,
63
- tabGroupMap: workspaceSnapshot?.tabGroupMap,
64
- tabs: mergeSnapshotTabMetadata(result.tabs, workspaceSnapshot),
65
- type: 'hydrate-workspace',
64
+ layoutTree: projectSnapshot?.layoutTree,
65
+ layoutTrees: projectSnapshot?.layoutTrees,
66
+ tabGroupMap: projectSnapshot?.tabGroupMap,
67
+ tabs,
68
+ type: 'hydrate-project',
66
69
  })
67
- // Dispatch the session-status snapshot *after* hydrate-workspace so
68
- // sidebar chips reflect per-session state on attach without waiting
70
+ // Same reason as the project-status snapshot below, one level down: the
71
+ // per-tab statuses are edge-triggered, so a workspace whose agent has been
72
+ // waiting since before this client started would otherwise draw nothing.
73
+ seedTabActivity(tabs)
74
+ // Dispatch the project-status snapshot *after* hydrate-project so
75
+ // sidebar chips reflect per-project state on attach without waiting
69
76
  // for the next daemon-side status transition.
70
- for (const entry of result.initialSessionStatuses) {
71
- dispatch({ sessionId: entry.sessionId, status: entry.status, type: 'set-session-status' })
77
+ for (const entry of result.initialProjectStatuses) {
78
+ dispatch({ projectId: entry.projectId, status: entry.status, type: 'set-project-status' })
72
79
  }
73
- resizeSnapshotPanes(workspaceSnapshot, layoutRef, backend)
80
+ resizeSnapshotPanes(projectSnapshot, layoutRef, backend)
74
81
  return
75
82
  }
76
83
 
77
- if (!workspaceSnapshot) {
84
+ if (!projectSnapshot) {
78
85
  return
79
86
  }
80
87
 
81
88
  dispatch({
82
- sessionId,
83
- type: 'load-session',
84
- workspaceSnapshot,
89
+ projectId,
90
+ projectSnapshot,
91
+ type: 'load-project',
85
92
  })
86
- resizeSnapshotPanes(workspaceSnapshot, layoutRef, backend)
93
+ resizeSnapshotPanes(projectSnapshot, layoutRef, backend)
87
94
  }
88
95
 
89
96
  interface AttachCurrentSessionOptions {
90
97
  backend: SessionBackend
91
98
  dispatch: (action: AppAction) => void
92
- currentSessionId: string
93
- currentSessionWorkspaceSnapshot: Parameters<SessionBackend['attach']>[0]['workspaceSnapshot']
99
+ currentProjectId: string
100
+ currentProjectProjectSnapshot: Parameters<SessionBackend['attach']>[0]['projectSnapshot']
94
101
  layoutRef: MutableRefObject<LayoutState>
95
102
  attachRequestIdRef: MutableRefObject<number>
96
103
  }
@@ -98,8 +105,8 @@ interface AttachCurrentSessionOptions {
98
105
  export function attachCurrentSession({
99
106
  attachRequestIdRef,
100
107
  backend,
101
- currentSessionId,
102
- currentSessionWorkspaceSnapshot,
108
+ currentProjectId,
109
+ currentProjectProjectSnapshot,
103
110
  dispatch,
104
111
  layoutRef,
105
112
  }: AttachCurrentSessionOptions): () => void {
@@ -111,9 +118,9 @@ export function attachCurrentSession({
111
118
  try {
112
119
  const result = await backend.attach({
113
120
  cols: layoutRef.current.terminalCols,
121
+ projectId: currentProjectId,
122
+ projectSnapshot: currentProjectProjectSnapshot,
114
123
  rows: layoutRef.current.terminalRows,
115
- sessionId: currentSessionId,
116
- workspaceSnapshot: currentSessionWorkspaceSnapshot,
117
124
  })
118
125
  if (cancelled || attachRequestIdRef.current !== attachRequestId) {
119
126
  return
@@ -126,8 +133,8 @@ export function attachCurrentSession({
126
133
  })
127
134
  hydrateAttachedSession(
128
135
  dispatch,
129
- currentSessionId,
130
- currentSessionWorkspaceSnapshot,
136
+ currentProjectId,
137
+ currentProjectProjectSnapshot,
131
138
  result,
132
139
  layoutRef,
133
140
  backend
@@ -142,8 +149,8 @@ export function attachCurrentSession({
142
149
  })
143
150
  hydrateAttachedSession(
144
151
  dispatch,
145
- currentSessionId,
146
- currentSessionWorkspaceSnapshot,
152
+ currentProjectId,
153
+ currentProjectProjectSnapshot,
147
154
  null,
148
155
  layoutRef,
149
156
  backend