@brimveyn/aimux 1.20.4 → 1.22.0

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 (233) 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 +305 -1589
  17. package/src/app-runtime/snippet-actions.ts +3 -2
  18. package/src/app-runtime/split-drag-controller.ts +4 -8
  19. package/src/app-runtime/tab-actions.ts +218 -0
  20. package/src/app-runtime/tab-runtime-timeouts.ts +1 -1
  21. package/src/app-runtime/use-auto-commit-driver.ts +14 -13
  22. package/src/app-runtime/use-backend-runtime.ts +12 -11
  23. package/src/app-runtime/use-directory-search.ts +5 -4
  24. package/src/app-runtime/use-mouse-handlers.ts +28 -42
  25. package/src/app-runtime/{use-workspace-autosave.ts → use-project-autosave.ts} +4 -4
  26. package/src/app-runtime/use-renderer-bindings.ts +2 -1
  27. package/src/app-runtime/use-setup-runner.ts +96 -0
  28. package/src/app-runtime/use-terminal-resize.ts +28 -33
  29. package/src/app-runtime/workspace-actions.ts +366 -0
  30. package/src/app-runtime/workspace-activity.ts +138 -0
  31. package/src/app-runtime/workspace-naming.ts +90 -0
  32. package/src/app.tsx +102 -85
  33. package/src/assets/sounds/bell.wav +0 -0
  34. package/src/assets/sounds/ding.wav +0 -0
  35. package/src/assets/sounds/plane.wav +0 -0
  36. package/src/cli/client/daemon-client.ts +2 -2
  37. package/src/cli/client/project-resolver.ts +112 -0
  38. package/src/cli/commands/project/close.ts +32 -0
  39. package/src/cli/commands/project/create.ts +93 -0
  40. package/src/cli/commands/project/list.ts +25 -0
  41. package/src/cli/commands/project/show.ts +32 -0
  42. package/src/cli/commands/{workspace → project}/switch.ts +19 -19
  43. package/src/cli/commands/tab/await.ts +2 -2
  44. package/src/cli/commands/tab/close.ts +3 -3
  45. package/src/cli/commands/tab/create.ts +74 -73
  46. package/src/cli/commands/tab/focus.ts +3 -3
  47. package/src/cli/commands/tab/list.ts +6 -6
  48. package/src/cli/commands/tab/run.ts +2 -2
  49. package/src/cli/commands/tab/send.ts +2 -2
  50. package/src/cli/commands/tab/snapshot.ts +2 -2
  51. package/src/cli/commands/tab/tail.ts +2 -2
  52. package/src/cli/commands/tab/wait.ts +2 -2
  53. package/src/cli/commands/worker/await.ts +3 -3
  54. package/src/cli/commands/worker/doctor.ts +30 -30
  55. package/src/cli/commands/worker/list.ts +15 -15
  56. package/src/cli/commands/worker/prompt.ts +3 -3
  57. package/src/cli/commands/worker/run.ts +23 -23
  58. package/src/cli/commands/worker/shared.ts +53 -59
  59. package/src/cli/commands/worker/stop.ts +29 -29
  60. package/src/cli/commands/worker/submit.ts +3 -3
  61. package/src/cli/commands/{worktree → workspace}/create-core.ts +26 -26
  62. package/src/cli/commands/workspace/create.ts +30 -68
  63. package/src/cli/commands/workspace/list.ts +52 -9
  64. package/src/cli/commands/workspace/remove.ts +81 -0
  65. package/src/cli/completion/plan.ts +2 -2
  66. package/src/cli/completion/sources.ts +18 -18
  67. package/src/cli/context.ts +13 -13
  68. package/src/cli/flags.ts +30 -10
  69. package/src/cli/index.ts +26 -16
  70. package/src/cli/output.ts +1 -1
  71. package/src/cli/registry.ts +12 -12
  72. package/src/config/loader.ts +16 -5
  73. package/src/config.ts +149 -101
  74. package/src/daemon/catalog-writer.ts +67 -67
  75. package/src/daemon/daemon.ts +205 -201
  76. package/src/daemon/session-manager.ts +39 -39
  77. package/src/daemon/session-registry.ts +14 -14
  78. package/src/git/divergence.ts +33 -2
  79. package/src/git/git-poller.ts +1 -1
  80. package/src/git/git-status.ts +2 -2
  81. package/src/git/{move-worktree.ts → move-workspace.ts} +5 -5
  82. package/src/git/pr-merge.ts +64 -0
  83. package/src/git/pr-status-poller.ts +57 -0
  84. package/src/git/pr-status.ts +227 -0
  85. package/src/git/repo-discovery.ts +1 -1
  86. package/src/git/use-repo-discovery.ts +1 -1
  87. package/src/git/workspace-branch-poller.ts +54 -0
  88. package/src/git/workspace-divergence-poller.ts +65 -0
  89. package/src/git/worktree.ts +29 -0
  90. package/src/index.tsx +11 -6
  91. package/src/input/keymap/help-entries.ts +7 -5
  92. package/src/input/modes/bridge.ts +13 -12
  93. package/src/input/modes/handlers/shared.ts +1 -1
  94. package/src/input/modes/transitions.ts +34 -21
  95. package/src/input/modes/types.ts +48 -31
  96. package/src/ipc/manager-protocol.ts +44 -44
  97. package/src/ipc/protocol.ts +192 -166
  98. package/src/platform/open-url.ts +39 -0
  99. package/src/platform/play-sound.ts +184 -0
  100. package/src/platform/project-search.ts +8 -8
  101. package/src/platform/worktree-paths.ts +8 -6
  102. package/src/pty/assistant-question-extractor.ts +1 -1
  103. package/src/pty/assistant-status-detection-loop.ts +79 -55
  104. package/src/pty/assistant-status-detector.ts +40 -15
  105. package/src/pty/command-registry.ts +81 -1
  106. package/src/restart-terminal-manager.ts +1 -1
  107. package/src/services/ai-usage/provider.ts +9 -2
  108. package/src/services/ai-usage/spawn.ts +3 -1
  109. package/src/session-backend/bootstrap.ts +4 -4
  110. package/src/session-backend/local-session-backend.ts +78 -75
  111. package/src/session-backend/remote-session-backend.ts +64 -52
  112. package/src/session-backend/types.ts +43 -32
  113. package/src/settings/live.ts +90 -0
  114. package/src/settings/search.ts +43 -0
  115. package/src/settings/sections/about.ts +49 -0
  116. package/src/settings/sections/appearance.ts +54 -0
  117. package/src/settings/sections/automation.ts +105 -0
  118. package/src/settings/sections/commands.ts +72 -0
  119. package/src/settings/sections/editor.ts +56 -0
  120. package/src/settings/sections/experimental.ts +57 -0
  121. package/src/settings/sections/git.ts +112 -0
  122. package/src/settings/sections/index.ts +95 -0
  123. package/src/settings/sections/integrations.ts +23 -0
  124. package/src/settings/sections/layout.ts +72 -0
  125. package/src/settings/sections/notifications.ts +99 -0
  126. package/src/settings/sections/setup.ts +54 -0
  127. package/src/settings/sections/status-bar.ts +63 -0
  128. package/src/settings/settings-store.ts +207 -0
  129. package/src/settings/types.ts +108 -0
  130. package/src/snippets/run-shell-var.ts +48 -22
  131. package/src/state/actions.ts +342 -0
  132. package/src/state/app-store.ts +2 -1
  133. package/src/state/bars.ts +75 -0
  134. package/src/state/dispatch-ref.ts +1 -1
  135. package/src/state/git-pane-sizing.ts +0 -9
  136. package/src/state/layout-resize.ts +2 -2
  137. package/src/state/pr-status-store.ts +39 -0
  138. package/src/state/project-catalog.ts +275 -0
  139. package/src/state/project-data.ts +143 -0
  140. package/src/state/{session-persistence.ts → project-persistence.ts} +77 -73
  141. package/src/state/project-save.ts +46 -0
  142. package/src/state/project-workspaces.ts +392 -0
  143. package/src/state/reducers/auto-commit-state.ts +11 -10
  144. package/src/state/reducers/git-commit-modal-state.ts +122 -0
  145. package/src/state/reducers/git-mode-state.ts +1 -1
  146. package/src/state/reducers/git-panel-state.ts +10 -53
  147. package/src/state/reducers/modal-state.ts +290 -681
  148. package/src/state/reducers/multi-repo-state.ts +3 -6
  149. package/src/state/reducers/project-state.ts +312 -0
  150. package/src/state/reducers/settings-state.ts +80 -0
  151. package/src/state/reducers/tab-state.ts +107 -60
  152. package/src/state/reducers/ui-state.ts +86 -15
  153. package/src/state/selectors.ts +49 -37
  154. package/src/state/store.ts +83 -57
  155. package/src/state/types.ts +261 -417
  156. package/src/state/validation.ts +14 -12
  157. package/src/terminal-manager/manager-client.ts +32 -32
  158. package/src/terminal-manager/terminal-manager.ts +24 -24
  159. package/src/ui/components/git/diff-renderer/pierre-diff.tsx +2 -1
  160. package/src/ui/components/git/git-panel.tsx +2 -2
  161. package/src/ui/components/git/git-view.tsx +12 -12
  162. package/src/ui/components/git/image-diff/image-diff-view.tsx +1 -1
  163. package/src/ui/components/git/pane/git-pane-header.tsx +105 -39
  164. package/src/ui/components/git/pane/git-pane-widget.tsx +37 -23
  165. package/src/ui/components/git/pane/pr-checks-panel.tsx +197 -0
  166. package/src/ui/components/git/pane/pr-state-row.tsx +103 -0
  167. package/src/ui/components/layout/bar.tsx +191 -0
  168. package/src/ui/components/layout/sidebar/project-list.tsx +444 -0
  169. package/src/ui/components/layout/sidebar/tab-item.tsx +27 -12
  170. package/src/ui/components/layout/sidebar/use-sidebar-auto-scroll.ts +1 -1
  171. package/src/ui/components/layout/sidebar/workspace-row.tsx +253 -0
  172. package/src/ui/components/layout/status-bar.tsx +6 -2
  173. package/src/ui/components/layout/terminal-pane.tsx +34 -29
  174. package/src/ui/components/layout/top-tab-bar.tsx +48 -37
  175. package/src/ui/components/modals/git/git-commit-modal.tsx +8 -8
  176. package/src/ui/components/modals/{sessions/create-session-modal.tsx → projects/create-project-modal.tsx} +12 -12
  177. package/src/ui/components/modals/{sessions/session-name-modal.tsx → projects/project-name-modal.tsx} +2 -2
  178. package/src/ui/components/modals/{sessions/session-picker-modal.tsx → projects/project-picker-modal.tsx} +33 -35
  179. package/src/ui/components/modals/settings/settings-search-modal.tsx +70 -0
  180. package/src/ui/components/modals/shared/form.tsx +34 -22
  181. package/src/ui/components/modals/shared/picker.tsx +10 -10
  182. package/src/ui/components/modals/shared/{worktree-delete-confirm.tsx → workspace-delete-confirm.tsx} +9 -9
  183. package/src/ui/components/modals/tabs/new-tab-modal.tsx +30 -324
  184. package/src/ui/components/modals/workspace/create-workspace-modal.tsx +101 -0
  185. package/src/ui/components/modals/{worktree/worktree-move-confirm-modal.tsx → workspace/workspace-move-confirm-modal.tsx} +5 -5
  186. package/src/ui/components/modals/{worktree/worktree-move-modal.tsx → workspace/workspace-move-modal.tsx} +29 -27
  187. package/src/ui/components/primitives/input-field.tsx +7 -3
  188. package/src/ui/components/primitives/surface.tsx +3 -0
  189. package/src/ui/components/settings/row-value.tsx +74 -0
  190. package/src/ui/components/settings/settings-row.tsx +78 -0
  191. package/src/ui/components/settings/settings-view.tsx +169 -0
  192. package/src/ui/components/setup/setup-widget.tsx +152 -0
  193. package/src/ui/flash/build-labels.ts +27 -27
  194. package/src/ui/hooks/use-activity-sprite.ts +73 -0
  195. package/src/ui/hooks/use-scroll-active-into-view.ts +26 -0
  196. package/src/ui/{session-ordering.ts → project-ordering.ts} +4 -4
  197. package/src/ui/root.tsx +164 -230
  198. package/src/ui/status-bar-model.ts +48 -34
  199. package/src/ui/terminal-graphics/kitty.ts +28 -2
  200. package/src/ui/terminal-graphics/sprites/done@330/0.png +0 -0
  201. package/src/ui/terminal-graphics/sprites/done@330/1.png +0 -0
  202. package/src/ui/terminal-graphics/sprites/idle@1000/0.png +0 -0
  203. package/src/ui/terminal-graphics/sprites/idle@1000/1.png +0 -0
  204. package/src/ui/terminal-graphics/sprites/waiting@170/0.png +0 -0
  205. package/src/ui/terminal-graphics/sprites/waiting@170/1.png +0 -0
  206. package/src/ui/terminal-graphics/sprites/working@150/0.png +0 -0
  207. package/src/ui/terminal-graphics/sprites/working@150/1.png +0 -0
  208. package/src/ui/terminal-graphics/sprites/working@150/2.png +0 -0
  209. package/src/ui/terminal-graphics/sprites/working@150/3.png +0 -0
  210. package/src/ui/terminal-graphics/sprites.ts +246 -0
  211. package/src/ui/truncate.ts +7 -0
  212. package/src/ui/widgets/registry.tsx +21 -0
  213. package/src/ui/widgets/widget-context-menu.ts +85 -0
  214. package/src/update.ts +2 -2
  215. package/src/app-runtime/session-actions.ts +0 -187
  216. package/src/cli/client/workspace-resolver.ts +0 -110
  217. package/src/cli/commands/workspace/close.ts +0 -32
  218. package/src/cli/commands/workspace/show.ts +0 -32
  219. package/src/cli/commands/worktree/create.ts +0 -55
  220. package/src/cli/commands/worktree/list.ts +0 -68
  221. package/src/cli/commands/worktree/remove.ts +0 -81
  222. package/src/git/worktree-branch-poller.ts +0 -54
  223. package/src/git/worktree-divergence-poller.ts +0 -59
  224. package/src/state/reducers/session-state.ts +0 -262
  225. package/src/state/session-catalog.ts +0 -143
  226. package/src/state/session-worktrees.ts +0 -260
  227. package/src/state/workspace-save.ts +0 -45
  228. package/src/ui/components/git/pane/git-pane-context-menu.ts +0 -26
  229. package/src/ui/components/layout/sidebar/sidebar.tsx +0 -163
  230. package/src/ui/components/layout/sidebar/use-top-tab-bar-auto-scroll.ts +0 -30
  231. package/src/ui/components/layout/sidebar/workspace-list.tsx +0 -418
  232. package/src/ui/components/layout/sidebar/worktree-row.tsx +0 -149
  233. /package/src/ui/{components/git/image-diff → terminal-graphics}/dimensions.ts +0 -0
