@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
@@ -11,7 +11,7 @@ import {
11
11
  IPC_CAPABILITY_THIN_ATTACH,
12
12
  IPC_CAPABILITY_TURN_LIFECYCLE,
13
13
  IPC_CAPABILITY_WORKER_METADATA,
14
- IPC_CAPABILITY_WORKTREE_LIFECYCLE_EVENTS,
14
+ IPC_CAPABILITY_WORKSPACE_LIFECYCLE_EVENTS,
15
15
  } from '../../../ipc/protocol'
16
16
  import {
17
17
  getAllAssistantOptions,
@@ -19,10 +19,10 @@ import {
19
19
  parseCommand,
20
20
  } from '../../../pty/command-registry'
21
21
  import {
22
- findPrimaryWorktree,
23
- WORKSPACE_ENV_VAR,
24
- workspaceRepoRoot,
25
- } from '../../client/workspace-resolver'
22
+ findPrimaryWorkspace,
23
+ PROJECT_ENV_VAR,
24
+ projectRepoRoot,
25
+ } from '../../client/project-resolver'
26
26
  import { SHARED_FLAGS } from '../../flags'
27
27
  import { EXIT_OK, EXIT_RUNTIME, writeJson } from '../../output'
28
28
  import { WORKER_SCHEMA_VERSION } from './shared'
@@ -33,7 +33,7 @@ const REQUIRED_DAEMON_CAPABILITIES = [
33
33
  IPC_CAPABILITY_THIN_ATTACH,
34
34
  IPC_CAPABILITY_TURN_LIFECYCLE,
35
35
  IPC_CAPABILITY_WORKER_METADATA,
36
- IPC_CAPABILITY_WORKTREE_LIFECYCLE_EVENTS,
36
+ IPC_CAPABILITY_WORKSPACE_LIFECYCLE_EVENTS,
37
37
  ] as const
38
38
 
