mirror of
https://github.com/stablyai/orca.git
synced 2026-10-23 00:03:52 +00:00
@@ -60,6 +60,18 @@ export function formatCliError(error: unknown): string {
|
||||
) {
|
||||
return `${message}\nOrca is not running. Run 'orca open' first.`
|
||||
}
|
||||
if (error instanceof RuntimeRpcFailureError) {
|
||||
const data = error.response.error.data
|
||||
const nextSteps =
|
||||
data && typeof data === 'object' && Array.isArray((data as { nextSteps?: unknown }).nextSteps)
|
||||
? (data as { nextSteps: unknown[] }).nextSteps.filter(
|
||||
(step): step is string => typeof step === 'string'
|
||||
)
|
||||
: []
|
||||
if (nextSteps.length > 0) {
|
||||
return `${message}\n${nextSteps.map((step) => `Next step: ${step}`).join('\n')}`
|
||||
}
|
||||
}
|
||||
return message
|
||||
}
|
||||
|
||||
|
||||
@@ -207,12 +207,18 @@ export const ORCHESTRATION_HANDLERS: Record<string, CommandHandler> = {
|
||||
},
|
||||
|
||||
'orchestration task-create': async ({ flags, client, json }) => {
|
||||
const callerTerminalHandle =
|
||||
typeof process.env.ORCA_TERMINAL_HANDLE === 'string' &&
|
||||
process.env.ORCA_TERMINAL_HANDLE.length > 0
|
||||
? process.env.ORCA_TERMINAL_HANDLE
|
||||
: undefined
|
||||
const result = await client.call<{ task: { id: string; status: string } }>(
|
||||
'orchestration.taskCreate',
|
||||
{
|
||||
spec: getRequiredStringFlag(flags, 'spec'),
|
||||
deps: getOptionalStringFlag(flags, 'deps'),
|
||||
parent: getOptionalStringFlag(flags, 'parent')
|
||||
parent: getOptionalStringFlag(flags, 'parent'),
|
||||
callerTerminalHandle
|
||||
}
|
||||
)
|
||||
printResult(result, json, (r) => `Created ${r.task.id} [${r.task.status}]`)
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type {
|
||||
RuntimeWorktreeListResult,
|
||||
RuntimeWorktreePsResult,
|
||||
RuntimeWorktreeRecord
|
||||
RuntimeWorktreeRecord,
|
||||
RuntimeWorktreeCreateResult
|
||||
} from '../../shared/runtime-types'
|
||||
import type { CommandHandler } from '../dispatch'
|
||||
import { formatWorktreeList, formatWorktreePs, formatWorktreeShow, printResult } from '../format'
|
||||
@@ -24,6 +25,30 @@ function printHookWarning(result: HookWarningResult, json: boolean): void {
|
||||
}
|
||||
}
|
||||
|
||||
function printLineageSummary(result: RuntimeWorktreeCreateResult, json: boolean): void {
|
||||
if (json) {
|
||||
return
|
||||
}
|
||||
for (const warning of result.warnings ?? []) {
|
||||
console.error(`warning: ${warning.message}`)
|
||||
}
|
||||
if (result.lineage) {
|
||||
const source =
|
||||
result.lineage.capture.source === 'terminal-context'
|
||||
? 'terminal'
|
||||
: result.lineage.capture.source === 'orchestration-context'
|
||||
? 'orchestration'
|
||||
: result.lineage.capture.source === 'explicit-cli-flag'
|
||||
? 'explicit flag'
|
||||
: 'manual action'
|
||||
console.error(
|
||||
`parent: ${result.lineage.parentWorktreeId} (${result.lineage.capture.confidence} from ${source})`
|
||||
)
|
||||
} else {
|
||||
console.error('parent: none')
|
||||
}
|
||||
}
|
||||
|
||||
export const WORKTREE_HANDLERS: Record<string, CommandHandler> = {
|
||||
'worktree ps': async ({ flags, client, json }) => {
|
||||
const result = await client.call<RuntimeWorktreePsResult>('worktree.ps', {
|
||||
@@ -50,20 +75,38 @@ export const WORKTREE_HANDLERS: Record<string, CommandHandler> = {
|
||||
})
|
||||
printResult(result, json, formatWorktreeShow)
|
||||
},
|
||||
'worktree create': async ({ flags, client, json }) => {
|
||||
const result = await client.call<{ worktree: RuntimeWorktreeRecord; warning?: string }>(
|
||||
'worktree.create',
|
||||
{
|
||||
repo: getRequiredStringFlag(flags, 'repo'),
|
||||
name: getRequiredStringFlag(flags, 'name'),
|
||||
baseBranch: getOptionalStringFlag(flags, 'base-branch'),
|
||||
linkedIssue: getOptionalNumberFlag(flags, 'issue'),
|
||||
comment: getOptionalStringFlag(flags, 'comment'),
|
||||
runHooks: flags.get('run-hooks') === true,
|
||||
activate: flags.get('activate') === true || flags.get('run-hooks') === true
|
||||
'worktree create': async ({ flags, client, cwd, json }) => {
|
||||
const callerTerminalHandle =
|
||||
typeof process.env.ORCA_TERMINAL_HANDLE === 'string' &&
|
||||
process.env.ORCA_TERMINAL_HANDLE.length > 0
|
||||
? process.env.ORCA_TERMINAL_HANDLE
|
||||
: undefined
|
||||
const explicitParentWorktree = getOptionalStringFlag(flags, 'parent-worktree')
|
||||
const noParent = flags.get('no-parent') === true
|
||||
let parentWorktree = explicitParentWorktree
|
||||
if (!parentWorktree && !noParent && !callerTerminalHandle) {
|
||||
try {
|
||||
// Why: agent shells can lose ORCA_TERMINAL_HANDLE while still running
|
||||
// inside an Orca worktree. Cwd keeps CLI-created children nestable.
|
||||
parentWorktree = await resolveCurrentWorktreeSelector(cwd, client)
|
||||
} catch {
|
||||
parentWorktree = undefined
|
||||
}
|
||||
)
|
||||
}
|
||||
const result = await client.call<RuntimeWorktreeCreateResult>('worktree.create', {
|
||||
repo: getRequiredStringFlag(flags, 'repo'),
|
||||
name: getRequiredStringFlag(flags, 'name'),
|
||||
baseBranch: getOptionalStringFlag(flags, 'base-branch'),
|
||||
linkedIssue: getOptionalNumberFlag(flags, 'issue'),
|
||||
comment: getOptionalStringFlag(flags, 'comment'),
|
||||
runHooks: flags.get('run-hooks') === true,
|
||||
activate: flags.get('activate') === true || flags.get('run-hooks') === true,
|
||||
parentWorktree,
|
||||
noParent,
|
||||
callerTerminalHandle
|
||||
})
|
||||
printHookWarning(result.result, json)
|
||||
printLineageSummary(result.result, json)
|
||||
printResult(result, json, formatWorktreeShow)
|
||||
},
|
||||
'worktree set': async ({ flags, client, cwd, json }) => {
|
||||
@@ -71,7 +114,9 @@ export const WORKTREE_HANDLERS: Record<string, CommandHandler> = {
|
||||
worktree: await getRequiredWorktreeSelector(flags, 'worktree', cwd, client),
|
||||
displayName: getOptionalStringFlag(flags, 'display-name'),
|
||||
linkedIssue: getOptionalNullableNumberFlag(flags, 'issue'),
|
||||
comment: getOptionalStringFlag(flags, 'comment')
|
||||
comment: getOptionalStringFlag(flags, 'comment'),
|
||||
parentWorktree: getOptionalStringFlag(flags, 'parent-worktree'),
|
||||
noParent: flags.get('no-parent') === true
|
||||
})
|
||||
printResult(result, json, formatWorktreeShow)
|
||||
},
|
||||
|
||||
+2
-2
@@ -140,10 +140,10 @@ Common Commands:
|
||||
orca open [--json]
|
||||
orca status [--json]
|
||||
orca worktree list [--repo <selector>] [--limit <n>] [--json]
|
||||
orca worktree create --repo <selector> --name <name> [--base-branch <ref>] [--issue <number>] [--comment <text>] [--run-hooks] [--activate] [--json]
|
||||
orca worktree create --repo <selector> --name <name> [--base-branch <ref>] [--issue <number>] [--comment <text>] [--parent-worktree <selector>] [--no-parent] [--run-hooks] [--activate] [--json]
|
||||
orca worktree show --worktree <selector> [--json]
|
||||
orca worktree current [--json]
|
||||
orca worktree set --worktree <selector> [--display-name <name>] [--issue <number|null>] [--comment <text>] [--json]
|
||||
orca worktree set --worktree <selector> [--display-name <name>] [--issue <number|null>] [--comment <text>] [--parent-worktree <selector>|--no-parent] [--json]
|
||||
orca worktree rm --worktree <selector> [--force] [--run-hooks] [--json]
|
||||
orca worktree ps [--limit <n>] [--json]
|
||||
orca terminal list [--worktree <selector>] [--limit <n>] [--json]
|
||||
|
||||
+40
-5
@@ -61,6 +61,7 @@ describe('orca cli worktree awareness', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
callMock.mockReset()
|
||||
delete process.env.ORCA_TERMINAL_HANDLE
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
@@ -142,13 +143,16 @@ describe('orca cli worktree awareness', () => {
|
||||
worktree: `path:${path.resolve('/tmp/repo/feature')}`,
|
||||
displayName: undefined,
|
||||
linkedIssue: undefined,
|
||||
comment: 'hello'
|
||||
comment: 'hello',
|
||||
parentWorktree: undefined,
|
||||
noParent: false
|
||||
})
|
||||
})
|
||||
|
||||
it('passes explicit activation through worktree.create', async () => {
|
||||
queueFixtures(
|
||||
callMock,
|
||||
worktreeListFixture([buildWorktree('/tmp/repo', 'main', 'abc', 'repo-1')]),
|
||||
okFixture('req_create', {
|
||||
worktree: buildWorktree('/tmp/repo/feature', 'feature', 'abc', 'repo-1')
|
||||
})
|
||||
@@ -160,20 +164,24 @@ describe('orca cli worktree awareness', () => {
|
||||
'/tmp/repo'
|
||||
)
|
||||
|
||||
expect(callMock).toHaveBeenCalledWith('worktree.create', {
|
||||
expect(callMock).toHaveBeenNthCalledWith(2, 'worktree.create', {
|
||||
repo: 'id:repo-1',
|
||||
name: 'feature',
|
||||
baseBranch: undefined,
|
||||
linkedIssue: undefined,
|
||||
comment: undefined,
|
||||
runHooks: false,
|
||||
activate: true
|
||||
activate: true,
|
||||
parentWorktree: `path:${path.resolve('/tmp/repo')}`,
|
||||
noParent: false,
|
||||
callerTerminalHandle: undefined
|
||||
})
|
||||
})
|
||||
|
||||
it('opts into setup and activation when worktree.create runs hooks', async () => {
|
||||
queueFixtures(
|
||||
callMock,
|
||||
worktreeListFixture([buildWorktree('/tmp/repo', 'main', 'abc', 'repo-1')]),
|
||||
okFixture('req_create', {
|
||||
worktree: buildWorktree('/tmp/repo/feature', 'feature', 'abc', 'repo-1')
|
||||
})
|
||||
@@ -185,14 +193,17 @@ describe('orca cli worktree awareness', () => {
|
||||
'/tmp/repo'
|
||||
)
|
||||
|
||||
expect(callMock).toHaveBeenCalledWith('worktree.create', {
|
||||
expect(callMock).toHaveBeenNthCalledWith(2, 'worktree.create', {
|
||||
repo: 'id:repo-1',
|
||||
name: 'feature',
|
||||
baseBranch: undefined,
|
||||
linkedIssue: undefined,
|
||||
comment: undefined,
|
||||
runHooks: true,
|
||||
activate: true
|
||||
activate: true,
|
||||
parentWorktree: `path:${path.resolve('/tmp/repo')}`,
|
||||
noParent: false,
|
||||
callerTerminalHandle: undefined
|
||||
})
|
||||
})
|
||||
|
||||
@@ -308,6 +319,30 @@ describe('orca cli worktree awareness', () => {
|
||||
errSpy.mockRestore()
|
||||
})
|
||||
|
||||
it('passes the caller terminal handle through orchestration task-create', async () => {
|
||||
process.env.ORCA_TERMINAL_HANDLE = 'term_creator'
|
||||
callMock.mockResolvedValueOnce({
|
||||
id: 'req_task_create',
|
||||
ok: true,
|
||||
result: {
|
||||
task: { id: 'task_1', status: 'ready' }
|
||||
},
|
||||
_meta: {
|
||||
runtimeId: 'runtime-1'
|
||||
}
|
||||
})
|
||||
vi.spyOn(console, 'log').mockImplementation(() => {})
|
||||
|
||||
await main(['orchestration', 'task-create', '--spec', 'spawn child workspace'], '/tmp/repo')
|
||||
|
||||
expect(callMock).toHaveBeenCalledWith('orchestration.taskCreate', {
|
||||
spec: 'spawn child workspace',
|
||||
deps: undefined,
|
||||
parent: undefined,
|
||||
callerTerminalHandle: 'term_creator'
|
||||
})
|
||||
})
|
||||
|
||||
it('passes dev mode to injected orchestration dispatches', async () => {
|
||||
process.env.ORCA_TERMINAL_HANDLE = 'term_sender'
|
||||
process.env.ORCA_USER_DATA_PATH = '/tmp/orca-dev'
|
||||
|
||||
+13
-3
@@ -72,7 +72,7 @@ export const CORE_COMMAND_SPECS: CommandSpec[] = [
|
||||
path: ['worktree', 'create'],
|
||||
summary: 'Create a new Orca-managed worktree',
|
||||
usage:
|
||||
'orca worktree create --repo <selector> --name <name> [--base-branch <ref>] [--issue <number>] [--comment <text>] [--run-hooks] [--activate] [--json]',
|
||||
'orca worktree create --repo <selector> --name <name> [--base-branch <ref>] [--issue <number>] [--comment <text>] [--parent-worktree <selector>] [--no-parent] [--run-hooks] [--activate] [--json]',
|
||||
allowedFlags: [
|
||||
...GLOBAL_FLAGS,
|
||||
'repo',
|
||||
@@ -80,6 +80,8 @@ export const CORE_COMMAND_SPECS: CommandSpec[] = [
|
||||
'base-branch',
|
||||
'issue',
|
||||
'comment',
|
||||
'parent-worktree',
|
||||
'no-parent',
|
||||
'run-hooks',
|
||||
'activate'
|
||||
],
|
||||
@@ -94,8 +96,16 @@ export const CORE_COMMAND_SPECS: CommandSpec[] = [
|
||||
path: ['worktree', 'set'],
|
||||
summary: 'Update Orca metadata for a worktree',
|
||||
usage:
|
||||
'orca worktree set --worktree <selector> [--display-name <name>] [--issue <number|null>] [--comment <text>] [--json]',
|
||||
allowedFlags: [...GLOBAL_FLAGS, 'worktree', 'display-name', 'issue', 'comment']
|
||||
'orca worktree set --worktree <selector> [--display-name <name>] [--issue <number|null>] [--comment <text>] [--parent-worktree <selector>|--no-parent] [--json]',
|
||||
allowedFlags: [
|
||||
...GLOBAL_FLAGS,
|
||||
'worktree',
|
||||
'display-name',
|
||||
'issue',
|
||||
'comment',
|
||||
'parent-worktree',
|
||||
'no-parent'
|
||||
]
|
||||
},
|
||||
{
|
||||
path: ['worktree', 'rm'],
|
||||
|
||||
Reference in New Issue
Block a user