@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
@@ -1,14 +1,14 @@
1
1
  import type { CliCommand } from '../../registry'
2
2
 
3
- import { IPC_CAPABILITY_WORKSPACE_LIFECYCLE } from '../../../ipc/protocol'
4
- import { resolveWorkspace } from '../../client/workspace-resolver'
3
+ import { IPC_CAPABILITY_PROJECT_LIFECYCLE } from '../../../ipc/protocol'
4
+ import { resolveProject } from '../../client/project-resolver'
5
5
  import { SHARED_FLAGS } from '../../flags'
6
6
  import { EXIT_OK, EXIT_TIMEOUT, writeJson } from '../../output'
7
7
 
8
8
  const DEFAULT_WAIT_TIMEOUT_MS = 30_000
9
9
 
10
- export const workspaceSwitch: CliCommand = {
11
- args: [{ complete: { kind: 'dynamic', source: 'workspace' }, name: 'workspace', required: true }],
10
+ export const projectSwitch: CliCommand = {
11
+ args: [{ complete: { kind: 'dynamic', source: 'project' }, name: 'project', required: true }],
12
12
  flags: [
13
13
  ...SHARED_FLAGS,
14
14
  {
@@ -22,54 +22,54 @@ export const workspaceSwitch: CliCommand = {
22
22
  name: 'timeout',
23
23
  },
24
24
  ],
25
- group: 'workspace',
25
+ group: 'project',
26
26
  run: async (ctx) => {
27
27
  const target = ctx.args.positionals[0]
28
28
  if (typeof target !== 'string' || target.length === 0) {
29
- throw new Error('target workspace is required (id or name)')
29
+ throw new Error('target project is required (id or name)')
30
30
  }
31
- const session = resolveWorkspace(target)
31
+ const project = resolveProject(target)
32
32
  const wait = ctx.args.flags.wait === true
33
33
  const timeoutMs =
34
34
  typeof ctx.args.flags.timeout === 'number' ? ctx.args.flags.timeout : DEFAULT_WAIT_TIMEOUT_MS
35
35
 
36
36
  const daemon = await ctx.getDaemon()
37
- if (!daemon.hasCapability(IPC_CAPABILITY_WORKSPACE_LIFECYCLE)) {
37
+ if (!daemon.hasCapability(IPC_CAPABILITY_PROJECT_LIFECYCLE)) {
38
38
  throw new Error(
39
- 'daemon predates workspaceLifecycle capability — restart aimux to pick up the new daemon'
39
+ 'daemon predates projectLifecycle capability — restart aimux to pick up the new daemon'
40
40
  )
41
41
  }
42
42
 
43
43
  if (!wait) {
44
- await daemon.expectOk('switchWorkspace', { targetSessionId: session.id })
45
- writeJson({ name: session.name, targetSessionId: session.id })
44
+ await daemon.expectOk('switchProject', { targetProjectId: project.id })
45
+ writeJson({ name: project.name, targetProjectId: project.id })
46
46
  return EXIT_OK
47
47
  }
48
48
 
49
49
  // Subscribe BEFORE sending the request so we don't miss the broadcast for
50
- // the no-UI path (the daemon emits `workspaceSwitched` synchronously in
50
+ // the no-UI path (the daemon emits `projectSwitched` synchronously in
51
51
  // that branch).
52
52
  const settled = new Promise<number>((resolve) => {
53
- const off = daemon.on('workspaceSwitched', (payload) => {
54
- if (payload.sessionId !== session.id) return
53
+ const off = daemon.on('projectSwitched', (payload) => {
54
+ if (payload.projectId !== project.id) return
55
55
  off()
56
56
  clearTimeout(timer)
57
- writeJson({ name: session.name, targetSessionId: session.id })
57
+ writeJson({ name: project.name, targetProjectId: project.id })
58
58
  resolve(EXIT_OK)
59
59
  })
60
60
  const timer = setTimeout(() => {
61
61
  off()
62
62
  writeJson({
63
- error: 'timed out waiting for workspaceSwitched',
64
- targetSessionId: session.id,
63
+ error: 'timed out waiting for projectSwitched',
64
+ targetProjectId: project.id,
65
65
  })
66
66
  resolve(EXIT_TIMEOUT)
67
67
  }, timeoutMs)
68
68
  })
69
69
 
70
- await daemon.expectOk('switchWorkspace', { targetSessionId: session.id })
70
+ await daemon.expectOk('switchProject', { targetProjectId: project.id })
71
71
  return settled
72
72
  },
73
- summary: 'Switch the UI (or catalog when headless) to another workspace',
73
+ summary: 'Switch the UI (or catalog when headless) to another project',
74
74
  verb: 'switch',
75
75
  }
@@ -45,7 +45,7 @@ export const tabAwait: CliCommand = {
45
45
  const timeoutMs =
46
46
  typeof ctx.args.flags.timeout === 'number' ? ctx.args.flags.timeout : DEFAULT_TIMEOUT_MS
47
47
 
48
- const workspace = ctx.getWorkspace()
48
+ const project = ctx.getProject()
49
49
  const daemon = await ctx.getDaemon()
50
50
  if (
51
51
  !daemon.hasCapability(IPC_CAPABILITY_THIN_ATTACH) ||
@@ -57,7 +57,7 @@ export const tabAwait: CliCommand = {
57
57
  )
58
58
  }
59
59
 
60
- const attach = await daemon.attach({ cols: 0, rows: 0, sessionId: workspace.id, thin: true })
60
+ const attach = await daemon.attach({ cols: 0, projectId: project.id, rows: 0, thin: true })
61
61
  const tab = attach.tabs.find((t) => t.id === tabId)
62
62
  if (!tab) {
63
63
  // Exit 3 (runtime) via runCli — NOT 4, which is reserved for
@@ -14,7 +14,7 @@ export const tabClose: CliCommand = {
14
14
  throw new Error('tabId is required')
15
15
  }
16
16
 
17
- const workspace = ctx.getWorkspace()
17
+ const project = ctx.getProject()
18
18
  const daemon = await ctx.getDaemon()
19
19
 
20
20
  if (!daemon.hasCapability(IPC_CAPABILITY_THIN_ATTACH)) {
@@ -22,12 +22,12 @@ export const tabClose: CliCommand = {
22
22
  'daemon predates thinAttach capability — restart aimux to pick up the new daemon'
23
23
  )
24
24
  }
25
- await daemon.attach({ cols: 0, rows: 0, sessionId: workspace.id, thin: true })
25
+ await daemon.attach({ cols: 0, projectId: project.id, rows: 0, thin: true })
26
26
  await daemon.expectOk('closeTab', { tabId })
27
27
 
28
28
  writeJson({ ok: true })
29
29
  return EXIT_OK
30
30
  },
31
- summary: 'Close a tab in the workspace',
31
+ summary: 'Close a tab in the project',
32
32
  verb: 'close',
33
33
  }
@@ -1,6 +1,6 @@
1
1
  import { resolve as resolvePath } from 'node:path'
2
2
 
3
- import type { WorktreeRecord } from '../../../state/types'
3
+ import type { WorkspaceRecord } from '../../../state/types'
4
4
  import type { CliContext } from '../../context'
5
5
  import type { CliCommand } from '../../registry'
6
6
 
@@ -22,7 +22,7 @@ import {
22
22
  } from '../../../pty/command-registry'
23
23
  import { CliUsageError, SHARED_FLAGS } from '../../flags'
24
24
  import { EXIT_OK, writeJson } from '../../output'
25
- import { createWorkspaceWorktree } from '../worktree/create-core'
25
+ import { createProjectWorkspace } from '../workspace/create-core'
26
26
 
27
27
  const FALLBACK_COLS = 200
28
28
  const FALLBACK_ROWS = 60
@@ -35,10 +35,10 @@ export interface CreateCliTabOptions {
35
35
  cwd?: string
36
36
  effort?: string
37
37
  model?: string
38
- newWorktree?: boolean | string
38
+ newWorkspace?: boolean | string
39
39
  title?: string
40
40
  workerName?: string
41
- worktreeId?: string
41
+ workspaceId?: string
42
42
  }
43
43
 
44
44
  export interface CreateCliTabResult {
@@ -53,26 +53,26 @@ export interface CreateCliTabResult {
53
53
  tabId: string
54
54
  title: string
55
55
  workerName: string | null
56
- worktreeId: string | null
56
+ workspaceId: string | null
57
57
  }
58
58
 
59
59
  /**
60
60
  * Resolve the cwd a spawned tab should run in. Precedence: explicit `--cwd`
61
- * (resolved to absolute) > the resolved worktree's path > undefined (the
62
- * daemon's default). Worktree record paths are already absolute.
61
+ * (resolved to absolute) > the resolved workspace's path > undefined (the
62
+ * daemon's default). Workspace record paths are already absolute.
63
63
  */
64
64
  export function resolveTabCwd(
65
65
  cwdFlag: string | undefined,
66
- worktreeRecord: { path: string } | undefined
66
+ workspaceRecord: { path: string } | undefined
67
67
  ): string | undefined {
68
68
  if (cwdFlag !== undefined) return resolvePath(cwdFlag)
69
- return worktreeRecord?.path
69
+ return workspaceRecord?.path
70
70
  }
71
71
 
72
72
  /**
73
73
  * Resolve the base command a tab launches. Mirrors the UI's precedence
74
74
  * (`src/app-runtime/side-effects.ts`): an explicit `--command` override wins,
75
- * else the workspace's persisted `customCommands[assistantId]` (so CLI workers
75
+ * else the project's persisted `customCommands[assistantId]` (so CLI workers
76
76
  * inherit e.g. `claude --dangerously-skip-permissions`), else the builtin
77
77
  * default. `getAllAssistantOptions` deliberately does NOT apply builtin
78
78
  * overrides, so this lookup must be explicit.
@@ -85,13 +85,13 @@ export function resolveAssistantCommand(
85
85
  return commandOverride ?? customCommands[option.id] ?? option.command
86
86
  }
87
87
 
88
- async function rollbackCreatedWorktree(
89
- record: WorktreeRecord,
88
+ async function rollbackCreatedWorkspace(
89
+ record: WorkspaceRecord,
90
90
  ctx: CliContext,
91
91
  originalError: unknown
92
92
  ): Promise<never> {
93
93
  const daemon = await ctx.getDaemon()
94
- const workspace = ctx.getWorkspace()
94
+ const project = ctx.getProject()
95
95
  let rollbackError: unknown
96
96
  try {
97
97
  await removeGitWorktree({
@@ -100,9 +100,9 @@ async function rollbackCreatedWorktree(
100
100
  targetPath: record.path,
101
101
  })
102
102
  await pruneEmptyWorktreeParent(record.path)
103
- await daemon.expectOk('removeWorktreeRecord', {
104
- sessionId: workspace.id,
105
- worktreeId: record.id,
103
+ await daemon.expectOk('removeWorkspaceRecord', {
104
+ projectId: project.id,
105
+ workspaceId: record.id,
106
106
  })
107
107
  } catch (error) {
108
108
  rollbackError = error
@@ -117,8 +117,8 @@ async function rollbackCreatedWorktree(
117
117
  /**
118
118
  * Create a tab without writing to stdout. Worker commands compose this helper
119
119
  * with prompt dispatch/await while `tab create` remains a thin compatibility
120
- * adapter. All validation and daemon attachment happen before a worktree is
121
- * created; any later failure rolls the fresh worktree back.
120
+ * adapter. All validation and daemon attachment happen before a workspace is
121
+ * created; any later failure rolls the fresh workspace back.
122
122
  */
123
123
  export async function createCliTab(
124
124
  ctx: CliContext,
@@ -132,10 +132,10 @@ export async function createCliTab(
132
132
  cwd: cwdRaw,
133
133
  effort,
134
134
  model,
135
- newWorktree,
135
+ newWorkspace,
136
136
  title: requestedTitle,
137
137
  workerName,
138
- worktreeId: requestedWorktreeId,
138
+ workspaceId: requestedWorkspaceId,
139
139
  } = options
140
140
  if (assistantId.length === 0) throw new CliUsageError('--assistant is required')
141
141
  if (workerName !== undefined && workerName.trim().length === 0) {
@@ -147,18 +147,18 @@ export async function createCliTab(
147
147
  )
148
148
  }
149
149
 
150
- const createFreshWorktree = newWorktree !== undefined && newWorktree !== false
151
- if (createFreshWorktree && requestedWorktreeId !== undefined) {
150
+ const createFreshWorkspace = newWorkspace !== undefined && newWorkspace !== false
151
+ if (createFreshWorkspace && requestedWorkspaceId !== undefined) {
152
152
  throw new CliUsageError(
153
- '--new-worktree creates its own; use --worktree <id> to co-locate instead'
153
+ '--new-workspace creates its own; use --workspace <id> to co-locate instead'
154
154
  )
155
155
  }
156
- if (createFreshWorktree && cwdRaw !== undefined) {
157
- throw new CliUsageError('--new-worktree sets the cwd to the new worktree; drop --cwd')
156
+ if (createFreshWorkspace && cwdRaw !== undefined) {
157
+ throw new CliUsageError('--new-workspace sets the cwd to the new workspace; drop --cwd')
158
158
  }
159
- if (!createFreshWorktree && (base !== undefined || branch !== undefined)) {
159
+ if (!createFreshWorkspace && (base !== undefined || branch !== undefined)) {
160
160
  throw new CliUsageError(
161
- '--base / --branch require --new-worktree (use `worktree create` otherwise)'
161
+ '--base / --branch require --new-workspace (use `workspace create` otherwise)'
162
162
  )
163
163
  }
164
164
 
@@ -177,7 +177,7 @@ export async function createCliTab(
177
177
  const title = requestedTitle ?? option.label
178
178
  const tabId = createPrefixedId('tab')
179
179
 
180
- const workspace = ctx.getWorkspace()
180
+ const project = ctx.getProject()
181
181
  const daemon = await ctx.getDaemon()
182
182
  if (!daemon.hasCapability(IPC_CAPABILITY_THIN_ATTACH)) {
183
183
  throw new Error(
@@ -193,44 +193,44 @@ export async function createCliTab(
193
193
  if (!daemon.hasCapability(IPC_CAPABILITY_LIST_TABS)) {
194
194
  throw new Error('daemon cannot validate worker-name uniqueness — restart aimux')
195
195
  }
196
- const existing = await daemon.listTabs(workspace.id)
196
+ const existing = await daemon.listTabs(project.id)
197
197
  if (existing.tabs.some((tab) => tab.workerName === workerName)) {
198
- throw new Error(`worker name already exists in workspace "${workspace.name}": ${workerName}`)
198
+ throw new Error(`worker name already exists in project "${project.name}": ${workerName}`)
199
199
  }
200
200
  }
201
201
 
202
- // Attach before creating a worktree so stale daemon/session failures have no
202
+ // Attach before creating a workspace so stale daemon/project failures have no
203
203
  // git-side effect.
204
- await daemon.attach({ cols: 0, rows: 0, sessionId: workspace.id, thin: true })
204
+ await daemon.attach({ cols: 0, projectId: project.id, rows: 0, thin: true })
205
205
 
206
- let worktreeId = requestedWorktreeId ?? workspace.activeWorktreeId
207
- let worktreeRecord =
208
- worktreeId !== undefined
209
- ? workspace.worktrees?.find((entry) => entry.id === worktreeId)
206
+ let workspaceId = requestedWorkspaceId ?? project.activeWorkspaceId
207
+ let workspaceRecord =
208
+ workspaceId !== undefined
209
+ ? project.workspaces?.find((entry) => entry.id === workspaceId)
210
210
  : undefined
211
- if (requestedWorktreeId !== undefined && worktreeRecord === undefined) {
212
- const ids = workspace.worktrees?.map((entry) => entry.id).join(', ') ?? '(none)'
213
- throw new Error(`unknown worktree id: ${requestedWorktreeId} (known: ${ids})`)
211
+ if (requestedWorkspaceId !== undefined && workspaceRecord === undefined) {
212
+ const ids = project.workspaces?.map((entry) => entry.id).join(', ') ?? '(none)'
213
+ throw new Error(`unknown workspace id: ${requestedWorkspaceId} (known: ${ids})`)
214
214
  }
215
215
 
216
- let createdWorktree: WorktreeRecord | undefined
217
- if (createFreshWorktree) {
218
- const worktreeName =
219
- typeof newWorktree === 'string' && newWorktree !== ''
220
- ? newWorktree
216
+ let createdWorkspace: WorkspaceRecord | undefined
217
+ if (createFreshWorkspace) {
218
+ const workspaceName =
219
+ typeof newWorkspace === 'string' && newWorkspace !== ''
220
+ ? newWorkspace
221
221
  : `${assistantId}-${tabId.slice(-6)}`
222
- createdWorktree = await createWorkspaceWorktree({
222
+ createdWorkspace = await createProjectWorkspace({
223
223
  base: base ?? 'HEAD',
224
- branch: branch ?? `aimux/${worktreeName}`,
224
+ branch: branch ?? `aimux/${workspaceName}`,
225
225
  daemon,
226
- name: worktreeName,
227
- workspace,
226
+ name: workspaceName,
227
+ project,
228
228
  })
229
- worktreeId = createdWorktree.id
230
- worktreeRecord = createdWorktree
229
+ workspaceId = createdWorkspace.id
230
+ workspaceRecord = createdWorkspace
231
231
  }
232
232
 
233
- const cwd = resolveTabCwd(cwdRaw, worktreeRecord)
233
+ const cwd = resolveTabCwd(cwdRaw, workspaceRecord)
234
234
  const useFallback = daemon.hasCapability(IPC_CAPABILITY_CREATE_TAB_SIZE_FALLBACK)
235
235
  try {
236
236
  await daemon.expectOk('createTab', {
@@ -244,28 +244,28 @@ export async function createCliTab(
244
244
  tabId,
245
245
  title,
246
246
  workerName,
247
- worktreeId,
247
+ workspaceId,
248
248
  })
249
249
  } catch (error) {
250
- if (createdWorktree !== undefined) {
251
- return rollbackCreatedWorktree(createdWorktree, ctx, error)
250
+ if (createdWorkspace !== undefined) {
251
+ return rollbackCreatedWorkspace(createdWorkspace, ctx, error)
252
252
  }
253
253
  throw error
254
254
  }
255
255
 
256
256
  return {
257
257
  assistant: assistantId,
258
- branch: worktreeRecord?.branch ?? null,
258
+ branch: workspaceRecord?.branch ?? null,
259
259
  command: [executable, ...args].join(' '),
260
260
  cwd: cwd ?? null,
261
261
  effort: effort ?? null,
262
262
  model: model ?? null,
263
- name: worktreeRecord?.name ?? null,
264
- path: worktreeRecord?.path ?? null,
263
+ name: workspaceRecord?.name ?? null,
264
+ path: workspaceRecord?.path ?? null,
265
265
  tabId,
266
266
  title,
267
267
  workerName: workerName ?? null,
268
- worktreeId: worktreeId ?? null,
268
+ workspaceId: workspaceId ?? null,
269
269
  }
270
270
  }
271
271
 
@@ -310,26 +310,27 @@ export const tabCreate: CliCommand = {
310
310
  name: 'effort',
311
311
  },
312
312
  {
313
- complete: { kind: 'dynamic', source: 'worktree' },
314
- description: 'worktree id the tab belongs to (defaults to the workspace’s active worktree)',
313
+ complete: { kind: 'dynamic', source: 'workspace' },
314
+ description: 'workspace id the tab belongs to (defaults to the project’s active workspace)',
315
315
  kind: 'string',
316
- name: 'worktree',
316
+ name: 'workspace',
317
317
  },
318
318
  {
319
319
  complete: { kind: 'none' },
320
- description: 'create a fresh worktree for this tab (optionally named: --new-worktree=<name>)',
320
+ description:
321
+ 'create a fresh workspace for this tab (optionally named: --new-workspace=<name>)',
321
322
  kind: 'optional-string',
322
- name: 'new-worktree',
323
+ name: 'new-workspace',
323
324
  },
324
325
  {
325
326
  complete: { kind: 'dynamic', source: 'git-ref' },
326
- description: 'base ref for --new-worktree (default HEAD)',
327
+ description: 'base ref for --new-workspace (default HEAD)',
327
328
  kind: 'string',
328
329
  name: 'base',
329
330
  },
330
331
  {
331
332
  complete: { kind: 'none' },
332
- description: 'branch for --new-worktree (default aimux/<name>)',
333
+ description: 'branch for --new-workspace (default aimux/<name>)',
333
334
  kind: 'string',
334
335
  name: 'branch',
335
336
  },
@@ -347,13 +348,13 @@ export const tabCreate: CliCommand = {
347
348
 
348
349
  const title = typeof ctx.args.flags.title === 'string' ? ctx.args.flags.title : undefined
349
350
  const cwdRaw = typeof ctx.args.flags.cwd === 'string' ? ctx.args.flags.cwd : undefined
350
- const newWorktreeRaw = ctx.args.flags['new-worktree']
351
- const newWorktreeFlag =
352
- typeof newWorktreeRaw === 'string' || typeof newWorktreeRaw === 'boolean'
353
- ? newWorktreeRaw
351
+ const newWorkspaceRaw = ctx.args.flags['new-workspace']
352
+ const newWorkspaceFlag =
353
+ typeof newWorkspaceRaw === 'string' || typeof newWorkspaceRaw === 'boolean'
354
+ ? newWorkspaceRaw
354
355
  : undefined
355
- const worktreeFlag =
356
- typeof ctx.args.flags.worktree === 'string' ? ctx.args.flags.worktree : undefined
356
+ const workspaceFlag =
357
+ typeof ctx.args.flags.workspace === 'string' ? ctx.args.flags.workspace : undefined
357
358
  const baseFlag = typeof ctx.args.flags.base === 'string' ? ctx.args.flags.base : undefined
358
359
  const branchFlag = typeof ctx.args.flags.branch === 'string' ? ctx.args.flags.branch : undefined
359
360
  const result = await createCliTab(ctx, {
@@ -364,9 +365,9 @@ export const tabCreate: CliCommand = {
364
365
  cwd: cwdRaw,
365
366
  effort,
366
367
  model,
367
- newWorktree: newWorktreeFlag,
368
+ newWorkspace: newWorkspaceFlag,
368
369
  title,
369
- worktreeId: worktreeFlag,
370
+ workspaceId: workspaceFlag,
370
371
  })
371
372
  writeJson(result)
372
373
  return EXIT_OK
@@ -14,7 +14,7 @@ export const tabFocus: CliCommand = {
14
14
  throw new Error('tabId is required')
15
15
  }
16
16
 
17
- const workspace = ctx.getWorkspace()
17
+ const project = ctx.getProject()
18
18
  const daemon = await ctx.getDaemon()
19
19
 
20
20
  if (!daemon.hasCapability(IPC_CAPABILITY_THIN_ATTACH)) {
@@ -22,12 +22,12 @@ export const tabFocus: CliCommand = {
22
22
  'daemon predates thinAttach capability — restart aimux to pick up the new daemon'
23
23
  )
24
24
  }
25
- await daemon.attach({ cols: 0, rows: 0, sessionId: workspace.id, thin: true })
25
+ await daemon.attach({ cols: 0, projectId: project.id, rows: 0, thin: true })
26
26
  await daemon.expectOk('setActiveTab', { tabId })
27
27
 
28
28
  writeJson({ ok: true })
29
29
  return EXIT_OK
30
30
  },
31
- summary: 'Set the active tab in the workspace',
31
+ summary: 'Set the active tab in the project',
32
32
  verb: 'focus',
33
33
  }
@@ -32,7 +32,7 @@ export const tabList: CliCommand = {
32
32
  ],
33
33
  group: 'tab',
34
34
  run: async (ctx) => {
35
- const workspace = ctx.getWorkspace()
35
+ const project = ctx.getProject()
36
36
  const daemon = await ctx.getDaemon()
37
37
  const verbose = ctx.args.flags.verbose === true
38
38
 
@@ -43,7 +43,7 @@ export const tabList: CliCommand = {
43
43
  }
44
44
 
45
45
  if (daemon.hasCapability(IPC_CAPABILITY_LIST_TABS)) {
46
- const result = await daemon.listTabs(workspace.id)
46
+ const result = await daemon.listTabs(project.id)
47
47
  const tabs = verbose ? result.tabs : result.tabs.map(stripLastLine)
48
48
  writeJson({ activeTabId: result.activeTabId, tabs })
49
49
  return EXIT_OK
@@ -56,12 +56,12 @@ export const tabList: CliCommand = {
56
56
  }
57
57
 
58
58
  // Fallback: thin-attach to read the same information without resizing the
59
- // session. Pre-listTabs daemons that advertise thinAttach would in
59
+ // project. Pre-listTabs daemons that advertise thinAttach would in
60
60
  // principle land here, but that combination shouldn't ship.
61
61
  const attach = await daemon.attach({
62
62
  cols: 0,
63
+ projectId: project.id,
63
64
  rows: 0,
64
- sessionId: workspace.id,
65
65
  thin: true,
66
66
  })
67
67
  writeJson({
@@ -73,11 +73,11 @@ export const tabList: CliCommand = {
73
73
  id: tab.id,
74
74
  status: tab.status,
75
75
  title: tab.title,
76
- worktreeId: tab.worktreeId,
76
+ workspaceId: tab.workspaceId,
77
77
  })),
78
78
  })
79
79
  return EXIT_OK
80
80
  },
81
- summary: 'List tabs in the active workspace',
81
+ summary: 'List tabs in the active project',
82
82
  verb: 'list',
83
83
  }
@@ -60,7 +60,7 @@ export const tabRun: CliCommand = {
60
60
  typeof ctx.args.flags.timeout === 'number' ? ctx.args.flags.timeout : DEFAULT_TIMEOUT_MS
61
61
  const appendEnter = ctx.args.flags['no-enter'] !== true
62
62
 
63
- const workspace = ctx.getWorkspace()
63
+ const project = ctx.getProject()
64
64
  const daemon = await ctx.getDaemon()
65
65
  if (
66
66
  !daemon.hasCapability(IPC_CAPABILITY_THIN_ATTACH) ||
@@ -72,7 +72,7 @@ export const tabRun: CliCommand = {
72
72
  )
73
73
  }
74
74
 
75
- const attach = await daemon.attach({ cols: 0, rows: 0, sessionId: workspace.id, thin: true })
75
+ const attach = await daemon.attach({ cols: 0, projectId: project.id, rows: 0, thin: true })
76
76
  if (!attach.tabs.some((t) => t.id === tabId)) {
77
77
  throw new Error(`tab not found: ${tabId}`)
78
78
  }
@@ -97,7 +97,7 @@ export const tabSend: CliCommand = {
97
97
  }
98
98
  const payload = buildPromptPayload(text, asKeys)
99
99
 
100
- const workspace = ctx.getWorkspace()
100
+ const project = ctx.getProject()
101
101
  const daemon = await ctx.getDaemon()
102
102
  if (!daemon.hasCapability(IPC_CAPABILITY_THIN_ATTACH)) {
103
103
  throw new Error(
@@ -105,7 +105,7 @@ export const tabSend: CliCommand = {
105
105
  )
106
106
  }
107
107
 
108
- await daemon.attach({ cols: 0, rows: 0, sessionId: workspace.id, thin: true })
108
+ await daemon.attach({ cols: 0, projectId: project.id, rows: 0, thin: true })
109
109
 
110
110
  if (!awaitSubmit) {
111
111
  const bytesWritten = await writePromptPayload(daemon, tabId, payload, appendEnter)
@@ -38,7 +38,7 @@ export const tabSnapshot: CliCommand = {
38
38
  }
39
39
  const renderOptions = { trim: ctx.args.flags['no-trim'] !== true }
40
40
 
41
- const workspace = ctx.getWorkspace()
41
+ const project = ctx.getProject()
42
42
  const daemon = await ctx.getDaemon()
43
43
  if (!daemon.hasCapability(IPC_CAPABILITY_THIN_ATTACH)) {
44
44
  throw new Error(
@@ -71,8 +71,8 @@ export const tabSnapshot: CliCommand = {
71
71
 
72
72
  const attach = await daemon.attach({
73
73
  cols: 0,
74
+ projectId: project.id,
74
75
  rows: 0,
75
- sessionId: workspace.id,
76
76
  thin: true,
77
77
  })
78
78
  const tab = attach.tabs.find((t) => t.id === tabId)
@@ -57,7 +57,7 @@ export const tabTail: CliCommand = {
57
57
  const followStatus = ctx.args.flags['follow-status'] === true
58
58
  const timeoutMs = typeof ctx.args.flags.timeout === 'number' ? ctx.args.flags.timeout : 0
59
59
 
60
- const workspace = ctx.getWorkspace()
60
+ const project = ctx.getProject()
61
61
  const daemon = await ctx.getDaemon()
62
62
  if (!daemon.hasCapability(IPC_CAPABILITY_THIN_ATTACH)) {
63
63
  throw new Error(
@@ -69,7 +69,7 @@ export const tabTail: CliCommand = {
69
69
  // Attach before wiring subscribers so replay renders arrive after we
70
70
  // print the "attached" marker; that keeps NDJSON output deterministic
71
71
  // for downstream consumers.
72
- const attach = await daemon.attach({ cols: 0, rows: 0, sessionId: workspace.id, thin: true })
72
+ const attach = await daemon.attach({ cols: 0, projectId: project.id, rows: 0, thin: true })
73
73
 
74
74
  // Fail fast on a bad tabId — otherwise tail would sit silent until
75
75
  // --timeout, since no matching tabRender/tabExit would ever fire.
@@ -36,7 +36,7 @@ export const tabWait: CliCommand = {
36
36
  const timeoutMs =
37
37
  typeof ctx.args.flags.timeout === 'number' ? ctx.args.flags.timeout : DEFAULT_TIMEOUT_MS
38
38
 
39
- const workspace = ctx.getWorkspace()
39
+ const project = ctx.getProject()
40
40
  const daemon = await ctx.getDaemon()
41
41
  if (!daemon.hasCapability(IPC_CAPABILITY_THIN_ATTACH)) {
42
42
  throw new Error(
@@ -46,8 +46,8 @@ export const tabWait: CliCommand = {
46
46
 
47
47
  const attach = await daemon.attach({
48
48
  cols: 0,
49
+ projectId: project.id,
49
50
  rows: 0,
50
- sessionId: workspace.id,
51
51
  thin: true,
52
52
  })
53
53
 
@@ -19,14 +19,14 @@ export const workerAwait: CliCommand = {
19
19
  ],
20
20
  group: 'worker',
21
21
  run: async (ctx) => {
22
- const { tab, workspace } = await resolveWorkerTarget(ctx, ctx.args.positionals[0] ?? '')
22
+ const { project, tab } = await resolveWorkerTarget(ctx, ctx.args.positionals[0] ?? '')
23
23
  const outcome = await awaitExistingWorker(
24
24
  ctx,
25
- workspace,
25
+ project,
26
26
  tab.id,
27
27
  typeof ctx.args.flags.timeout === 'number' ? ctx.args.flags.timeout : DEFAULT_TIMEOUT_MS
28
28
  )
29
- writeJson(workerEnvelope(workspace, workerView(workspace, tab), outcome))
29
+ writeJson(workerEnvelope(project, workerView(project, tab), outcome))
30
30
  return workerOutcomeExitCode(outcome)
31
31
  },
32
32
  summary: "Await an existing worker's in-flight turn",