39
39
  export const workerDoctor: CliCommand = {
@@ -42,7 +42,7 @@ export const workerDoctor: CliCommand = {
42
42
  group: 'worker',
43
43
  run: async (ctx) => {
44
44
  const daemon = await ctx.getDaemon()
45
- const workspace = ctx.getWorkspace()
45
+ const project = ctx.getProject()
46
46
  const { version } = await import('../../../../package.json')
47
47
  const { customCommands } = loadConfig()
48
48
  const assistants = getAllAssistantOptions(customCommands).map((assistant) => ({
@@ -61,8 +61,8 @@ export const workerDoctor: CliCommand = {
61
61
  const missingManagerCapabilities = [MANAGER_CAPABILITY_WORKER_METADATA].filter(
62
62
  (capability) => !managerCapabilities.includes(capability)
63
63
  )
64
- const primaryWorktree = findPrimaryWorktree(workspace)
65
- const workspaceOrigin = ctx.getWorkspaceOrigin?.() ?? 'active'
64
+ const primaryWorkspace = findPrimaryWorkspace(project)
65
+ const projectOrigin = ctx.getProjectOrigin?.() ?? 'active'
66
66
  const availableAssistants = assistants.filter((assistant) => assistant.available)
67
67
  const skillPath = fileURLToPath(
68
68
  new URL('../../../../skills/aimux-orchestrator/', import.meta.url)
@@ -78,8 +78,8 @@ export const workerDoctor: CliCommand = {
78
78
  `restart the terminal manager; it is missing: ${missingManagerCapabilities.join(', ')}`
79
79
  )
80
80
  }
81
- if (primaryWorktree === undefined) {
82
- issues.push('workspace has no primary worktree; default isolated worker runs are unavailable')
81
+ if (primaryWorkspace === undefined) {
82
+ issues.push('project has no primary workspace; default isolated worker runs are unavailable')
83
83
  }
84
84
  if (availableAssistants.length === 0) {
85
85
  issues.push('no configured assistant executable is available on PATH')
@@ -87,14 +87,14 @@ export const workerDoctor: CliCommand = {
87
87
  if (!existsSync(skillPath)) {
88
88
  issues.push(`packaged orchestrator skill is missing: ${skillPath}`)
89
89
  }
90
- // Not an issue — an inferred workspace is the normal interactive case — but
90
+ // Not an issue — an inferred project is the normal interactive case — but
91
91
  // it IS the one resolution mode that can follow the UI to another project
92
92
  // between two calls. An orchestrator dispatching a multi-hour fleet wants to
93
93
  // see this before it starts, not after it reviews diffs from the wrong repo.
94
94
  const warnings: string[] = []
95
- if (workspaceOrigin === 'active') {
95
+ if (projectOrigin === 'active') {
96
96
  warnings.push(
97
- `workspace "${workspace.name}" was inferred from the most recently opened session and follows the UI; pin it with --workspace or ${WORKSPACE_ENV_VAR}`
97
+ `project "${project.name}" was inferred from the most recently opened project and follows the UI; pin it with --project or ${PROJECT_ENV_VAR}`
98
98
  )
99
99
  }
100
100
  const ready = issues.length === 0
@@ -113,16 +113,16 @@ export const workerDoctor: CliCommand = {
113
113
  missing: missingManagerCapabilities,
114
114
  ok: missingManagerCapabilities.length === 0,
115
115
  },
116
- skill: { ok: existsSync(skillPath), path: skillPath },
117
- workspace: {
118
- hasPrimaryWorktree: primaryWorktree !== undefined,
119
- name: workspace.name,
120
- ok: primaryWorktree !== undefined,
121
- /** Repo every fresh worker worktree is cut from — confirm before dispatching. */
122
- repoRoot: workspaceRepoRoot(workspace),
116
+ project: {
117
+ hasPrimaryWorkspace: primaryWorkspace !== undefined,
118
+ name: project.name,
119
+ ok: primaryWorkspace !== undefined,
120
+ /** Repo every fresh worker workspace is cut from — confirm before dispatching. */
121
+ repoRoot: projectRepoRoot(project),
123
122
  /** 'flag' | 'env' | 'active'; only 'active' can follow the UI. */
124
- source: workspaceOrigin,
123
+ source: projectOrigin,
125
124
  },
125
+ skill: { ok: existsSync(skillPath), path: skillPath },
126
126
  },
127
127
  cliVersion: version,
128
128
  daemon: {
@@ -134,20 +134,20 @@ export const workerDoctor: CliCommand = {
134
134
  protocolVersion: daemon.getSelectedVersion(),
135
135
  },
136
136
  issues,
137
+ project: {
138
+ id: project.id,
139
+ name: project.name,
140
+ projectPath: project.projectPath ?? null,
141
+ repoRoot: projectRepoRoot(project),
142
+ source: projectOrigin,
143
+ },
137
144
  ready,
138
145
  schemaVersion: WORKER_SCHEMA_VERSION,
139
146
  skillPath,
140
147
  warnings,
141
- workspace: {
142
- id: workspace.id,
143
- name: workspace.name,
144
- projectPath: workspace.projectPath ?? null,
145
- repoRoot: workspaceRepoRoot(workspace),
146
- source: workspaceOrigin,
147
- },
148
148
  })
149
149
  return ready ? EXIT_OK : EXIT_RUNTIME
150
150
  },
151
- summary: 'Check worker prerequisites, versions, assistants, and workspace',
151
+ summary: 'Check worker prerequisites, versions, assistants, and project',
152
152
  verb: 'doctor',
153
153
  }
@@ -1,6 +1,6 @@
1
1
  import type { CliCommand } from '../../registry'
2
2
 
3
- import { workspaceIdentity } from '../../client/workspace-resolver'
3
+ import { projectIdentity } from '../../client/project-resolver'
4
4
  import { SHARED_FLAGS } from '../../flags'
5
5
  import { EXIT_OK, writeJson } from '../../output'
6
6
  import {
@@ -16,9 +16,9 @@ export const workerList: CliCommand = {
16
16
  flags: [
17
17
  ...SHARED_FLAGS,
18
18
  {
19
- description: 'list workers in every catalogued workspace, not just the target one',
19
+ description: 'list workers in every catalogued project, not just the target one',
20
20
  kind: 'boolean',
21
- name: 'all-workspaces',
21
+ name: 'all-projects',
22
22
  },
23
23
  ],
24
24
  group: 'worker',
@@ -27,41 +27,41 @@ export const workerList: CliCommand = {
27
27
 
28
28
  // `{"workers":[]}` alone is indistinguishable from "every worker died", and
29
29
  // the natural recovery from that reading is a destructive re-dispatch. Name
30
- // the workspace that was queried so an empty fleet is legible as "not here"
30
+ // the project that was queried so an empty fleet is legible as "not here"
31
31
  // rather than "gone", and offer one call that answers "are they really gone?"
32
- if (ctx.args.flags['all-workspaces'] === true) {
32
+ if (ctx.args.flags['all-projects'] === true) {
33
33
  const targets = await listWorkerTargets(ctx)
34
34
  writeJson({
35
35
  schemaVersion: WORKER_SCHEMA_VERSION,
36
36
  workers: targets
37
37
  .filter((target) => selector === undefined || target.tab.workerName === selector)
38
38
  .map((target) => ({
39
- ...workerView(target.workspace, target.tab),
40
- workspace: workspaceIdentity(target.workspace),
39
+ ...workerView(target.project, target.tab),
40
+ project: projectIdentity(target.project),
41
41
  })),
42
42
  })
43
43
  return EXIT_OK
44
44
  }
45
45
 
46
46
  if (selector !== undefined) {
47
- const { tab, workspace } = await resolveWorkerTarget(ctx, selector)
47
+ const { project, tab } = await resolveWorkerTarget(ctx, selector)
48
48
  writeJson({
49
+ project: projectIdentity(project),
49
50
  schemaVersion: WORKER_SCHEMA_VERSION,
50
- workers: [workerView(workspace, tab)],
51
- workspace: workspaceIdentity(workspace),
51
+ workers: [workerView(project, tab)],
52
52
  })
53
53
  return EXIT_OK
54
54
  }
55
55
 
56
- const workspace = ctx.getWorkspace()
57
- const tabs = await listNamedWorkerTabs(ctx, workspace)
56
+ const project = ctx.getProject()
57
+ const tabs = await listNamedWorkerTabs(ctx, project)
58
58
  writeJson({
59
+ project: projectIdentity(project),
59
60
  schemaVersion: WORKER_SCHEMA_VERSION,
60
- workers: tabs.map((tab) => workerView(workspace, tab)),
61
- workspace: workspaceIdentity(workspace),
61
+ workers: tabs.map((tab) => workerView(project, tab)),
62
62
  })
63
63
  return EXIT_OK
64
64
  },
65
- summary: 'List named workers with liveness and worktree context',
65
+ summary: 'List named workers with liveness and workspace context',
66
66
  verb: 'list',
67
67
  }
@@ -48,7 +48,7 @@ export const workerPrompt: CliCommand = {
48
48
  group: 'worker',
49
49
  run: async (ctx) => {
50
50
  const selector = ctx.args.positionals[0] ?? ''
51
- const { tab, workspace } = await resolveWorkerTarget(ctx, selector)
51
+ const { project, tab } = await resolveWorkerTarget(ctx, selector)
52
52
  const promptFile =
53
53
  typeof ctx.args.flags['prompt-file'] === 'string' ? ctx.args.flags['prompt-file'] : undefined
54
54
  const text = await resolvePromptText(
@@ -56,7 +56,7 @@ export const workerPrompt: CliCommand = {
56
56
  ctx.args.flags.stdin === true,
57
57
  ctx.args.positionals[1]
58
58
  )
59
- const outcome = await dispatchWorkerPrompt(ctx, workspace, tab.id, text, {
59
+ const outcome = await dispatchWorkerPrompt(ctx, project, tab.id, text, {
60
60
  detach: ctx.args.flags.detach === true,
61
61
  replace: ctx.args.flags.replace === true,
62
62
  timeoutMs:
@@ -66,7 +66,7 @@ export const workerPrompt: CliCommand = {
66
66
  ? ctx.args.flags['uptake-timeout']
67
67
  : undefined,
68
68
  })
69
- writeJson(workerEnvelope(workspace, workerView(workspace, tab), outcome))
69
+ writeJson(workerEnvelope(project, workerView(project, tab), outcome))
70
70
  return workerOutcomeExitCode(outcome)
71
71
  },
72
72
  summary: 'Prompt an existing named worker and await its outcome',
@@ -19,7 +19,7 @@ export const workerRun: CliCommand = {
19
19
  ...SHARED_FLAGS,
20
20
  {
21
21
  complete: { kind: 'none' },
22
- description: 'unique workspace-scoped worker name',
22
+ description: 'unique project-scoped worker name',
23
23
  kind: 'string',
24
24
  name: 'name',
25
25
  },
@@ -61,25 +61,25 @@ export const workerRun: CliCommand = {
61
61
  name: 'uptake-timeout',
62
62
  },
63
63
  {
64
- complete: { kind: 'dynamic', source: 'worktree' },
65
- description: 'co-locate in an existing worktree id',
64
+ complete: { kind: 'dynamic', source: 'workspace' },
65
+ description: 'co-locate in an existing workspace id',
66
66
  kind: 'string',
67
- name: 'worktree',
67
+ name: 'workspace',
68
68
  },
69
69
  {
70
- description: 'run in the workspace active worktree instead of creating one',
70
+ description: 'run in the project active workspace instead of creating one',
71
71
  kind: 'boolean',
72
- name: 'no-worktree',
72
+ name: 'no-workspace',
73
73
  },
74
74
  {
75
75
  complete: { kind: 'dynamic', source: 'git-ref' },
76
- description: 'base ref for the fresh worktree (default HEAD)',
76
+ description: 'base ref for the fresh workspace (default HEAD)',
77
77
  kind: 'string',
78
78
  name: 'base',
79
79
  },
80
80
  {
81
81
  complete: { kind: 'none' },
82
- description: 'branch for the fresh worktree (default aimux/<name>)',
82
+ description: 'branch for the fresh workspace (default aimux/<name>)',
83
83
  kind: 'string',
84
84
  name: 'branch',
85
85
  },
@@ -90,11 +90,11 @@ export const workerRun: CliCommand = {
90
90
  const assistant = typeof ctx.args.flags.assistant === 'string' ? ctx.args.flags.assistant : ''
91
91
  validateWorkerName(name)
92
92
  if (assistant === '') throw new CliUsageError('--assistant is required')
93
- const worktree =
94
- typeof ctx.args.flags.worktree === 'string' ? ctx.args.flags.worktree : undefined
95
- const noWorktree = ctx.args.flags['no-worktree'] === true
96
- if (worktree !== undefined && noWorktree) {
97
- throw new CliUsageError('--worktree and --no-worktree are mutually exclusive')
93
+ const workspace =
94
+ typeof ctx.args.flags.workspace === 'string' ? ctx.args.flags.workspace : undefined
95
+ const noWorkspace = ctx.args.flags['no-workspace'] === true
96
+ if (workspace !== undefined && noWorkspace) {
97
+ throw new CliUsageError('--workspace and --no-workspace are mutually exclusive')
98
98
  }
99
99
  const promptFile =
100
100
  typeof ctx.args.flags['prompt-file'] === 'string' ? ctx.args.flags['prompt-file'] : undefined
@@ -103,27 +103,27 @@ export const workerRun: CliCommand = {
103
103
  ctx.args.flags.stdin === true,
104
104
  ctx.args.positionals[0]
105
105
  )
106
- // Resolve the target workspace ONCE, up front, and use that record for
107
- // every later step. The repo a worktree is cut from comes from this record,
108
- // so an orchestrator that omits --workspace at least gets the resolved
106
+ // Resolve the target project ONCE, up front, and use that record for
107
+ // every later step. The repo a workspace is cut from comes from this record,
108
+ // so an orchestrator that omits --project at least gets the resolved
109
109
  // identity echoed back in the response instead of having to infer it.
110
- const workspace = ctx.getWorkspace()
110
+ const project = ctx.getProject()
111
111
  const result = await createCliTab(ctx, {
112
112
  assistantId: assistant,
113
113
  base: typeof ctx.args.flags.base === 'string' ? ctx.args.flags.base : undefined,
114
114
  branch: typeof ctx.args.flags.branch === 'string' ? ctx.args.flags.branch : undefined,
115
115
  effort: typeof ctx.args.flags.effort === 'string' ? ctx.args.flags.effort : undefined,
116
116
  model: typeof ctx.args.flags.model === 'string' ? ctx.args.flags.model : undefined,
117
- newWorktree: noWorktree || worktree !== undefined ? undefined : name,
117
+ newWorkspace: noWorkspace || workspace !== undefined ? undefined : name,
118
118
  title: name,
119
119
  workerName: name,
120
- worktreeId: worktree,
120
+ workspaceId: workspace,
121
121
  })
122
- const tabs = await (await ctx.getDaemon()).listTabs(workspace.id)
122
+ const tabs = await (await ctx.getDaemon()).listTabs(project.id)
123
123
  const tab = tabs.tabs.find((entry) => entry.id === result.tabId)
124
124
  if (!tab) throw new Error(`created worker disappeared: ${result.tabId}`)
125
- const worker = workerView(workspace, tab)
126
- const outcome = await dispatchWorkerPrompt(ctx, workspace, result.tabId, text, {
125
+ const worker = workerView(project, tab)
126
+ const outcome = await dispatchWorkerPrompt(ctx, project, result.tabId, text, {
127
127
  // The tab was spawned microseconds ago: its assistant is still booting, so
128
128
  // the prompt must not be written until the TUI is reading keystrokes.
129
129
  awaitFirstPaint: true,
@@ -135,7 +135,7 @@ export const workerRun: CliCommand = {
135
135
  ? ctx.args.flags['uptake-timeout']
136
136
  : undefined,
137
137
  })
138
- writeJson(workerEnvelope(workspace, worker, outcome))
138
+ writeJson(workerEnvelope(project, worker, outcome))
139
139
  return workerOutcomeExitCode(outcome)
140
140
  },
141
141
  summary: 'Create a named worker, dispatch a prompt, and await its outcome',
@@ -1,4 +1,4 @@
1
- import type { SessionRecord, TerminalSnapshot, WorktreeRecord } from '../../../state/types'
1
+ import type { ProjectRecord, TerminalSnapshot, WorkspaceRecord } from '../../../state/types'
2
2
  import type { CliContext } from '../../context'
3
3
 
4
4
  import {
@@ -9,11 +9,7 @@ import {
9
9
  IPC_CAPABILITY_WORKER_METADATA,
10
10
  type TabSessionSummary,
11
11
  } from '../../../ipc/protocol'
12
- import {
13
- listWorkspaces,
14
- workspaceIdentity,
15
- workspaceRepoRoot,
16
- } from '../../client/workspace-resolver'
12
+ import { listProjects, projectIdentity, projectRepoRoot } from '../../client/project-resolver'
17
13
  import { CliUsageError } from '../../flags'
18
14
  import {
19
15
  EXIT_OK,
@@ -62,12 +58,12 @@ export interface WorkerView {
62
58
  lastLine?: string
63
59
  name: string
64
60
  path: string | null
65
- /** Repository the worker's worktree was cut from — see `workspaceRepoRoot`. */
61
+ /** Repository the worker's workspace was cut from — see `projectRepoRoot`. */
66
62
  repoRoot: string | null
67
63
  status: TabSessionSummary['status']
68
64
  tabId: string
69
65
  title: string
70
- worktreeId: string | null
66
+ workspaceId: string | null
71
67
  }
72
68
 
73
69
  export interface WorkerOutcome {
@@ -85,10 +81,10 @@ export interface WorkerOutcome {
85
81
  }
86
82
  }
87
83
 
88
- /** A worker plus the workspace it is actually bound to. */
84
+ /** A worker plus the project it is actually bound to. */
89
85
  export interface WorkerTarget {
90
86
  tab: TabSessionSummary
91
- workspace: SessionRecord
87
+ project: ProjectRecord
92
88
  }
93
89
 
94
90
  export function validateWorkerName(name: string): void {
@@ -99,32 +95,32 @@ export function validateWorkerName(name: string): void {
99
95
  }
100
96
  }
101
97
 
102
- function worktreeFor(
103
- workspace: SessionRecord,
104
- worktreeId: string | undefined
105
- ): WorktreeRecord | undefined {
106
- if (worktreeId === undefined) return undefined
107
- return workspace.worktrees?.find((worktree) => worktree.id === worktreeId)
98
+ function workspaceFor(
99
+ project: ProjectRecord,
100
+ workspaceId: string | undefined
101
+ ): WorkspaceRecord | undefined {
102
+ if (workspaceId === undefined) return undefined
103
+ return project.workspaces?.find((workspace) => workspace.id === workspaceId)
108
104
  }
109
105
 
110
- export function workerView(workspace: SessionRecord, tab: TabSessionSummary): WorkerView {
106
+ export function workerView(project: ProjectRecord, tab: TabSessionSummary): WorkerView {
111
107
  if (tab.workerName === undefined) {
112
108
  throw new Error(`tab is not a named worker: ${tab.id}`)
113
109
  }
114
- const worktree = worktreeFor(workspace, tab.worktreeId)
110
+ const workspace = workspaceFor(project, tab.workspaceId)
115
111
  return {
116
112
  activity: tab.activity,
117
113
  assistant: tab.assistant,
118
- branch: worktree?.branch ?? null,
114
+ branch: workspace?.branch ?? null,
119
115
  command: tab.command,
120
116
  lastLine: tab.lastLine,
121
117
  name: tab.workerName,
122
- path: worktree?.path ?? null,
123
- repoRoot: worktree?.repoRoot ?? workspaceRepoRoot(workspace),
118
+ path: workspace?.path ?? null,
119
+ repoRoot: workspace?.repoRoot ?? projectRepoRoot(project),
124
120
  status: tab.status,
125
121
  tabId: tab.id,
126
122
  title: tab.title,
127
- worktreeId: tab.worktreeId ?? null,
123
+ workspaceId: tab.workspaceId ?? null,
128
124
  }
129
125
  }
130
126
 
@@ -143,20 +139,20 @@ async function requireWorkerCapabilities(
143
139
 
144
140
  export async function listNamedWorkerTabs(
145
141
  ctx: CliContext,
146
- workspace: SessionRecord = ctx.getWorkspace()
142
+ project: ProjectRecord = ctx.getProject()
147
143
  ): Promise<TabSessionSummary[]> {
148
144
  const daemon = await requireWorkerCapabilities(ctx)
149
- return (await daemon.listTabs(workspace.id)).tabs.filter((tab) => tab.workerName !== undefined)
145
+ return (await daemon.listTabs(project.id)).tabs.filter((tab) => tab.workerName !== undefined)
150
146
  }
151
147
 
152
- /** Every named worker the daemon knows about, across every catalogued workspace. */
148
+ /** Every named worker the daemon knows about, across every catalogued project. */
153
149
  export async function listWorkerTargets(ctx: CliContext): Promise<WorkerTarget[]> {
154
150
  const daemon = await requireWorkerCapabilities(ctx)
155
151
  const targets: WorkerTarget[] = []
156
- for (const workspace of ctx.getWorkspaces?.() ?? listWorkspaces()) {
157
- const { tabs } = await daemon.listTabs(workspace.id)
152
+ for (const project of ctx.getProjects?.() ?? listProjects()) {
153
+ const { tabs } = await daemon.listTabs(project.id)
158
154
  for (const tab of tabs) {
159
- if (tab.workerName !== undefined) targets.push({ tab, workspace })
155
+ if (tab.workerName !== undefined) targets.push({ project, tab })
160
156
  }
161
157
  }
162
158
  return targets
@@ -167,47 +163,45 @@ function matches(tab: TabSessionSummary, selector: string): boolean {
167
163
  }
168
164
 
169
165
  /**
170
- * Resolve a worker selector to the tab AND the workspace that owns it.
166
+ * Resolve a worker selector to the tab AND the project that owns it.
171
167
  *
172
- * A worker is addressed for its whole lifetime, but the *active* workspace can
168
+ * A worker is addressed for its whole lifetime, but the *active* project can
173
169
  * change under it (the UI switching projects is enough). Resolving only against
174
- * the active workspace is what made a live fleet look dead — `worker list`
170
+ * the active project is what made a live fleet look dead — `worker list`
175
171
  * returning `[]` and `worker await` saying "worker not found" while all twelve
176
- * workers were healthy. So when the workspace was merely inferred, fall back to
172
+ * workers were healthy. So when the project was merely inferred, fall back to
177
173
  * a catalog-wide search and bind to wherever the worker actually lives; when it
178
- * was pinned (`--workspace` / `AIMUX_WORKSPACE`), keep the boundary but name the
179
- * workspace that does hold it instead of a bare "not found".
174
+ * was pinned (`--project` / `AIMUX_PROJECT`), keep the boundary but name the
175
+ * project that does hold it instead of a bare "not found".
180
176
  */
181
177
  export async function resolveWorkerTarget(
182
178
  ctx: CliContext,
183
179
  selector: string
184
180
  ): Promise<WorkerTarget> {
185
- const workspace = ctx.getWorkspace()
186
- const local = (await listNamedWorkerTabs(ctx, workspace)).filter((tab) => matches(tab, selector))
181
+ const project = ctx.getProject()
182
+ const local = (await listNamedWorkerTabs(ctx, project)).filter((tab) => matches(tab, selector))
187
183
  if (local.length > 1) {
188
184
  throw new Error(
189
- `worker selector is ambiguous in workspace "${workspace.name}": ${selector} (${local
185
+ `worker selector is ambiguous in project "${project.name}": ${selector} (${local
190
186
  .map((tab) => tab.id)
191
187
  .join(', ')})`
192
188
  )
193
189
  }
194
190
  const only = local[0]
195
- if (only) return { tab: only, workspace }
191
+ if (only) return { project, tab: only }
196
192
 
197
193
  const elsewhere = (await listWorkerTargets(ctx)).filter(
198
- (target) => target.workspace.id !== workspace.id && matches(target.tab, selector)
194
+ (target) => target.project.id !== project.id && matches(target.tab, selector)
199
195
  )
200
- const pinned = (ctx.getWorkspaceOrigin?.() ?? 'active') !== 'active'
196
+ const pinned = (ctx.getProjectOrigin?.() ?? 'active') !== 'active'
201
197
  if (elsewhere.length === 1 && elsewhere[0] && !pinned) return elsewhere[0]
202
198
  if (elsewhere.length > 0) {
203
- const where = elsewhere
204
- .map((target) => `${target.workspace.name} (${target.tab.id})`)
205
- .join(', ')
199
+ const where = elsewhere.map((target) => `${target.project.name} (${target.tab.id})`).join(', ')
206
200
  throw new Error(
207
- `worker not found in workspace "${workspace.name}": ${selector} — it lives in ${where}; re-run with --workspace`
201
+ `worker not found in project "${project.name}": ${selector} — it lives in ${where}; re-run with --project`
208
202
  )
209
203
  }
210
- throw new Error(`worker not found in workspace "${workspace.name}": ${selector}`)
204
+ throw new Error(`worker not found in project "${project.name}": ${selector}`)
211
205
  }
212
206
 
213
207
  export function normalizeTurnOutcome(outcome: TurnOutcome): WorkerOutcome {
@@ -331,11 +325,11 @@ async function armUptake(
331
325
  */
332
326
  async function liveUptake(
333
327
  daemon: Awaited<ReturnType<CliContext['getDaemon']>>,
334
- sessionId: string,
328
+ projectId: string,
335
329
  tabId: string,
336
330
  startedAt: number
337
331
  ): Promise<UptakeResult | undefined> {
338
- const live = (await daemon.listTabs(sessionId)).tabs.find((tab) => tab.id === tabId)
332
+ const live = (await daemon.listTabs(projectId)).tabs.find((tab) => tab.id === tabId)
339
333
  if (live?.activity !== 'working' && live?.activity !== 'waiting-input') return undefined
340
334
  return { activity: live.activity, confirmed: true, ms: Date.now() - startedAt }
341
335
  }
@@ -357,13 +351,13 @@ export interface DispatchOptions {
357
351
 
358
352
  export async function dispatchWorkerPrompt(
359
353
  ctx: CliContext,
360
- workspace: SessionRecord,
354
+ project: ProjectRecord,
361
355
  tabId: string,
362
356
  text: string,
363
357
  options: DispatchOptions
364
358
  ): Promise<WorkerOutcome> {
365
359
  const { daemon } = await requireTurnCapabilities(ctx)
366
- const attach = await daemon.attach({ cols: 0, rows: 0, sessionId: workspace.id, thin: true })
360
+ const attach = await daemon.attach({ cols: 0, projectId: project.id, rows: 0, thin: true })
367
361
  const attached = attach.tabs.find((tab) => tab.id === tabId)
368
362
  if (!attached) throw new Error(`tab not found: ${tabId}`)
369
363
  const payload = buildPromptPayload(text, false)
@@ -397,7 +391,7 @@ export async function dispatchWorkerPrompt(
397
391
  let resubmits = 0
398
392
 
399
393
  while (!result.confirmed && resubmits < MAX_UPTAKE_RESUBMITS) {
400
- const live = await liveUptake(daemon, workspace.id, tabId, startedAt)
394
+ const live = await liveUptake(daemon, project.id, tabId, startedAt)
401
395
  if (live) {
402
396
  result = live
403
397
  break
@@ -413,7 +407,7 @@ export async function dispatchWorkerPrompt(
413
407
  // Last look before declaring failure: never report a healthy, working worker
414
408
  // as pending just because an edge-triggered event was missed.
415
409
  if (!result.confirmed) {
416
- result = (await liveUptake(daemon, workspace.id, tabId, startedAt)) ?? result
410
+ result = (await liveUptake(daemon, project.id, tabId, startedAt)) ?? result
417
411
  }
418
412
 
419
413
  if (!result.confirmed) {
@@ -438,19 +432,19 @@ export async function dispatchWorkerPrompt(
438
432
  */
439
433
  export async function submitWorkerPrompt(
440
434
  ctx: CliContext,
441
- workspace: SessionRecord,
435
+ project: ProjectRecord,
442
436
  tabId: string,
443
437
  uptakeTimeoutMs: number
444
438
  ): Promise<WorkerOutcome> {
445
439
  const { daemon } = await requireTurnCapabilities(ctx)
446
- const attach = await daemon.attach({ cols: 0, rows: 0, sessionId: workspace.id, thin: true })
440
+ const attach = await daemon.attach({ cols: 0, projectId: project.id, rows: 0, thin: true })
447
441
  if (!attach.tabs.some((tab) => tab.id === tabId)) throw new Error(`tab not found: ${tabId}`)
448
442
  const startedAt = Date.now()
449
443
  const uptake = armUptake(daemon, tabId, uptakeTimeoutMs, startedAt)
450
444
  await daemon.expectOk('write', { data: '\r', tabId })
451
445
  const result = await uptake
452
446
  if (!result.confirmed) {
453
- const live = await liveUptake(daemon, workspace.id, tabId, startedAt)
447
+ const live = await liveUptake(daemon, project.id, tabId, startedAt)
454
448
  if (live) {
455
449
  return { durationMs: Date.now() - startedAt, status: 'dispatched', uptake: live }
456
450
  }
@@ -466,15 +460,15 @@ export async function submitWorkerPrompt(
466
460
 
467
461
  export async function awaitExistingWorker(
468
462
  ctx: CliContext,
469
- workspace: SessionRecord,
463
+ project: ProjectRecord,
470
464
  tabId: string,
471
465
  timeoutMs: number
472
466
  ): Promise<WorkerOutcome> {
473
467
  const { daemon } = await requireTurnCapabilities(ctx)
474
468
  const attach = await daemon.attach({
475
469
  cols: 0,
470
+ projectId: project.id,
476
471
  rows: 0,
477
- sessionId: workspace.id,
478
472
  thin: true,
479
473
  })
480
474
  const tab = attach.tabs.find((entry) => entry.id === tabId)
@@ -497,19 +491,19 @@ export async function awaitExistingWorker(
497
491
  }
498
492
 
499
493
  /**
500
- * Versioned envelope. `workspace` is not decoration: without it a caller cannot
494
+ * Versioned envelope. `project` is not decoration: without it a caller cannot
501
495
  * tell which project a worker belongs to, which is how a whole orchestration
502
496
  * can run against the wrong repository unnoticed.
503
497
  */
504
498
  export function workerEnvelope(
505
- workspace: SessionRecord,
499
+ project: ProjectRecord,
506
500
  worker: WorkerView,
507
501
  outcome?: WorkerOutcome
508
502
  ): Record<string, unknown> {
509
503
  return {
504
+ project: projectIdentity(project),
510
505
  schemaVersion: WORKER_SCHEMA_VERSION,
511
506
  worker,
512
- workspace: workspaceIdentity(workspace),
513
507
  ...(outcome === undefined ? {} : { outcome }),
514
508
  }
515
509
  }