@brimveyn/aimux 1.21.0 → 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.
- 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/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
|
@@ -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
|
-
|
|
6
|
+
IPC_CAPABILITY_WORKSPACE_LIFECYCLE_EVENTS,
|
|
7
7
|
} from '../../../ipc/protocol'
|
|
8
8
|
import { pruneEmptyWorktreeParent } from '../../../platform/worktree-paths'
|
|
9
|
-
import {
|
|
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
|
|
19
|
+
description: 'remove its aimux-created workspace after closing the tab',
|
|
20
20
|
kind: 'boolean',
|
|
21
|
-
name: 'cleanup-
|
|
21
|
+
name: 'cleanup-workspace',
|
|
22
22
|
},
|
|
23
|
-
{ description: 'force removal of a dirty
|
|
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 {
|
|
28
|
-
const worker = workerView(
|
|
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-
|
|
30
|
+
const cleanup = ctx.args.flags['cleanup-workspace'] === true
|
|
31
31
|
const record =
|
|
32
|
-
tab.
|
|
32
|
+
tab.workspaceId === undefined
|
|
33
33
|
? undefined
|
|
34
|
-
:
|
|
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
|
|
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
|
|
39
|
+
throw new Error('refusing to clean up a primary or externally managed workspace')
|
|
40
40
|
}
|
|
41
|
-
const siblings = (await daemon.listTabs(
|
|
42
|
-
(entry) => entry.id !== tab.id && entry.
|
|
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
|
|
46
|
+
`refusing to clean up shared workspace; live tabs: ${siblings.map((entry) => entry.id).join(', ')}`
|
|
47
47
|
)
|
|
48
48
|
}
|
|
49
|
-
if (!daemon.hasCapability(
|
|
50
|
-
throw new Error('daemon predates safe
|
|
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
|
|
53
|
+
throw new Error('refusing to clean up a dirty workspace without --force')
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
// `closeTab` is
|
|
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,
|
|
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
|
|
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
|
|
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('
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 {
|
|
26
|
+
const { project, tab } = await resolveWorkerTarget(ctx, ctx.args.positionals[0] ?? '')
|
|
27
27
|
const outcome = await submitWorkerPrompt(
|
|
28
28
|
ctx,
|
|
29
|
-
|
|
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(
|
|
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',
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ProjectRecord, WorkspaceRecord } from '../../../state/types'
|
|
2
2
|
import type { DaemonClient } from '../../client/daemon-client'
|
|
3
3
|
|
|
4
4
|
import { createGitWorktree, removeGitWorktree, resolveGitRef } from '../../../git/worktree'
|
|
5
|
-
import {
|
|
5
|
+
import { IPC_CAPABILITY_WORKSPACE_LIFECYCLE_EVENTS } from '../../../ipc/protocol'
|
|
6
6
|
import { createPrefixedId } from '../../../platform/id'
|
|
7
7
|
import {
|
|
8
8
|
assertSafeAimuxWorktreePath,
|
|
@@ -11,59 +11,59 @@ import {
|
|
|
11
11
|
pruneEmptyWorktreeParent,
|
|
12
12
|
} from '../../../platform/worktree-paths'
|
|
13
13
|
|
|
14
|
-
export interface
|
|
14
|
+
export interface CreateWorkspaceParams {
|
|
15
15
|
/** Base ref for the branch (callers default to 'HEAD'). */
|
|
16
16
|
base: string
|
|
17
17
|
/** Branch name (callers default to `aimux/<name>`). */
|
|
18
18
|
branch: string
|
|
19
19
|
daemon: DaemonClient
|
|
20
20
|
name: string
|
|
21
|
-
|
|
21
|
+
project: ProjectRecord
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
|
-
* Create a git worktree + its catalog record for a
|
|
26
|
-
* `
|
|
27
|
-
* capability BEFORE touching disk, and rolls back the on-disk
|
|
25
|
+
* Create a git worktree + its catalog record for a project. Shared by
|
|
26
|
+
* `workspace create` and `tab create --new-workspace`. Checks the daemon
|
|
27
|
+
* capability BEFORE touching disk, and rolls back the on-disk workspace if
|
|
28
28
|
* catalog registration fails (so `worktree list` never surfaces an orphan).
|
|
29
29
|
* Throws on any failure; returns the registered record on success.
|
|
30
30
|
*/
|
|
31
|
-
export async function
|
|
32
|
-
params:
|
|
33
|
-
): Promise<
|
|
34
|
-
const { base, branch, daemon, name,
|
|
31
|
+
export async function createProjectWorkspace(
|
|
32
|
+
params: CreateWorkspaceParams
|
|
33
|
+
): Promise<WorkspaceRecord> {
|
|
34
|
+
const { base, branch, daemon, name, project } = params
|
|
35
35
|
|
|
36
|
-
const primary =
|
|
36
|
+
const primary = project.workspaces?.find((w) => w.source === 'primary')
|
|
37
37
|
if (!primary) {
|
|
38
38
|
throw new Error(
|
|
39
|
-
`
|
|
39
|
+
`project "${project.name}" has no primary workspace — set --project when creating it`
|
|
40
40
|
)
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
// Check the daemon's capability BEFORE mutating disk — otherwise a capability
|
|
44
44
|
// mismatch would leave a git worktree on disk with no catalog record.
|
|
45
|
-
if (!daemon.hasCapability(
|
|
45
|
+
if (!daemon.hasCapability(IPC_CAPABILITY_WORKSPACE_LIFECYCLE_EVENTS)) {
|
|
46
46
|
throw new Error(
|
|
47
|
-
'daemon predates
|
|
47
|
+
'daemon predates workspaceLifecycleEvents capability — restart aimux to pick up the new daemon'
|
|
48
48
|
)
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
// Verify the base ref in the REPO WE ARE ABOUT TO USE, before touching disk.
|
|
52
|
-
// The
|
|
53
|
-
// project A while the resolved
|
|
52
|
+
// The project decides the repo, so a caller who believes it is orchestrating
|
|
53
|
+
// project A while the resolved project points at project B would otherwise
|
|
54
54
|
// get either a confusing bare git error or — when the ref exists in both repos
|
|
55
55
|
// — a silent success in the wrong project.
|
|
56
56
|
if ((await resolveGitRef(primary.repoRoot, base)) === undefined) {
|
|
57
57
|
throw new Error(
|
|
58
|
-
`base ref "${base}" does not exist in ${primary.repoRoot} (
|
|
58
|
+
`base ref "${base}" does not exist in ${primary.repoRoot} (project "${project.name}") — check that this is the repository you meant`
|
|
59
59
|
)
|
|
60
60
|
}
|
|
61
61
|
|
|
62
|
-
const
|
|
62
|
+
const workspaceId = createPrefixedId('workspace')
|
|
63
63
|
const targetPath = makeWorktreePath({
|
|
64
64
|
repoRoot: primary.repoRoot,
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
workspaceId,
|
|
66
|
+
workspaceName: name,
|
|
67
67
|
})
|
|
68
68
|
await ensureAimuxWorktreeRoot()
|
|
69
69
|
await assertSafeAimuxWorktreePath(targetPath)
|
|
@@ -83,12 +83,12 @@ export async function createWorkspaceWorktree(
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
const now = new Date().toISOString()
|
|
86
|
-
const record:
|
|
86
|
+
const record: WorkspaceRecord = {
|
|
87
87
|
baseRef: base,
|
|
88
88
|
branch,
|
|
89
89
|
createdAt: now,
|
|
90
90
|
createdByAimux: true,
|
|
91
|
-
id:
|
|
91
|
+
id: workspaceId,
|
|
92
92
|
name,
|
|
93
93
|
path: targetPath,
|
|
94
94
|
repoRoot: primary.repoRoot,
|
|
@@ -97,16 +97,16 @@ export async function createWorkspaceWorktree(
|
|
|
97
97
|
}
|
|
98
98
|
|
|
99
99
|
try {
|
|
100
|
-
await daemon.expectOk('
|
|
100
|
+
await daemon.expectOk('addWorkspaceRecord', { projectId: project.id, workspace: record })
|
|
101
101
|
} catch (error) {
|
|
102
|
-
// Catalog registration failed — roll back the on-disk
|
|
102
|
+
// Catalog registration failed — roll back the on-disk workspace so
|
|
103
103
|
// `worktree list` doesn't perpetually surface an orphan. Swallow rollback
|
|
104
104
|
// errors: report the original failure, the real problem to surface.
|
|
105
105
|
try {
|
|
106
106
|
await removeGitWorktree({ force: true, repoPath: primary.repoRoot, targetPath })
|
|
107
107
|
await pruneEmptyWorktreeParent(targetPath)
|
|
108
108
|
} catch {
|
|
109
|
-
// Best-effort rollback; leave the git-side
|
|
109
|
+
// Best-effort rollback; leave the git-side workspace if it can't be removed
|
|
110
110
|
// cleanly. `worktree list` will flag it as gitTracked with no catalog.
|
|
111
111
|
}
|
|
112
112
|
throw error
|
|
@@ -1,93 +1,55 @@
|
|
|
1
|
-
import { resolve as resolvePath } from 'node:path'
|
|
2
|
-
|
|
3
1
|
import type { CliCommand } from '../../registry'
|
|
4
2
|
|
|
5
|
-
import { IPC_CAPABILITY_WORKSPACE_LIFECYCLE } from '../../../ipc/protocol'
|
|
6
3
|
import { SHARED_FLAGS } from '../../flags'
|
|
7
|
-
import { EXIT_OK,
|
|
8
|
-
|
|
9
|
-
const DEFAULT_WAIT_TIMEOUT_MS = 30_000
|
|
4
|
+
import { EXIT_OK, writeJson } from '../../output'
|
|
5
|
+
import { createProjectWorkspace } from './create-core'
|
|
10
6
|
|
|
11
7
|
export const workspaceCreate: CliCommand = {
|
|
12
|
-
args: [
|
|
8
|
+
args: [],
|
|
13
9
|
flags: [
|
|
14
10
|
...SHARED_FLAGS,
|
|
15
11
|
{
|
|
16
|
-
complete: { kind: '
|
|
17
|
-
description: '
|
|
12
|
+
complete: { kind: 'none' },
|
|
13
|
+
description: 'display name for the new workspace',
|
|
18
14
|
kind: 'string',
|
|
19
|
-
name: '
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
description: 'immediately switch the running UI to the new workspace',
|
|
23
|
-
kind: 'boolean',
|
|
24
|
-
name: 'switch',
|
|
15
|
+
name: 'name',
|
|
25
16
|
},
|
|
26
17
|
{
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
18
|
+
complete: { kind: 'none' },
|
|
19
|
+
description: 'branch name (defaults to aimux/<name>)',
|
|
20
|
+
kind: 'string',
|
|
21
|
+
name: 'branch',
|
|
30
22
|
},
|
|
31
23
|
{
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
24
|
+
complete: { kind: 'dynamic', source: 'git-ref' },
|
|
25
|
+
description: 'base ref for the branch (defaults to HEAD)',
|
|
26
|
+
kind: 'string',
|
|
27
|
+
name: 'base',
|
|
35
28
|
},
|
|
36
29
|
],
|
|
37
30
|
group: 'workspace',
|
|
38
31
|
run: async (ctx) => {
|
|
39
|
-
const name = ctx.args.
|
|
32
|
+
const name = ctx.args.flags.name
|
|
40
33
|
if (typeof name !== 'string' || name.length === 0) {
|
|
41
|
-
throw new Error('
|
|
42
|
-
}
|
|
43
|
-
const projectRaw =
|
|
44
|
-
typeof ctx.args.flags.project === 'string' ? ctx.args.flags.project : undefined
|
|
45
|
-
const projectPath = projectRaw === undefined ? undefined : resolvePath(projectRaw)
|
|
46
|
-
const doSwitch = ctx.args.flags.switch === true
|
|
47
|
-
const wait = ctx.args.flags.wait === true
|
|
48
|
-
const timeoutMs =
|
|
49
|
-
typeof ctx.args.flags.timeout === 'number' ? ctx.args.flags.timeout : DEFAULT_WAIT_TIMEOUT_MS
|
|
50
|
-
|
|
51
|
-
if (wait && !doSwitch) {
|
|
52
|
-
// Without --switch, the UI never emits an ack event, so there's
|
|
53
|
-
// nothing meaningful for --wait to wait on.
|
|
54
|
-
throw new Error('--wait requires --switch (nothing to wait for otherwise)')
|
|
34
|
+
throw new Error('--name is required')
|
|
55
35
|
}
|
|
36
|
+
const branch =
|
|
37
|
+
typeof ctx.args.flags.branch === 'string' ? ctx.args.flags.branch : `aimux/${name}`
|
|
38
|
+
const base = typeof ctx.args.flags.base === 'string' ? ctx.args.flags.base : 'HEAD'
|
|
56
39
|
|
|
40
|
+
const project = ctx.getProject()
|
|
57
41
|
const daemon = await ctx.getDaemon()
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
writeJson({ name, projectPath, switch: doSwitch })
|
|
67
|
-
return EXIT_OK
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Subscribe BEFORE sending the request. The UI's create+switch path
|
|
71
|
-
// relays as `workspaceSwitched` once handleCreateSessionEffect has
|
|
72
|
-
// dispatched load-session. Match on name+projectPath since the CLI
|
|
73
|
-
// doesn't know the id the UI will assign to the new session.
|
|
74
|
-
const settled = new Promise<number>((resolve) => {
|
|
75
|
-
const off = daemon.on('workspaceSwitched', (payload) => {
|
|
76
|
-
off()
|
|
77
|
-
clearTimeout(timer)
|
|
78
|
-
writeJson({ name, projectPath, sessionId: payload.sessionId, switch: doSwitch })
|
|
79
|
-
resolve(EXIT_OK)
|
|
80
|
-
})
|
|
81
|
-
const timer = setTimeout(() => {
|
|
82
|
-
off()
|
|
83
|
-
writeJson({ error: 'timed out waiting for workspaceSwitched', name, projectPath })
|
|
84
|
-
resolve(EXIT_TIMEOUT)
|
|
85
|
-
}, timeoutMs)
|
|
42
|
+
const record = await createProjectWorkspace({ base, branch, daemon, name, project })
|
|
43
|
+
|
|
44
|
+
writeJson({
|
|
45
|
+
branch: record.branch,
|
|
46
|
+
id: record.id,
|
|
47
|
+
name: record.name,
|
|
48
|
+
path: record.path,
|
|
49
|
+
repoRoot: record.repoRoot,
|
|
86
50
|
})
|
|
87
|
-
|
|
88
|
-
await daemon.expectOk('createWorkspace', { name, projectPath, switch: doSwitch })
|
|
89
|
-
return settled
|
|
51
|
+
return EXIT_OK
|
|
90
52
|
},
|
|
91
|
-
summary: 'Create a new workspace
|
|
53
|
+
summary: 'Create a new workspace in the active project',
|
|
92
54
|
verb: 'create',
|
|
93
55
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { CliCommand } from '../../registry'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { listGitWorktrees } from '../../../git/worktree'
|
|
4
|
+
import { IPC_CAPABILITY_LIST_TABS } from '../../../ipc/protocol'
|
|
4
5
|
import { SHARED_FLAGS } from '../../flags'
|
|
5
6
|
import { EXIT_OK, writeJson } from '../../output'
|
|
6
7
|
|
|
@@ -8,18 +9,60 @@ export const workspaceList: CliCommand = {
|
|
|
8
9
|
args: [],
|
|
9
10
|
flags: SHARED_FLAGS,
|
|
10
11
|
group: 'workspace',
|
|
11
|
-
run: async () => {
|
|
12
|
-
const
|
|
12
|
+
run: async (ctx) => {
|
|
13
|
+
const project = ctx.getProject()
|
|
14
|
+
const records = project.workspaces ?? []
|
|
15
|
+
|
|
16
|
+
// Count live tabs per workspace so an orchestrator can see co-location
|
|
17
|
+
// (several workers sharing one workspace) at a glance. Best-effort: `null`
|
|
18
|
+
// when the daemon is unreachable or predates the listTabs capability —
|
|
19
|
+
// `worktree list` otherwise reads purely from the catalog + git, so we don't
|
|
20
|
+
// want a down daemon to fail it.
|
|
21
|
+
const tabCounts = new Map<string, number>()
|
|
22
|
+
let tabCountsAvailable = false
|
|
23
|
+
try {
|
|
24
|
+
const daemon = await ctx.getDaemon()
|
|
25
|
+
if (daemon.hasCapability(IPC_CAPABILITY_LIST_TABS)) {
|
|
26
|
+
const { tabs } = await daemon.listTabs(project.id)
|
|
27
|
+
for (const tab of tabs) {
|
|
28
|
+
if (tab.workspaceId != null) {
|
|
29
|
+
tabCounts.set(tab.workspaceId, (tabCounts.get(tab.workspaceId) ?? 0) + 1)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
tabCountsAvailable = true
|
|
33
|
+
}
|
|
34
|
+
} catch {
|
|
35
|
+
// daemon unreachable — leave tabCount null rather than failing the list.
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Cross-check against git so we can flag catalog entries that git no
|
|
39
|
+
// longer knows about (prunable / vanished) — otherwise the CLI would
|
|
40
|
+
// happily report workspaces that don't exist on disk.
|
|
41
|
+
const primary = records.find((w) => w.source === 'primary')
|
|
42
|
+
const gitPaths = new Set<string>()
|
|
43
|
+
if (primary) {
|
|
44
|
+
for (const w of await listGitWorktrees(primary.repoRoot)) {
|
|
45
|
+
if (w.prunable !== true) gitPaths.add(w.path)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
13
49
|
writeJson({
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
50
|
+
activeWorkspaceId: project.activeWorkspaceId ?? null,
|
|
51
|
+
projectId: project.id,
|
|
52
|
+
workspaces: records.map((w) => ({
|
|
53
|
+
branch: w.branch,
|
|
54
|
+
createdByAimux: w.createdByAimux,
|
|
55
|
+
gitTracked: primary ? gitPaths.has(w.path) : null,
|
|
56
|
+
id: w.id,
|
|
57
|
+
name: w.name,
|
|
58
|
+
path: w.path,
|
|
59
|
+
repoRoot: w.repoRoot,
|
|
60
|
+
source: w.source,
|
|
61
|
+
tabCount: tabCountsAvailable ? (tabCounts.get(w.id) ?? 0) : null,
|
|
19
62
|
})),
|
|
20
63
|
})
|
|
21
64
|
return EXIT_OK
|
|
22
65
|
},
|
|
23
|
-
summary: 'List
|
|
66
|
+
summary: 'List workspaces for a project',
|
|
24
67
|
verb: 'list',
|
|
25
68
|
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import type { CliCommand } from '../../registry'
|
|
2
|
+
|
|
3
|
+
import { removeGitWorktree } from '../../../git/worktree'
|
|
4
|
+
import {
|
|
5
|
+
IPC_CAPABILITY_LIST_TABS,
|
|
6
|
+
IPC_CAPABILITY_WORKSPACE_LIFECYCLE_EVENTS,
|
|
7
|
+
} from '../../../ipc/protocol'
|
|
8
|
+
import { pruneEmptyWorktreeParent } from '../../../platform/worktree-paths'
|
|
9
|
+
import { SHARED_FLAGS } from '../../flags'
|
|
10
|
+
import { EXIT_OK, writeJson } from '../../output'
|
|
11
|
+
|
|
12
|
+
export const workspaceRemove: CliCommand = {
|
|
13
|
+
args: [{ complete: { kind: 'dynamic', source: 'workspace' }, name: 'workspace', required: true }],
|
|
14
|
+
flags: [
|
|
15
|
+
...SHARED_FLAGS,
|
|
16
|
+
{ description: 'pass --force to git worktree remove', kind: 'boolean', name: 'force' },
|
|
17
|
+
],
|
|
18
|
+
group: 'workspace',
|
|
19
|
+
run: async (ctx) => {
|
|
20
|
+
const target = ctx.args.positionals[0]
|
|
21
|
+
if (typeof target !== 'string' || target.length === 0) {
|
|
22
|
+
throw new Error('workspace id or path is required')
|
|
23
|
+
}
|
|
24
|
+
const force = ctx.args.flags.force === true
|
|
25
|
+
|
|
26
|
+
const project = ctx.getProject()
|
|
27
|
+
const workspace =
|
|
28
|
+
project.workspaces?.find((w) => w.id === target) ??
|
|
29
|
+
project.workspaces?.find((w) => w.path === target)
|
|
30
|
+
if (!workspace) {
|
|
31
|
+
const known = project.workspaces?.map((w) => w.id).join(', ') ?? '(none)'
|
|
32
|
+
throw new Error(`unknown workspace: ${target} (known: ${known})`)
|
|
33
|
+
}
|
|
34
|
+
if (workspace.source === 'primary') {
|
|
35
|
+
throw new Error('refusing to remove the primary workspace')
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const primary = project.workspaces?.find((w) => w.source === 'primary')
|
|
39
|
+
if (!primary) {
|
|
40
|
+
throw new Error('project has no primary workspace — cannot resolve repoRoot for git remove')
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const daemon = await ctx.getDaemon()
|
|
44
|
+
if (!daemon.hasCapability(IPC_CAPABILITY_WORKSPACE_LIFECYCLE_EVENTS)) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
'daemon predates workspaceLifecycleEvents capability — restart aimux to pick up the new daemon'
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
if (daemon.hasCapability(IPC_CAPABILITY_LIST_TABS)) {
|
|
50
|
+
const live = (await daemon.listTabs(project.id)).tabs.filter(
|
|
51
|
+
(tab) => tab.workspaceId === workspace.id
|
|
52
|
+
)
|
|
53
|
+
if (live.length > 0) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
`refusing to remove workspace with live tabs: ${live.map((tab) => tab.id).join(', ')}`
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// All capability and liveness checks happen before touching git. If git
|
|
61
|
+
// refuses a dirty workspace the catalog remains unchanged.
|
|
62
|
+
await removeGitWorktree({ force, repoPath: primary.repoRoot, targetPath: workspace.path })
|
|
63
|
+
await pruneEmptyWorktreeParent(workspace.path)
|
|
64
|
+
try {
|
|
65
|
+
await daemon.expectOk('removeWorkspaceRecord', {
|
|
66
|
+
projectId: project.id,
|
|
67
|
+
workspaceId: workspace.id,
|
|
68
|
+
})
|
|
69
|
+
} catch (error) {
|
|
70
|
+
const message = error instanceof Error ? error.message : String(error)
|
|
71
|
+
throw new Error(
|
|
72
|
+
`worktree removed from git but catalog reconciliation failed for ${workspace.id}: ${message}`
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
writeJson({ id: workspace.id, name: workspace.name, path: workspace.path })
|
|
77
|
+
return EXIT_OK
|
|
78
|
+
},
|
|
79
|
+
summary: 'Remove a workspace from the active project',
|
|
80
|
+
verb: 'remove',
|
|
81
|
+
}
|
|
@@ -51,10 +51,10 @@ const COMPLETION_SUBCOMMANDS: readonly CompletionCandidate[] = [
|
|
|
51
51
|
]
|
|
52
52
|
|
|
53
53
|
const GROUP_DESCRIPTIONS: Record<string, string> = {
|
|
54
|
+
project: 'Projects (projects) in the profile catalog',
|
|
54
55
|
tab: 'Drive individual tabs (create, send, snapshot, await)',
|
|
55
56
|
worker: 'Named agent workers — the preferred orchestration surface',
|
|
56
|
-
workspace: '
|
|
57
|
-
worktree: 'Git worktrees attached to the active workspace',
|
|
57
|
+
workspace: 'Git workspaces attached to the active project',
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
function groupCandidates(): CompletionCandidate[] {
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
* shell must never see a stack trace where a completion list belongs.
|
|
9
9
|
*
|
|
10
10
|
* Phase 1 implements only the sources that read local state (built-in
|
|
11
|
-
* assistants, the
|
|
12
|
-
*
|
|
11
|
+
* assistants, the project catalog). Daemon-backed sources — tabs, workers,
|
|
12
|
+
* workspaces — and git refs return nothing until phase 2 wires them up.
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import type { DynamicCompletionSource } from '../flags'
|
|
@@ -24,20 +24,20 @@ function assistantCandidates(): CompletionCandidate[] {
|
|
|
24
24
|
}))
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
async function
|
|
28
|
-
// Imported lazily: the
|
|
27
|
+
async function projectCandidates(): Promise<CompletionCandidate[]> {
|
|
28
|
+
// Imported lazily: the project catalog pulls in config + state modules that
|
|
29
29
|
// the static-source paths (groups, verbs, flags) have no reason to load.
|
|
30
|
-
const {
|
|
31
|
-
const
|
|
30
|
+
const { listProjects } = await import('../client/project-resolver')
|
|
31
|
+
const projects = listProjects()
|
|
32
32
|
const nameCounts = new Map<string, number>()
|
|
33
|
-
for (const
|
|
34
|
-
nameCounts.set(
|
|
33
|
+
for (const project of projects) {
|
|
34
|
+
nameCounts.set(project.name, (nameCounts.get(project.name) ?? 0) + 1)
|
|
35
35
|
}
|
|
36
|
-
return
|
|
37
|
-
// Ambiguous names can't be resolved by `--
|
|
38
|
-
(nameCounts.get(
|
|
39
|
-
? { description:
|
|
40
|
-
: { description:
|
|
36
|
+
return projects.map((project) =>
|
|
37
|
+
// Ambiguous names can't be resolved by `--project`, so offer the id.
|
|
38
|
+
(nameCounts.get(project.name) ?? 0) > 1
|
|
39
|
+
? { description: project.name, value: project.id }
|
|
40
|
+
: { description: project.id, value: project.name }
|
|
41
41
|
)
|
|
42
42
|
}
|
|
43
43
|
|
|
@@ -45,10 +45,10 @@ async function resolveSource(source: DynamicCompletionSource): Promise<Completio
|
|
|
45
45
|
switch (source) {
|
|
46
46
|
case 'assistant':
|
|
47
47
|
return assistantCandidates()
|
|
48
|
-
case '
|
|
49
|
-
return await
|
|
50
|
-
// Phase 2: 'tab' | 'worker' | '
|
|
51
|
-
// deadline; 'git-ref' needs a `git for-each-ref` in the
|
|
48
|
+
case 'project':
|
|
49
|
+
return await projectCandidates()
|
|
50
|
+
// Phase 2: 'tab' | 'worker' | 'workspace' need a daemon round-trip under a
|
|
51
|
+
// deadline; 'git-ref' needs a `git for-each-ref` in the project repo.
|
|
52
52
|
default:
|
|
53
53
|
return []
|
|
54
54
|
}
|
|
@@ -56,7 +56,7 @@ async function resolveSource(source: DynamicCompletionSource): Promise<Completio
|
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
58
|
* Resolve a dynamic source, filter by the partial word, and re-apply the
|
|
59
|
-
* prefix the planner stripped (e.g. `--
|
|
59
|
+
* prefix the planner stripped (e.g. `--project=`). Always resolves.
|
|
60
60
|
*/
|
|
61
61
|
export async function resolveDynamicCandidates(
|
|
62
62
|
source: DynamicCompletionSource,
|