@brimveyn/aimux 1.19.7 → 1.20.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 +14 -0
- package/package.json +3 -2
- package/skills/aimux-orchestrator/SKILL.md +127 -0
- package/skills/aimux-orchestrator/assets/ledger.template.md +18 -0
- package/skills/aimux-orchestrator/references/prompts.md +68 -0
- package/skills/aimux-orchestrator/references/review.md +18 -0
- package/src/cli/client/daemon-client.ts +16 -0
- package/src/cli/client/workspace-resolver.ts +61 -8
- package/src/cli/commands/tab/await.ts +1 -1
- package/src/cli/commands/tab/close.ts +1 -1
- package/src/cli/commands/tab/create.ts +263 -128
- package/src/cli/commands/tab/focus.ts +1 -1
- package/src/cli/commands/tab/prompt-io.ts +8 -2
- package/src/cli/commands/tab/run.ts +10 -2
- package/src/cli/commands/tab/send.ts +12 -7
- package/src/cli/commands/tab/snapshot.ts +2 -1
- package/src/cli/commands/tab/tail.ts +1 -1
- package/src/cli/commands/tab/wait.ts +2 -1
- package/src/cli/commands/worker/await.ts +34 -0
- package/src/cli/commands/worker/doctor.ts +153 -0
- package/src/cli/commands/worker/list.ts +67 -0
- package/src/cli/commands/worker/prompt.ts +74 -0
- package/src/cli/commands/worker/run.ts +143 -0
- package/src/cli/commands/worker/shared.ts +515 -0
- package/src/cli/commands/worker/stop.ts +113 -0
- package/src/cli/commands/worker/submit.ts +40 -0
- package/src/cli/commands/workspace/close.ts +1 -1
- package/src/cli/commands/workspace/create.ts +2 -1
- package/src/cli/commands/workspace/switch.ts +1 -1
- package/src/cli/commands/worktree/create-core.ts +27 -7
- package/src/cli/commands/worktree/create.ts +18 -3
- package/src/cli/commands/worktree/remove.ts +32 -10
- package/src/cli/completion/entry.ts +181 -0
- package/src/cli/completion/install.ts +222 -0
- package/src/cli/completion/plan.ts +216 -0
- package/src/cli/completion/scripts.ts +147 -0
- package/src/cli/completion/sources.ts +74 -0
- package/src/cli/context.ts +15 -0
- package/src/cli/flags.ts +45 -2
- package/src/cli/index.ts +40 -18
- package/src/cli/output.ts +3 -0
- package/src/cli/registry.ts +14 -0
- package/src/daemon/daemon.ts +58 -8
- package/src/daemon/session-registry.ts +5 -0
- package/src/doctor.ts +4 -0
- package/src/git/worktree.ts +23 -1
- package/src/index.tsx +53 -92
- package/src/ipc/manager-protocol.ts +15 -2
- package/src/ipc/protocol.ts +26 -3
- package/src/platform/worktree-paths.ts +21 -1
- package/src/state/session-persistence.ts +2 -0
- package/src/state/types.ts +4 -0
- package/src/state/validation.ts +1 -0
- package/src/terminal-manager/manager-client.ts +18 -3
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { fileURLToPath } from 'node:url'
|
|
3
|
+
|
|
4
|
+
import type { CliCommand } from '../../registry'
|
|
5
|
+
|
|
6
|
+
import { loadConfig } from '../../../config'
|
|
7
|
+
import { MANAGER_CAPABILITY_WORKER_METADATA } from '../../../ipc/manager-protocol'
|
|
8
|
+
import {
|
|
9
|
+
IPC_CAPABILITY_LIST_TABS,
|
|
10
|
+
IPC_CAPABILITY_QUESTION_EVENTS,
|
|
11
|
+
IPC_CAPABILITY_THIN_ATTACH,
|
|
12
|
+
IPC_CAPABILITY_TURN_LIFECYCLE,
|
|
13
|
+
IPC_CAPABILITY_WORKER_METADATA,
|
|
14
|
+
IPC_CAPABILITY_WORKTREE_LIFECYCLE_EVENTS,
|
|
15
|
+
} from '../../../ipc/protocol'
|
|
16
|
+
import {
|
|
17
|
+
getAllAssistantOptions,
|
|
18
|
+
isCommandAvailable,
|
|
19
|
+
parseCommand,
|
|
20
|
+
} from '../../../pty/command-registry'
|
|
21
|
+
import {
|
|
22
|
+
findPrimaryWorktree,
|
|
23
|
+
WORKSPACE_ENV_VAR,
|
|
24
|
+
workspaceRepoRoot,
|
|
25
|
+
} from '../../client/workspace-resolver'
|
|
26
|
+
import { SHARED_FLAGS } from '../../flags'
|
|
27
|
+
import { EXIT_OK, EXIT_RUNTIME, writeJson } from '../../output'
|
|
28
|
+
import { WORKER_SCHEMA_VERSION } from './shared'
|
|
29
|
+
|
|
30
|
+
const REQUIRED_DAEMON_CAPABILITIES = [
|
|
31
|
+
IPC_CAPABILITY_LIST_TABS,
|
|
32
|
+
IPC_CAPABILITY_QUESTION_EVENTS,
|
|
33
|
+
IPC_CAPABILITY_THIN_ATTACH,
|
|
34
|
+
IPC_CAPABILITY_TURN_LIFECYCLE,
|
|
35
|
+
IPC_CAPABILITY_WORKER_METADATA,
|
|
36
|
+
IPC_CAPABILITY_WORKTREE_LIFECYCLE_EVENTS,
|
|
37
|
+
] as const
|
|
38
|
+
|
|
39
|
+
export const workerDoctor: CliCommand = {
|
|
40
|
+
args: [],
|
|
41
|
+
flags: SHARED_FLAGS,
|
|
42
|
+
group: 'worker',
|
|
43
|
+
run: async (ctx) => {
|
|
44
|
+
const daemon = await ctx.getDaemon()
|
|
45
|
+
const workspace = ctx.getWorkspace()
|
|
46
|
+
const { version } = await import('../../../../package.json')
|
|
47
|
+
const { customCommands } = loadConfig()
|
|
48
|
+
const assistants = getAllAssistantOptions(customCommands).map((assistant) => ({
|
|
49
|
+
available: isCommandAvailable(
|
|
50
|
+
parseCommand(customCommands[assistant.id] ?? assistant.command).executable
|
|
51
|
+
),
|
|
52
|
+
id: assistant.id,
|
|
53
|
+
supportsEffort: assistant.model?.buildEffortArgs !== undefined,
|
|
54
|
+
supportsModel: assistant.model?.buildModelArgs !== undefined,
|
|
55
|
+
}))
|
|
56
|
+
const daemonCapabilities = daemon.getCapabilities()
|
|
57
|
+
const managerCapabilities = daemon.getManagerCapabilities()
|
|
58
|
+
const missingDaemonCapabilities = REQUIRED_DAEMON_CAPABILITIES.filter(
|
|
59
|
+
(capability) => !daemonCapabilities.includes(capability)
|
|
60
|
+
)
|
|
61
|
+
const missingManagerCapabilities = [MANAGER_CAPABILITY_WORKER_METADATA].filter(
|
|
62
|
+
(capability) => !managerCapabilities.includes(capability)
|
|
63
|
+
)
|
|
64
|
+
const primaryWorktree = findPrimaryWorktree(workspace)
|
|
65
|
+
const workspaceOrigin = ctx.getWorkspaceOrigin?.() ?? 'active'
|
|
66
|
+
const availableAssistants = assistants.filter((assistant) => assistant.available)
|
|
67
|
+
const skillPath = fileURLToPath(
|
|
68
|
+
new URL('../../../../skills/aimux-orchestrator/', import.meta.url)
|
|
69
|
+
)
|
|
70
|
+
const issues: string[] = []
|
|
71
|
+
if (missingDaemonCapabilities.length > 0) {
|
|
72
|
+
issues.push(
|
|
73
|
+
`restart or update aimux; daemon is missing: ${missingDaemonCapabilities.join(', ')}`
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
if (missingManagerCapabilities.length > 0) {
|
|
77
|
+
issues.push(
|
|
78
|
+
`restart the terminal manager; it is missing: ${missingManagerCapabilities.join(', ')}`
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
if (primaryWorktree === undefined) {
|
|
82
|
+
issues.push('workspace has no primary worktree; default isolated worker runs are unavailable')
|
|
83
|
+
}
|
|
84
|
+
if (availableAssistants.length === 0) {
|
|
85
|
+
issues.push('no configured assistant executable is available on PATH')
|
|
86
|
+
}
|
|
87
|
+
if (!existsSync(skillPath)) {
|
|
88
|
+
issues.push(`packaged orchestrator skill is missing: ${skillPath}`)
|
|
89
|
+
}
|
|
90
|
+
// Not an issue — an inferred workspace is the normal interactive case — but
|
|
91
|
+
// it IS the one resolution mode that can follow the UI to another project
|
|
92
|
+
// between two calls. An orchestrator dispatching a multi-hour fleet wants to
|
|
93
|
+
// see this before it starts, not after it reviews diffs from the wrong repo.
|
|
94
|
+
const warnings: string[] = []
|
|
95
|
+
if (workspaceOrigin === 'active') {
|
|
96
|
+
warnings.push(
|
|
97
|
+
`workspace "${workspace.name}" was inferred from the most recently opened session and follows the UI; pin it with --workspace or ${WORKSPACE_ENV_VAR}`
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
const ready = issues.length === 0
|
|
101
|
+
writeJson({
|
|
102
|
+
assistants,
|
|
103
|
+
checks: {
|
|
104
|
+
assistants: {
|
|
105
|
+
available: availableAssistants.map((assistant) => assistant.id),
|
|
106
|
+
ok: availableAssistants.length > 0,
|
|
107
|
+
},
|
|
108
|
+
daemonCapabilities: {
|
|
109
|
+
missing: missingDaemonCapabilities,
|
|
110
|
+
ok: missingDaemonCapabilities.length === 0,
|
|
111
|
+
},
|
|
112
|
+
managerCapabilities: {
|
|
113
|
+
missing: missingManagerCapabilities,
|
|
114
|
+
ok: missingManagerCapabilities.length === 0,
|
|
115
|
+
},
|
|
116
|
+
skill: { ok: existsSync(skillPath), path: skillPath },
|
|
117
|
+
workspace: {
|
|
118
|
+
hasPrimaryWorktree: primaryWorktree !== undefined,
|
|
119
|
+
name: workspace.name,
|
|
120
|
+
ok: primaryWorktree !== undefined,
|
|
121
|
+
/** Repo every fresh worker worktree is cut from — confirm before dispatching. */
|
|
122
|
+
repoRoot: workspaceRepoRoot(workspace),
|
|
123
|
+
/** 'flag' | 'env' | 'active'; only 'active' can follow the UI. */
|
|
124
|
+
source: workspaceOrigin,
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
cliVersion: version,
|
|
128
|
+
daemon: {
|
|
129
|
+
appVersion: daemon.getAppVersion(),
|
|
130
|
+
capabilities: daemonCapabilities,
|
|
131
|
+
managerCapabilities,
|
|
132
|
+
managerProtocolVersion: daemon.getManagerSelectedVersion(),
|
|
133
|
+
processVersion: daemon.getProcessVersion(),
|
|
134
|
+
protocolVersion: daemon.getSelectedVersion(),
|
|
135
|
+
},
|
|
136
|
+
issues,
|
|
137
|
+
ready,
|
|
138
|
+
schemaVersion: WORKER_SCHEMA_VERSION,
|
|
139
|
+
skillPath,
|
|
140
|
+
warnings,
|
|
141
|
+
workspace: {
|
|
142
|
+
id: workspace.id,
|
|
143
|
+
name: workspace.name,
|
|
144
|
+
projectPath: workspace.projectPath ?? null,
|
|
145
|
+
repoRoot: workspaceRepoRoot(workspace),
|
|
146
|
+
source: workspaceOrigin,
|
|
147
|
+
},
|
|
148
|
+
})
|
|
149
|
+
return ready ? EXIT_OK : EXIT_RUNTIME
|
|
150
|
+
},
|
|
151
|
+
summary: 'Check worker prerequisites, versions, assistants, and workspace',
|
|
152
|
+
verb: 'doctor',
|
|
153
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { CliCommand } from '../../registry'
|
|
2
|
+
|
|
3
|
+
import { workspaceIdentity } from '../../client/workspace-resolver'
|
|
4
|
+
import { SHARED_FLAGS } from '../../flags'
|
|
5
|
+
import { EXIT_OK, writeJson } from '../../output'
|
|
6
|
+
import {
|
|
7
|
+
listNamedWorkerTabs,
|
|
8
|
+
listWorkerTargets,
|
|
9
|
+
resolveWorkerTarget,
|
|
10
|
+
WORKER_SCHEMA_VERSION,
|
|
11
|
+
workerView,
|
|
12
|
+
} from './shared'
|
|
13
|
+
|
|
14
|
+
export const workerList: CliCommand = {
|
|
15
|
+
args: [{ complete: { kind: 'dynamic', source: 'worker' }, name: 'worker' }],
|
|
16
|
+
flags: [
|
|
17
|
+
...SHARED_FLAGS,
|
|
18
|
+
{
|
|
19
|
+
description: 'list workers in every catalogued workspace, not just the target one',
|
|
20
|
+
kind: 'boolean',
|
|
21
|
+
name: 'all-workspaces',
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
group: 'worker',
|
|
25
|
+
run: async (ctx) => {
|
|
26
|
+
const selector = ctx.args.positionals[0]
|
|
27
|
+
|
|
28
|
+
// `{"workers":[]}` alone is indistinguishable from "every worker died", and
|
|
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"
|
|
31
|
+
// rather than "gone", and offer one call that answers "are they really gone?"
|
|
32
|
+
if (ctx.args.flags['all-workspaces'] === true) {
|
|
33
|
+
const targets = await listWorkerTargets(ctx)
|
|
34
|
+
writeJson({
|
|
35
|
+
schemaVersion: WORKER_SCHEMA_VERSION,
|
|
36
|
+
workers: targets
|
|
37
|
+
.filter((target) => selector === undefined || target.tab.workerName === selector)
|
|
38
|
+
.map((target) => ({
|
|
39
|
+
...workerView(target.workspace, target.tab),
|
|
40
|
+
workspace: workspaceIdentity(target.workspace),
|
|
41
|
+
})),
|
|
42
|
+
})
|
|
43
|
+
return EXIT_OK
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (selector !== undefined) {
|
|
47
|
+
const { tab, workspace } = await resolveWorkerTarget(ctx, selector)
|
|
48
|
+
writeJson({
|
|
49
|
+
schemaVersion: WORKER_SCHEMA_VERSION,
|
|
50
|
+
workers: [workerView(workspace, tab)],
|
|
51
|
+
workspace: workspaceIdentity(workspace),
|
|
52
|
+
})
|
|
53
|
+
return EXIT_OK
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const workspace = ctx.getWorkspace()
|
|
57
|
+
const tabs = await listNamedWorkerTabs(ctx, workspace)
|
|
58
|
+
writeJson({
|
|
59
|
+
schemaVersion: WORKER_SCHEMA_VERSION,
|
|
60
|
+
workers: tabs.map((tab) => workerView(workspace, tab)),
|
|
61
|
+
workspace: workspaceIdentity(workspace),
|
|
62
|
+
})
|
|
63
|
+
return EXIT_OK
|
|
64
|
+
},
|
|
65
|
+
summary: 'List named workers with liveness and worktree context',
|
|
66
|
+
verb: 'list',
|
|
67
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { CliCommand } from '../../registry'
|
|
2
|
+
|
|
3
|
+
import { SHARED_FLAGS } from '../../flags'
|
|
4
|
+
import { writeJson } from '../../output'
|
|
5
|
+
import { DEFAULT_TIMEOUT_MS } from '../tab/await-turn'
|
|
6
|
+
import { resolvePromptText } from '../tab/prompt-io'
|
|
7
|
+
import {
|
|
8
|
+
dispatchWorkerPrompt,
|
|
9
|
+
resolveWorkerTarget,
|
|
10
|
+
workerEnvelope,
|
|
11
|
+
workerOutcomeExitCode,
|
|
12
|
+
workerView,
|
|
13
|
+
} from './shared'
|
|
14
|
+
|
|
15
|
+
export const workerPrompt: CliCommand = {
|
|
16
|
+
args: [
|
|
17
|
+
{ complete: { kind: 'dynamic', source: 'worker' }, name: 'worker', required: true },
|
|
18
|
+
{ complete: { kind: 'none' }, name: 'text' },
|
|
19
|
+
],
|
|
20
|
+
flags: [
|
|
21
|
+
...SHARED_FLAGS,
|
|
22
|
+
{
|
|
23
|
+
complete: { kind: 'file' },
|
|
24
|
+
description: 'read the prompt from this file',
|
|
25
|
+
kind: 'string',
|
|
26
|
+
name: 'prompt-file',
|
|
27
|
+
},
|
|
28
|
+
{ description: 'read the prompt from stdin', kind: 'boolean', name: 'stdin' },
|
|
29
|
+
{
|
|
30
|
+
description: 'return after prompt uptake instead of turn completion',
|
|
31
|
+
kind: 'boolean',
|
|
32
|
+
name: 'detach',
|
|
33
|
+
},
|
|
34
|
+
{ description: 'overall turn cap in milliseconds', kind: 'number', name: 'timeout' },
|
|
35
|
+
{
|
|
36
|
+
description:
|
|
37
|
+
'milliseconds to wait for the detached submit→working confirmation (default 15000)',
|
|
38
|
+
kind: 'number',
|
|
39
|
+
name: 'uptake-timeout',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
description:
|
|
43
|
+
'clear the composer (<C-u>) before writing, so text typed by a human in the UI cannot be concatenated onto this prompt',
|
|
44
|
+
kind: 'boolean',
|
|
45
|
+
name: 'replace',
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
group: 'worker',
|
|
49
|
+
run: async (ctx) => {
|
|
50
|
+
const selector = ctx.args.positionals[0] ?? ''
|
|
51
|
+
const { tab, workspace } = await resolveWorkerTarget(ctx, selector)
|
|
52
|
+
const promptFile =
|
|
53
|
+
typeof ctx.args.flags['prompt-file'] === 'string' ? ctx.args.flags['prompt-file'] : undefined
|
|
54
|
+
const text = await resolvePromptText(
|
|
55
|
+
promptFile,
|
|
56
|
+
ctx.args.flags.stdin === true,
|
|
57
|
+
ctx.args.positionals[1]
|
|
58
|
+
)
|
|
59
|
+
const outcome = await dispatchWorkerPrompt(ctx, workspace, tab.id, text, {
|
|
60
|
+
detach: ctx.args.flags.detach === true,
|
|
61
|
+
replace: ctx.args.flags.replace === true,
|
|
62
|
+
timeoutMs:
|
|
63
|
+
typeof ctx.args.flags.timeout === 'number' ? ctx.args.flags.timeout : DEFAULT_TIMEOUT_MS,
|
|
64
|
+
uptakeTimeoutMs:
|
|
65
|
+
typeof ctx.args.flags['uptake-timeout'] === 'number'
|
|
66
|
+
? ctx.args.flags['uptake-timeout']
|
|
67
|
+
: undefined,
|
|
68
|
+
})
|
|
69
|
+
writeJson(workerEnvelope(workspace, workerView(workspace, tab), outcome))
|
|
70
|
+
return workerOutcomeExitCode(outcome)
|
|
71
|
+
},
|
|
72
|
+
summary: 'Prompt an existing named worker and await its outcome',
|
|
73
|
+
verb: 'prompt',
|
|
74
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import type { CliCommand } from '../../registry'
|
|
2
|
+
|
|
3
|
+
import { CliUsageError, SHARED_FLAGS } from '../../flags'
|
|
4
|
+
import { writeJson } from '../../output'
|
|
5
|
+
import { DEFAULT_TIMEOUT_MS } from '../tab/await-turn'
|
|
6
|
+
import { createCliTab } from '../tab/create'
|
|
7
|
+
import { resolvePromptText } from '../tab/prompt-io'
|
|
8
|
+
import {
|
|
9
|
+
dispatchWorkerPrompt,
|
|
10
|
+
validateWorkerName,
|
|
11
|
+
workerEnvelope,
|
|
12
|
+
workerOutcomeExitCode,
|
|
13
|
+
workerView,
|
|
14
|
+
} from './shared'
|
|
15
|
+
|
|
16
|
+
export const workerRun: CliCommand = {
|
|
17
|
+
args: [{ complete: { kind: 'none' }, name: 'text' }],
|
|
18
|
+
flags: [
|
|
19
|
+
...SHARED_FLAGS,
|
|
20
|
+
{
|
|
21
|
+
complete: { kind: 'none' },
|
|
22
|
+
description: 'unique workspace-scoped worker name',
|
|
23
|
+
kind: 'string',
|
|
24
|
+
name: 'name',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
complete: { kind: 'dynamic', source: 'assistant' },
|
|
28
|
+
description: 'assistant id (claude, codex, opencode, ...)',
|
|
29
|
+
kind: 'string',
|
|
30
|
+
name: 'assistant',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
complete: { kind: 'none' },
|
|
34
|
+
description: 'model passed through to the assistant',
|
|
35
|
+
kind: 'string',
|
|
36
|
+
name: 'model',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
complete: { kind: 'none' },
|
|
40
|
+
description: 'reasoning effort passed through to the assistant',
|
|
41
|
+
kind: 'string',
|
|
42
|
+
name: 'effort',
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
complete: { kind: 'file' },
|
|
46
|
+
description: 'read the prompt from this file',
|
|
47
|
+
kind: 'string',
|
|
48
|
+
name: 'prompt-file',
|
|
49
|
+
},
|
|
50
|
+
{ description: 'read the prompt from stdin', kind: 'boolean', name: 'stdin' },
|
|
51
|
+
{
|
|
52
|
+
description: 'return after prompt uptake instead of turn completion',
|
|
53
|
+
kind: 'boolean',
|
|
54
|
+
name: 'detach',
|
|
55
|
+
},
|
|
56
|
+
{ description: 'overall turn cap in milliseconds', kind: 'number', name: 'timeout' },
|
|
57
|
+
{
|
|
58
|
+
description:
|
|
59
|
+
'milliseconds to wait for the detached submit→working confirmation (default 15000)',
|
|
60
|
+
kind: 'number',
|
|
61
|
+
name: 'uptake-timeout',
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
complete: { kind: 'dynamic', source: 'worktree' },
|
|
65
|
+
description: 'co-locate in an existing worktree id',
|
|
66
|
+
kind: 'string',
|
|
67
|
+
name: 'worktree',
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
description: 'run in the workspace active worktree instead of creating one',
|
|
71
|
+
kind: 'boolean',
|
|
72
|
+
name: 'no-worktree',
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
complete: { kind: 'dynamic', source: 'git-ref' },
|
|
76
|
+
description: 'base ref for the fresh worktree (default HEAD)',
|
|
77
|
+
kind: 'string',
|
|
78
|
+
name: 'base',
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
complete: { kind: 'none' },
|
|
82
|
+
description: 'branch for the fresh worktree (default aimux/<name>)',
|
|
83
|
+
kind: 'string',
|
|
84
|
+
name: 'branch',
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
group: 'worker',
|
|
88
|
+
run: async (ctx) => {
|
|
89
|
+
const name = typeof ctx.args.flags.name === 'string' ? ctx.args.flags.name : ''
|
|
90
|
+
const assistant = typeof ctx.args.flags.assistant === 'string' ? ctx.args.flags.assistant : ''
|
|
91
|
+
validateWorkerName(name)
|
|
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')
|
|
98
|
+
}
|
|
99
|
+
const promptFile =
|
|
100
|
+
typeof ctx.args.flags['prompt-file'] === 'string' ? ctx.args.flags['prompt-file'] : undefined
|
|
101
|
+
const text = await resolvePromptText(
|
|
102
|
+
promptFile,
|
|
103
|
+
ctx.args.flags.stdin === true,
|
|
104
|
+
ctx.args.positionals[0]
|
|
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
|
|
109
|
+
// identity echoed back in the response instead of having to infer it.
|
|
110
|
+
const workspace = ctx.getWorkspace()
|
|
111
|
+
const result = await createCliTab(ctx, {
|
|
112
|
+
assistantId: assistant,
|
|
113
|
+
base: typeof ctx.args.flags.base === 'string' ? ctx.args.flags.base : undefined,
|
|
114
|
+
branch: typeof ctx.args.flags.branch === 'string' ? ctx.args.flags.branch : undefined,
|
|
115
|
+
effort: typeof ctx.args.flags.effort === 'string' ? ctx.args.flags.effort : undefined,
|
|
116
|
+
model: typeof ctx.args.flags.model === 'string' ? ctx.args.flags.model : undefined,
|
|
117
|
+
newWorktree: noWorktree || worktree !== undefined ? undefined : name,
|
|
118
|
+
title: name,
|
|
119
|
+
workerName: name,
|
|
120
|
+
worktreeId: worktree,
|
|
121
|
+
})
|
|
122
|
+
const tabs = await (await ctx.getDaemon()).listTabs(workspace.id)
|
|
123
|
+
const tab = tabs.tabs.find((entry) => entry.id === result.tabId)
|
|
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, {
|
|
127
|
+
// The tab was spawned microseconds ago: its assistant is still booting, so
|
|
128
|
+
// the prompt must not be written until the TUI is reading keystrokes.
|
|
129
|
+
awaitFirstPaint: true,
|
|
130
|
+
detach: ctx.args.flags.detach === true,
|
|
131
|
+
timeoutMs:
|
|
132
|
+
typeof ctx.args.flags.timeout === 'number' ? ctx.args.flags.timeout : DEFAULT_TIMEOUT_MS,
|
|
133
|
+
uptakeTimeoutMs:
|
|
134
|
+
typeof ctx.args.flags['uptake-timeout'] === 'number'
|
|
135
|
+
? ctx.args.flags['uptake-timeout']
|
|
136
|
+
: undefined,
|
|
137
|
+
})
|
|
138
|
+
writeJson(workerEnvelope(workspace, worker, outcome))
|
|
139
|
+
return workerOutcomeExitCode(outcome)
|
|
140
|
+
},
|
|
141
|
+
summary: 'Create a named worker, dispatch a prompt, and await its outcome',
|
|
142
|
+
verb: 'run',
|
|
143
|
+
}
|