Add workspace lineage grouping

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
brennanb2025
2026-05-15 01:13:54 -07:00
co-authored by Orca
parent c66e9801fe
commit 7948ef2ce8
40 changed files with 2800 additions and 107 deletions
+12
View File
@@ -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
}
+7 -1
View File
@@ -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}]`)
+59 -14
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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'],