@@ -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
  }
@@ -3,10 +3,10 @@ import type { CliCommand } from '../../registry'
3
3
  import { isGitWorktreeDirty, removeGitWorktree } from '../../../git/worktree'
4
4
  import {
5
5
  IPC_CAPABILITY_THIN_ATTACH,
6
- IPC_CAPABILITY_WORKTREE_LIFECYCLE_EVENTS,
6
+ IPC_CAPABILITY_WORKSPACE_LIFECYCLE_EVENTS,
7
7
  } from '../../../ipc/protocol'
8
8
  import { pruneEmptyWorktreeParent } from '../../../platform/worktree-paths'
9
- import { workspaceIdentity } from '../../client/workspace-resolver'
9
+ import { projectIdentity } from '../../client/project-resolver'
10
10
  import { SHARED_FLAGS } from '../../flags'
11
11
  import { EXIT_OK, EXIT_RUNTIME, writeJson } from '../../output'
12
12
  import { resolveWorkerTarget, WORKER_SCHEMA_VERSION, workerView } from './shared'
@@ -16,45 +16,45 @@ export const workerStop: CliCommand = {
16
16
  flags: [
17
17
  ...SHARED_FLAGS,
18
18
  {
19
- description: 'remove its aimux-created worktree after closing the tab',
19
+ description: 'remove its aimux-created workspace after closing the tab',
20
20
  kind: 'boolean',
21
- name: 'cleanup-worktree',
21
+ name: 'cleanup-workspace',
22
22
  },
23
- { description: 'force removal of a dirty worktree', kind: 'boolean', name: 'force' },
23
+ { description: 'force removal of a dirty workspace', kind: 'boolean', name: 'force' },
24
24
  ],
25
25
  group: 'worker',
26
26
  run: async (ctx) => {
27
- const { tab, workspace } = await resolveWorkerTarget(ctx, ctx.args.positionals[0] ?? '')
28
- const worker = workerView(workspace, tab)
27
+ const { project, tab } = await resolveWorkerTarget(ctx, ctx.args.positionals[0] ?? '')
28
+ const worker = workerView(project, tab)
29
29
  const daemon = await ctx.getDaemon()
30
- const cleanup = ctx.args.flags['cleanup-worktree'] === true
30
+ const cleanup = ctx.args.flags['cleanup-workspace'] === true
31
31
  const record =
32
- tab.worktreeId === undefined
32
+ tab.workspaceId === undefined
33
33
  ? undefined
34
- : workspace.worktrees?.find((worktree) => worktree.id === tab.worktreeId)
34
+ : project.workspaces?.find((workspace) => workspace.id === tab.workspaceId)
35
35
 
36
36
  if (cleanup) {
37
- if (record === undefined) throw new Error('worker has no registered worktree to clean up')
37
+ if (record === undefined) throw new Error('worker has no registered workspace to clean up')
38
38
  if (record.source !== 'aimux-temp' || !record.createdByAimux) {
39
- throw new Error('refusing to clean up a primary or externally managed worktree')
39
+ throw new Error('refusing to clean up a primary or externally managed workspace')
40
40
  }
41
- const siblings = (await daemon.listTabs(workspace.id)).tabs.filter(
42
- (entry) => entry.id !== tab.id && entry.worktreeId === record.id
41
+ const siblings = (await daemon.listTabs(project.id)).tabs.filter(
42
+ (entry) => entry.id !== tab.id && entry.workspaceId === record.id
43
43
  )
44
44
  if (siblings.length > 0) {
45
45
  throw new Error(
46
- `refusing to clean up shared worktree; live tabs: ${siblings.map((entry) => entry.id).join(', ')}`
46
+ `refusing to clean up shared workspace; live tabs: ${siblings.map((entry) => entry.id).join(', ')}`
47
47
  )
48
48
  }
49
- if (!daemon.hasCapability(IPC_CAPABILITY_WORKTREE_LIFECYCLE_EVENTS)) {
50
- throw new Error('daemon predates safe worktree cleanup — restart aimux')
49
+ if (!daemon.hasCapability(IPC_CAPABILITY_WORKSPACE_LIFECYCLE_EVENTS)) {
50
+ throw new Error('daemon predates safe workspace cleanup — restart aimux')
51
51
  }
52
52
  if (ctx.args.flags.force !== true && (await isGitWorktreeDirty(record.path))) {
53
- throw new Error('refusing to clean up a dirty worktree without --force')
53
+ throw new Error('refusing to clean up a dirty workspace without --force')
54
54
  }
55
55
  }
56
56
 
57
- // `closeTab` is session-scoped on the daemon, so it fails with "No session
57
+ // `closeTab` is project-scoped on the daemon, so it fails with "No project
58
58
  // attached" unless this connection has attached first. Every other worker
59
59
  // verb thin-attaches; this one did not, which made teardown the one step of
60
60
  // the documented lifecycle a headless orchestrator could not complete.
@@ -63,10 +63,10 @@ export const workerStop: CliCommand = {
63
63
  'daemon predates thinAttach capability — restart aimux to pick up the new daemon'
64
64
  )
65
65
  }
66
- await daemon.attach({ cols: 0, rows: 0, sessionId: workspace.id, thin: true })
66
+ await daemon.attach({ cols: 0, projectId: project.id, rows: 0, thin: true })
67
67
 
68
68
  // Teardown is two independent effects. Report them independently: a failed
69
- // tab close must not strand a merged worktree on disk with no way to remove
69
+ // tab close must not strand a merged workspace on disk with no way to remove
70
70
  // it through aimux (the alternative — a bare `git worktree remove` — desyncs
71
71
  // the catalog from disk).
72
72
  let closeError: string | undefined
@@ -77,7 +77,7 @@ export const workerStop: CliCommand = {
77
77
  if (!cleanup) throw error
78
78
  }
79
79
 
80
- let worktreeRemoved = false
80
+ let workspaceRemoved = false
81
81
  if (cleanup && record !== undefined) {
82
82
  await removeGitWorktree({
83
83
  force: ctx.args.flags.force === true,
@@ -86,9 +86,9 @@ export const workerStop: CliCommand = {
86
86
  })
87
87
  await pruneEmptyWorktreeParent(record.path)
88
88
  try {
89
- await daemon.expectOk('removeWorktreeRecord', {
90
- sessionId: workspace.id,
91
- worktreeId: record.id,
89
+ await daemon.expectOk('removeWorkspaceRecord', {
90
+ projectId: project.id,
91
+ workspaceId: record.id,
92
92
  })
93
93
  } catch (error) {
94
94
  const message = error instanceof Error ? error.message : String(error)
@@ -96,18 +96,18 @@ export const workerStop: CliCommand = {
96
96
  `worker stopped and git worktree removed, but catalog reconciliation failed: ${message}`
97
97
  )
98
98
  }
99
- worktreeRemoved = true
99
+ workspaceRemoved = true
100
100
  }
101
101
  writeJson({
102
102
  closed: closeError === undefined,
103
103
  ...(closeError === undefined ? {} : { closeError }),
104
+ project: projectIdentity(project),
104
105
  schemaVersion: WORKER_SCHEMA_VERSION,
105
106
  worker,
106
- workspace: workspaceIdentity(workspace),
107
- worktreeRemoved,
107
+ workspaceRemoved,
108
108
  })
109
109
  return closeError === undefined ? EXIT_OK : EXIT_RUNTIME
110
110
  },
111
- summary: 'Stop a named worker and optionally clean up its worktree',
111
+ summary: 'Stop a named worker and optionally clean up its workspace',
112
112
  verb: 'stop',
113
113
  }
@@ -23,16 +23,16 @@ export const workerSubmit: CliCommand = {
23
23
  ],
24
24
  group: 'worker',
25
25
  run: async (ctx) => {
26
- const { tab, workspace } = await resolveWorkerTarget(ctx, ctx.args.positionals[0] ?? '')
26
+ const { project, tab } = await resolveWorkerTarget(ctx, ctx.args.positionals[0] ?? '')
27
27
  const outcome = await submitWorkerPrompt(
28
28
  ctx,
29
- workspace,
29
+ project,
30
30
  tab.id,
31
31
  typeof ctx.args.flags['uptake-timeout'] === 'number'
32
32
  ? ctx.args.flags['uptake-timeout']
33
33
  : DETACH_UPTAKE_TIMEOUT_MS
34
34
  )
35
- writeJson(workerEnvelope(workspace, workerView(workspace, tab), outcome))
35
+ writeJson(workerEnvelope(project, workerView(project, tab), outcome))
36
36
  return workerOutcomeExitCode(outcome)
37
37
  },
38
38
  summary: 'Submit a prompt already sitting in a worker composer and confirm uptake',