@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.
- package/README.md +27 -25
- package/package.json +2 -2
- package/src/app-runtime/auto-commit-driver.ts +22 -27
- package/src/app-runtime/backend-attach-runtime.ts +43 -36
- package/src/app-runtime/backend-runtime-events.ts +107 -88
- package/src/app-runtime/editor-actions.ts +201 -0
- package/src/app-runtime/git-actions.ts +197 -0
- package/src/app-runtime/measure-ref.ts +19 -0
- package/src/app-runtime/navigation-actions.ts +213 -0
- package/src/app-runtime/project-actions.ts +188 -0
- package/src/app-runtime/prompt-injection.ts +123 -0
- package/src/app-runtime/selection.ts +29 -0
- package/src/app-runtime/settings-actions.ts +156 -0
- package/src/app-runtime/setup-actions.ts +237 -0
- package/src/app-runtime/side-effect-context.ts +33 -0
- package/src/app-runtime/side-effects.ts +302 -1541
- package/src/app-runtime/snippet-actions.ts +3 -2
- package/src/app-runtime/tab-actions.ts +218 -0
- package/src/app-runtime/tab-runtime-timeouts.ts +1 -1
- package/src/app-runtime/use-auto-commit-driver.ts +14 -13
- package/src/app-runtime/use-backend-runtime.ts +12 -11
- package/src/app-runtime/use-directory-search.ts +5 -4
- package/src/app-runtime/use-mouse-handlers.ts +2 -1
- package/src/app-runtime/{use-workspace-autosave.ts → use-project-autosave.ts} +4 -4
- package/src/app-runtime/use-renderer-bindings.ts +2 -1
- package/src/app-runtime/use-setup-runner.ts +96 -0
- package/src/app-runtime/use-terminal-resize.ts +18 -14
- package/src/app-runtime/workspace-actions.ts +366 -0
- package/src/app-runtime/workspace-activity.ts +138 -0
- package/src/app-runtime/workspace-naming.ts +90 -0
- package/src/app.tsx +93 -48
- package/src/assets/sounds/bell.wav +0 -0
- package/src/assets/sounds/ding.wav +0 -0
- package/src/assets/sounds/plane.wav +0 -0
- package/src/cli/client/daemon-client.ts +2 -2
- package/src/cli/client/project-resolver.ts +112 -0
- package/src/cli/commands/project/close.ts +32 -0
- package/src/cli/commands/project/create.ts +93 -0
- package/src/cli/commands/project/list.ts +25 -0
- package/src/cli/commands/project/show.ts +32 -0
- package/src/cli/commands/{workspace → project}/switch.ts +19 -19
- package/src/cli/commands/tab/await.ts +2 -2
- package/src/cli/commands/tab/close.ts +3 -3
- package/src/cli/commands/tab/create.ts +74 -73
- package/src/cli/commands/tab/focus.ts +3 -3
- package/src/cli/commands/tab/list.ts +6 -6
- package/src/cli/commands/tab/run.ts +2 -2
- package/src/cli/commands/tab/send.ts +2 -2
- package/src/cli/commands/tab/snapshot.ts +2 -2
- package/src/cli/commands/tab/tail.ts +2 -2
- package/src/cli/commands/tab/wait.ts +2 -2
- package/src/cli/commands/worker/await.ts +3 -3
- package/src/cli/commands/worker/doctor.ts +30 -30
- package/src/cli/commands/worker/list.ts +15 -15
- package/src/cli/commands/worker/prompt.ts +3 -3
- package/src/cli/commands/worker/run.ts +23 -23
- package/src/cli/commands/worker/shared.ts +53 -59
- package/src/cli/commands/worker/stop.ts +29 -29
- package/src/cli/commands/worker/submit.ts +3 -3
- package/src/cli/commands/{worktree → workspace}/create-core.ts +26 -26
- package/src/cli/commands/workspace/create.ts +30 -68
- package/src/cli/commands/workspace/list.ts +52 -9
- package/src/cli/commands/workspace/remove.ts +81 -0
- package/src/cli/completion/plan.ts +2 -2
- package/src/cli/completion/sources.ts +18 -18
- package/src/cli/context.ts +13 -13
- package/src/cli/flags.ts +30 -10
- package/src/cli/index.ts +26 -16
- package/src/cli/output.ts +1 -1
- package/src/cli/registry.ts +12 -12
- package/src/config/loader.ts +16 -5
- package/src/config.ts +70 -112
- package/src/daemon/catalog-writer.ts +67 -67
- package/src/daemon/daemon.ts +205 -201
- package/src/daemon/session-manager.ts +39 -39
- package/src/daemon/session-registry.ts +14 -14
- package/src/git/divergence.ts +33 -2
- package/src/git/git-poller.ts +1 -1
- package/src/git/git-status.ts +2 -2
- package/src/git/{move-worktree.ts → move-workspace.ts} +5 -5
- package/src/git/pr-status.ts +1 -1
- package/src/git/repo-discovery.ts +1 -1
- package/src/git/use-repo-discovery.ts +1 -1
- package/src/git/workspace-branch-poller.ts +54 -0
- package/src/git/workspace-divergence-poller.ts +65 -0
- package/src/git/worktree.ts +29 -0
- package/src/index.tsx +4 -4
- package/src/input/keymap/help-entries.ts +7 -5
- package/src/input/modes/bridge.ts +13 -12
- package/src/input/modes/handlers/shared.ts +1 -1
- package/src/input/modes/transitions.ts +34 -21
- package/src/input/modes/types.ts +48 -31
- package/src/ipc/manager-protocol.ts +44 -44
- package/src/ipc/protocol.ts +192 -166
- package/src/platform/play-sound.ts +184 -0
- package/src/platform/project-search.ts +8 -8
- package/src/platform/worktree-paths.ts +8 -6
- package/src/pty/assistant-question-extractor.ts +1 -1
- package/src/pty/assistant-status-detection-loop.ts +79 -55
- package/src/pty/assistant-status-detector.ts +40 -15
- package/src/pty/command-registry.ts +81 -1
- package/src/restart-terminal-manager.ts +1 -1
- package/src/services/ai-usage/provider.ts +9 -2
- package/src/services/ai-usage/spawn.ts +19 -6
- package/src/session-backend/bootstrap.ts +4 -4
- package/src/session-backend/local-session-backend.ts +78 -75
- package/src/session-backend/remote-session-backend.ts +64 -52
- package/src/session-backend/types.ts +43 -32
- package/src/settings/live.ts +90 -0
- package/src/settings/search.ts +43 -0
- package/src/settings/sections/about.ts +49 -0
- package/src/settings/sections/appearance.ts +54 -0
- package/src/settings/sections/automation.ts +105 -0
- package/src/settings/sections/commands.ts +72 -0
- package/src/settings/sections/editor.ts +56 -0
- package/src/settings/sections/experimental.ts +57 -0
- package/src/settings/sections/git.ts +112 -0
- package/src/settings/sections/index.ts +95 -0
- package/src/settings/sections/integrations.ts +23 -0
- package/src/settings/sections/layout.ts +72 -0
- package/src/settings/sections/notifications.ts +99 -0
- package/src/settings/sections/setup.ts +54 -0
- package/src/settings/sections/status-bar.ts +63 -0
- package/src/settings/settings-store.ts +207 -0
- package/src/settings/types.ts +108 -0
- package/src/snippets/run-shell-var.ts +48 -22
- package/src/state/actions.ts +342 -0
- package/src/state/app-store.ts +2 -1
- package/src/state/bars.ts +1 -1
- package/src/state/dispatch-ref.ts +1 -1
- package/src/state/layout-resize.ts +2 -2
- package/src/state/project-catalog.ts +275 -0
- package/src/state/project-data.ts +143 -0
- package/src/state/{session-persistence.ts → project-persistence.ts} +74 -68
- package/src/state/project-save.ts +46 -0
- package/src/state/project-workspaces.ts +392 -0
- package/src/state/reducers/auto-commit-state.ts +11 -10
- package/src/state/reducers/git-commit-modal-state.ts +122 -0
- package/src/state/reducers/git-mode-state.ts +1 -1
- package/src/state/reducers/git-panel-state.ts +10 -6
- package/src/state/reducers/modal-state.ts +290 -681
- package/src/state/reducers/multi-repo-state.ts +3 -6
- package/src/state/reducers/project-state.ts +312 -0
- package/src/state/reducers/settings-state.ts +80 -0
- package/src/state/reducers/tab-state.ts +107 -60
- package/src/state/reducers/ui-state.ts +4 -3
- package/src/state/selectors.ts +49 -37
- package/src/state/store.ts +57 -24
- package/src/state/types.ts +244 -405
- package/src/state/validation.ts +14 -12
- package/src/terminal-manager/manager-client.ts +32 -32
- package/src/terminal-manager/terminal-manager.ts +24 -24
- package/src/ui/components/git/git-panel.tsx +2 -2
- package/src/ui/components/git/git-view.tsx +12 -12
- package/src/ui/components/git/image-diff/image-diff-view.tsx +1 -1
- package/src/ui/components/git/pane/git-pane-widget.tsx +7 -7
- package/src/ui/components/layout/sidebar/project-list.tsx +444 -0
- package/src/ui/components/layout/sidebar/tab-item.tsx +27 -12
- package/src/ui/components/layout/sidebar/use-sidebar-auto-scroll.ts +1 -1
- package/src/ui/components/layout/sidebar/workspace-row.tsx +253 -0
- package/src/ui/components/layout/status-bar.tsx +6 -2
- package/src/ui/components/layout/terminal-pane.tsx +34 -29
- package/src/ui/components/layout/top-tab-bar.tsx +46 -36
- package/src/ui/components/modals/git/git-commit-modal.tsx +8 -8
- package/src/ui/components/modals/{sessions/create-session-modal.tsx → projects/create-project-modal.tsx} +12 -12
- package/src/ui/components/modals/{sessions/session-name-modal.tsx → projects/project-name-modal.tsx} +2 -2
- package/src/ui/components/modals/{sessions/session-picker-modal.tsx → projects/project-picker-modal.tsx} +33 -35
- package/src/ui/components/modals/settings/settings-search-modal.tsx +70 -0
- package/src/ui/components/modals/shared/form.tsx +34 -22
- package/src/ui/components/modals/shared/picker.tsx +10 -10
- package/src/ui/components/modals/shared/{worktree-delete-confirm.tsx → workspace-delete-confirm.tsx} +9 -9
- package/src/ui/components/modals/tabs/new-tab-modal.tsx +30 -324
- package/src/ui/components/modals/workspace/create-workspace-modal.tsx +101 -0
- package/src/ui/components/modals/{worktree/worktree-move-confirm-modal.tsx → workspace/workspace-move-confirm-modal.tsx} +5 -5
- package/src/ui/components/modals/{worktree/worktree-move-modal.tsx → workspace/workspace-move-modal.tsx} +29 -27
- package/src/ui/components/primitives/input-field.tsx +7 -3
- package/src/ui/components/primitives/surface.tsx +3 -0
- package/src/ui/components/settings/row-value.tsx +74 -0
- package/src/ui/components/settings/settings-row.tsx +78 -0
- package/src/ui/components/settings/settings-view.tsx +169 -0
- package/src/ui/components/setup/setup-widget.tsx +152 -0
- package/src/ui/flash/build-labels.ts +27 -27
- package/src/ui/hooks/use-activity-sprite.ts +73 -0
- package/src/ui/hooks/use-scroll-active-into-view.ts +26 -0
- package/src/ui/{session-ordering.ts → project-ordering.ts} +4 -4
- package/src/ui/root.tsx +139 -121
- package/src/ui/status-bar-model.ts +48 -34
- package/src/ui/terminal-graphics/kitty.ts +28 -2
- package/src/ui/terminal-graphics/sprites/done@330/0.png +0 -0
- package/src/ui/terminal-graphics/sprites/done@330/1.png +0 -0
- package/src/ui/terminal-graphics/sprites/idle@1000/0.png +0 -0
- package/src/ui/terminal-graphics/sprites/idle@1000/1.png +0 -0
- package/src/ui/terminal-graphics/sprites/waiting@170/0.png +0 -0
- package/src/ui/terminal-graphics/sprites/waiting@170/1.png +0 -0
- package/src/ui/terminal-graphics/sprites/working@150/0.png +0 -0
- package/src/ui/terminal-graphics/sprites/working@150/1.png +0 -0
- package/src/ui/terminal-graphics/sprites/working@150/2.png +0 -0
- package/src/ui/terminal-graphics/sprites/working@150/3.png +0 -0
- package/src/ui/terminal-graphics/sprites.ts +246 -0
- package/src/ui/truncate.ts +7 -0
- package/src/ui/widgets/registry.tsx +6 -3
- package/src/ui/widgets/widget-context-menu.ts +26 -10
- package/src/update.ts +2 -2
- package/src/app-runtime/session-actions.ts +0 -187
- package/src/cli/client/workspace-resolver.ts +0 -110
- package/src/cli/commands/workspace/close.ts +0 -32
- package/src/cli/commands/workspace/show.ts +0 -32
- package/src/cli/commands/worktree/create.ts +0 -55
- package/src/cli/commands/worktree/list.ts +0 -68
- package/src/cli/commands/worktree/remove.ts +0 -81
- package/src/git/worktree-branch-poller.ts +0 -54
- package/src/git/worktree-divergence-poller.ts +0 -59
- package/src/state/reducers/session-state.ts +0 -262
- package/src/state/session-catalog.ts +0 -143
- package/src/state/session-worktrees.ts +0 -260
- package/src/state/workspace-save.ts +0 -43
- package/src/ui/components/layout/sidebar/use-top-tab-bar-auto-scroll.ts +0 -30
- package/src/ui/components/layout/sidebar/workspace-list.tsx +0 -418
- package/src/ui/components/layout/sidebar/worktree-row.tsx +0 -149
- /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 {
|
|
4
|
-
import {
|
|
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
|
|
11
|
-
args: [{ complete: { kind: 'dynamic', source: '
|
|
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: '
|
|
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
|
|
29
|
+
throw new Error('target project is required (id or name)')
|
|
30
30
|
}
|
|
31
|
-
const
|
|
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(
|
|
37
|
+
if (!daemon.hasCapability(IPC_CAPABILITY_PROJECT_LIFECYCLE)) {
|
|
38
38
|
throw new Error(
|
|
39
|
-
'daemon predates
|
|
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('
|
|
45
|
-
writeJson({ name:
|
|
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 `
|
|
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('
|
|
54
|
-
if (payload.
|
|
53
|
+
const off = daemon.on('projectSwitched', (payload) => {
|
|
54
|
+
if (payload.projectId !== project.id) return
|
|
55
55
|
off()
|
|
56
56
|
clearTimeout(timer)
|
|
57
|
-
writeJson({ name:
|
|
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
|
|
64
|
-
|
|
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('
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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,
|
|
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
|
|
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 {
|
|
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 {
|
|
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
|
-
|
|
38
|
+
newWorkspace?: boolean | string
|
|
39
39
|
title?: string
|
|
40
40
|
workerName?: string
|
|
41
|
-
|
|
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
|
-
|
|
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
|
|
62
|
-
* daemon's default).
|
|
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
|
-
|
|
66
|
+
workspaceRecord: { path: string } | undefined
|
|
67
67
|
): string | undefined {
|
|
68
68
|
if (cwdFlag !== undefined) return resolvePath(cwdFlag)
|
|
69
|
-
return
|
|
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
|
|
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
|
|
89
|
-
record:
|
|
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
|
|
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('
|
|
104
|
-
|
|
105
|
-
|
|
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
|
|
121
|
-
* created; any later failure rolls the fresh
|
|
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
|
-
|
|
135
|
+
newWorkspace,
|
|
136
136
|
title: requestedTitle,
|
|
137
137
|
workerName,
|
|
138
|
-
|
|
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
|
|
151
|
-
if (
|
|
150
|
+
const createFreshWorkspace = newWorkspace !== undefined && newWorkspace !== false
|
|
151
|
+
if (createFreshWorkspace && requestedWorkspaceId !== undefined) {
|
|
152
152
|
throw new CliUsageError(
|
|
153
|
-
'--new-
|
|
153
|
+
'--new-workspace creates its own; use --workspace <id> to co-locate instead'
|
|
154
154
|
)
|
|
155
155
|
}
|
|
156
|
-
if (
|
|
157
|
-
throw new CliUsageError('--new-
|
|
156
|
+
if (createFreshWorkspace && cwdRaw !== undefined) {
|
|
157
|
+
throw new CliUsageError('--new-workspace sets the cwd to the new workspace; drop --cwd')
|
|
158
158
|
}
|
|
159
|
-
if (!
|
|
159
|
+
if (!createFreshWorkspace && (base !== undefined || branch !== undefined)) {
|
|
160
160
|
throw new CliUsageError(
|
|
161
|
-
'--base / --branch require --new-
|
|
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
|
|
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(
|
|
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
|
|
198
|
+
throw new Error(`worker name already exists in project "${project.name}": ${workerName}`)
|
|
199
199
|
}
|
|
200
200
|
}
|
|
201
201
|
|
|
202
|
-
// Attach before creating a
|
|
202
|
+
// Attach before creating a workspace so stale daemon/project failures have no
|
|
203
203
|
// git-side effect.
|
|
204
|
-
await daemon.attach({ cols: 0,
|
|
204
|
+
await daemon.attach({ cols: 0, projectId: project.id, rows: 0, thin: true })
|
|
205
205
|
|
|
206
|
-
let
|
|
207
|
-
let
|
|
208
|
-
|
|
209
|
-
?
|
|
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 (
|
|
212
|
-
const ids =
|
|
213
|
-
throw new Error(`unknown
|
|
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
|
|
217
|
-
if (
|
|
218
|
-
const
|
|
219
|
-
typeof
|
|
220
|
-
?
|
|
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
|
-
|
|
222
|
+
createdWorkspace = await createProjectWorkspace({
|
|
223
223
|
base: base ?? 'HEAD',
|
|
224
|
-
branch: branch ?? `aimux/${
|
|
224
|
+
branch: branch ?? `aimux/${workspaceName}`,
|
|
225
225
|
daemon,
|
|
226
|
-
name:
|
|
227
|
-
|
|
226
|
+
name: workspaceName,
|
|
227
|
+
project,
|
|
228
228
|
})
|
|
229
|
-
|
|
230
|
-
|
|
229
|
+
workspaceId = createdWorkspace.id
|
|
230
|
+
workspaceRecord = createdWorkspace
|
|
231
231
|
}
|
|
232
232
|
|
|
233
|
-
const cwd = resolveTabCwd(cwdRaw,
|
|
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
|
-
|
|
247
|
+
workspaceId,
|
|
248
248
|
})
|
|
249
249
|
} catch (error) {
|
|
250
|
-
if (
|
|
251
|
-
return
|
|
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:
|
|
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:
|
|
264
|
-
path:
|
|
263
|
+
name: workspaceRecord?.name ?? null,
|
|
264
|
+
path: workspaceRecord?.path ?? null,
|
|
265
265
|
tabId,
|
|
266
266
|
title,
|
|
267
267
|
workerName: workerName ?? null,
|
|
268
|
-
|
|
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: '
|
|
314
|
-
description: '
|
|
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: '
|
|
316
|
+
name: 'workspace',
|
|
317
317
|
},
|
|
318
318
|
{
|
|
319
319
|
complete: { kind: 'none' },
|
|
320
|
-
description:
|
|
320
|
+
description:
|
|
321
|
+
'create a fresh workspace for this tab (optionally named: --new-workspace=<name>)',
|
|
321
322
|
kind: 'optional-string',
|
|
322
|
-
name: 'new-
|
|
323
|
+
name: 'new-workspace',
|
|
323
324
|
},
|
|
324
325
|
{
|
|
325
326
|
complete: { kind: 'dynamic', source: 'git-ref' },
|
|
326
|
-
description: 'base ref for --new-
|
|
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-
|
|
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
|
|
351
|
-
const
|
|
352
|
-
typeof
|
|
353
|
-
?
|
|
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
|
|
356
|
-
typeof ctx.args.flags.
|
|
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
|
-
|
|
368
|
+
newWorkspace: newWorkspaceFlag,
|
|
368
369
|
title,
|
|
369
|
-
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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(
|
|
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
|
-
//
|
|
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
|
-
|
|
76
|
+
workspaceId: tab.workspaceId,
|
|
77
77
|
})),
|
|
78
78
|
})
|
|
79
79
|
return EXIT_OK
|
|
80
80
|
},
|
|
81
|
-
summary: 'List tabs in the active
|
|
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
|
|
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,
|
|
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
|
|
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,
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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 {
|
|
22
|
+
const { project, tab } = await resolveWorkerTarget(ctx, ctx.args.positionals[0] ?? '')
|
|
23
23
|
const outcome = await awaitExistingWorker(
|
|
24
24
|
ctx,
|
|
25
|
-
|
|
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(
|
|
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",
|