mirror of
https://github.com/stablyai/orca.git
synced 2026-10-18 16:03:17 +00:00
Preserve automation CLI run context
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -243,3 +243,4 @@ Done means: reviewers and future agents can understand the vision, implementatio
|
||||
- [x] 2026-06-12: Guarded remaining GitLab path-only fallbacks by making `workItemByPath` use the same repoId/source-context selector as the rest of GitLab IPC, passing repoId from URL picker and right-sidebar GitLab review actions, and verifying source-host routing with GitLab IPC regressions plus focused oxlint and full `pnpm run typecheck`.
|
||||
- [x] 2026-06-12: Guarded GitHub label and assignable-user metadata lookups with the same repo/source-context selector as task mutations, so drawer edit metadata cannot bypass source-host validation; verified with GitHub IPC regressions (17 tests), focused oxlint, and full `pnpm run typecheck`.
|
||||
- [x] 2026-06-12: Continued the Host copy audit through onboarding, remote folder-add, attachment import, Settings SSH copy, and remote file browser catalog text; extracted `RepoStepNestedImportPanel` to keep the touched onboarding component under max-lines, and verified with stale-copy scans, focused onboarding/repos tests, focused oxlint, full `pnpm run typecheck`, and `git diff --check`.
|
||||
- [x] 2026-06-12: Preserved explicit automation run-host context in CLI project selectors: `orca automations create/edit --project/--host` and `--project-host-setup` now send `WorkspaceRunContext` alongside the legacy repo selector, so scheduled runs retain logical project, host, setup, repo, and path identity. Verified with focused CLI regressions (108 tests), targeted oxlint, full `pnpm run typecheck`, and `git diff --check`.
|
||||
|
||||
@@ -7,7 +7,11 @@ import type {
|
||||
AutomationSchedulePreset,
|
||||
AutomationUpdateInput
|
||||
} from '../../shared/automations-types'
|
||||
import type { TuiAgent } from '../../shared/types'
|
||||
import {
|
||||
buildWorkspaceRunContext,
|
||||
type WorkspaceRunContext
|
||||
} from '../../shared/task-source-context'
|
||||
import type { ProjectHostSetup, TuiAgent } from '../../shared/types'
|
||||
import {
|
||||
DEFAULT_AUTOMATION_PRECHECK_TIMEOUT_SECONDS,
|
||||
MAX_AUTOMATION_PRECHECK_TIMEOUT_SECONDS
|
||||
@@ -33,7 +37,7 @@ import { getOptionalWorktreeSelector, resolveCurrentWorktreeSelector } from '../
|
||||
import {
|
||||
assertWorkspaceTargetFlagsCompatible,
|
||||
hasWorkspaceProjectTarget,
|
||||
resolveProjectCreateRepoSelector
|
||||
resolveProjectCreateTarget
|
||||
} from '../worktree-project-target'
|
||||
|
||||
type AutomationCreateParams = Omit<AutomationCreateInput, 'projectId' | 'timezone'> & {
|
||||
@@ -298,7 +302,7 @@ async function resolveDefaultTarget(
|
||||
flags: Map<string, string | boolean>,
|
||||
cwd: string,
|
||||
client: Parameters<CommandHandler>[0]['client']
|
||||
): Promise<{ repo?: string; workspace?: string }> {
|
||||
): Promise<{ repo?: string; workspace?: string; runContext?: WorkspaceRunContext }> {
|
||||
assertWorkspaceTargetFlagsCompatible(flags)
|
||||
const repo = getOptionalStringFlag(flags, 'repo')
|
||||
if (repo && getOptionalStringFlag(flags, 'workspace')) {
|
||||
@@ -310,9 +314,12 @@ async function resolveDefaultTarget(
|
||||
'Use either --workspace or project target flags, not both.'
|
||||
)
|
||||
}
|
||||
const projectRepo = await resolveProjectCreateRepoSelector(flags, client)
|
||||
if (projectRepo) {
|
||||
return { repo: projectRepo }
|
||||
const projectTarget = await resolveProjectCreateTarget(flags, client)
|
||||
if (projectTarget) {
|
||||
return {
|
||||
repo: projectTarget.repoSelector,
|
||||
runContext: buildAutomationRunContextFromSetup(projectTarget.setup)
|
||||
}
|
||||
}
|
||||
const workspace = await getOptionalWorktreeSelector(flags, 'workspace', cwd, client)
|
||||
if (repo || workspace) {
|
||||
@@ -332,7 +339,7 @@ async function getExplicitTarget(
|
||||
flags: Map<string, string | boolean>,
|
||||
cwd: string,
|
||||
client: Parameters<CommandHandler>[0]['client']
|
||||
): Promise<{ repo?: string; workspace?: string }> {
|
||||
): Promise<{ repo?: string; workspace?: string; runContext?: WorkspaceRunContext }> {
|
||||
assertWorkspaceTargetFlagsCompatible(flags)
|
||||
const repo = getOptionalStringFlag(flags, 'repo')
|
||||
if (repo && getOptionalStringFlag(flags, 'workspace')) {
|
||||
@@ -344,14 +351,34 @@ async function getExplicitTarget(
|
||||
'Use either --workspace or project target flags, not both.'
|
||||
)
|
||||
}
|
||||
const projectRepo = await resolveProjectCreateRepoSelector(flags, client)
|
||||
if (projectRepo) {
|
||||
return { repo: projectRepo }
|
||||
const projectTarget = await resolveProjectCreateTarget(flags, client)
|
||||
if (projectTarget) {
|
||||
return {
|
||||
repo: projectTarget.repoSelector,
|
||||
runContext: buildAutomationRunContextFromSetup(projectTarget.setup)
|
||||
}
|
||||
}
|
||||
const workspace = await getOptionalWorktreeSelector(flags, 'workspace', cwd, client)
|
||||
return { repo, workspace }
|
||||
}
|
||||
|
||||
function buildAutomationRunContextFromSetup(setup: ProjectHostSetup): WorkspaceRunContext {
|
||||
const runContext = buildWorkspaceRunContext({
|
||||
projectId: setup.projectId,
|
||||
hostId: setup.hostId,
|
||||
projectHostSetupId: setup.id,
|
||||
repoId: setup.repoId,
|
||||
path: setup.path
|
||||
})
|
||||
if (!runContext) {
|
||||
throw new RuntimeClientError(
|
||||
'invalid_argument',
|
||||
`Project host setup is missing automation run context fields: ${setup.id}`
|
||||
)
|
||||
}
|
||||
return runContext
|
||||
}
|
||||
|
||||
export const AUTOMATION_HANDLERS: Record<string, CommandHandler> = {
|
||||
'automations list': async ({ client, json }) => {
|
||||
const result = await client.call<{ automations: Automation[] }>('automation.list')
|
||||
@@ -376,6 +403,7 @@ export const AUTOMATION_HANDLERS: Record<string, CommandHandler> = {
|
||||
prompt: getRequiredStringFlag(flags, 'prompt'),
|
||||
precheck: getPrecheckFlag(flags),
|
||||
agentId: getProviderFlag(flags),
|
||||
...(target.runContext ? { runContext: target.runContext } : {}),
|
||||
repo: target.repo,
|
||||
workspace: target.workspace,
|
||||
workspaceMode,
|
||||
@@ -398,6 +426,7 @@ export const AUTOMATION_HANDLERS: Record<string, CommandHandler> = {
|
||||
prompt: getOptionalStringFlag(flags, 'prompt'),
|
||||
precheck: getPrecheckFlag(flags),
|
||||
agentId: getOptionalProviderFlag(flags),
|
||||
...(target.runContext ? { runContext: target.runContext } : {}),
|
||||
repo: target.repo,
|
||||
workspace: target.workspace,
|
||||
workspaceMode: getWorkspaceModeFlag(flags),
|
||||
|
||||
@@ -2710,12 +2710,71 @@ describe('orca cli worktree awareness', () => {
|
||||
'automation.create',
|
||||
expect.objectContaining({
|
||||
repo: 'id:repo-gpu',
|
||||
runContext: {
|
||||
kind: 'workspace-run',
|
||||
projectId: 'github:stablyai/orca',
|
||||
hostId: 'runtime:gpu',
|
||||
projectHostSetupId: 'setup-gpu',
|
||||
repoId: 'repo-gpu',
|
||||
path: '/srv/orca'
|
||||
},
|
||||
workspace: undefined,
|
||||
workspaceMode: 'new_per_run'
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('resolves project-host-setup flags for automation edit with explicit run context', async () => {
|
||||
queueFixtures(
|
||||
callMock,
|
||||
okFixture('req_project_setups', {
|
||||
setups: [
|
||||
{
|
||||
id: 'setup-gpu',
|
||||
projectId: 'github:stablyai/orca',
|
||||
hostId: 'runtime:gpu',
|
||||
repoId: 'repo-gpu',
|
||||
path: '/srv/orca',
|
||||
displayName: 'Orca',
|
||||
setupState: 'ready',
|
||||
setupMethod: 'legacy-repo',
|
||||
createdAt: 1,
|
||||
updatedAt: 1
|
||||
}
|
||||
]
|
||||
}),
|
||||
okFixture('req_edit', {
|
||||
automation: { id: 'auto-1', name: 'GPU review' }
|
||||
})
|
||||
)
|
||||
vi.spyOn(console, 'log').mockImplementation(() => {})
|
||||
|
||||
await main(
|
||||
['automations', 'edit', 'auto-1', '--project-host-setup', 'setup-gpu', '--json'],
|
||||
'/tmp/repo'
|
||||
)
|
||||
|
||||
expect(callMock).toHaveBeenNthCalledWith(1, 'projectHostSetup.list')
|
||||
expect(callMock).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
'automation.update',
|
||||
expect.objectContaining({
|
||||
id: 'auto-1',
|
||||
updates: expect.objectContaining({
|
||||
repo: 'id:repo-gpu',
|
||||
runContext: {
|
||||
kind: 'workspace-run',
|
||||
projectId: 'github:stablyai/orca',
|
||||
hostId: 'runtime:gpu',
|
||||
projectHostSetupId: 'setup-gpu',
|
||||
repoId: 'repo-gpu',
|
||||
path: '/srv/orca'
|
||||
}
|
||||
})
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it('rejects invalid automation --day values before calling the runtime', async () => {
|
||||
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => {})
|
||||
const errSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
||||
|
||||
@@ -2,6 +2,11 @@ import type { ProjectHostSetup } from '../shared/types'
|
||||
import type { RuntimeClient } from './runtime-client'
|
||||
import { RuntimeClientError } from './runtime-client'
|
||||
|
||||
export type ProjectCreateTarget = {
|
||||
repoSelector: string
|
||||
setup: ProjectHostSetup
|
||||
}
|
||||
|
||||
function getPresentStringFlag(
|
||||
flags: Map<string, string | boolean>,
|
||||
name: string
|
||||
@@ -40,6 +45,13 @@ export async function resolveProjectCreateRepoSelector(
|
||||
flags: Map<string, string | boolean>,
|
||||
client: RuntimeClient
|
||||
): Promise<string | undefined> {
|
||||
return (await resolveProjectCreateTarget(flags, client))?.repoSelector
|
||||
}
|
||||
|
||||
export async function resolveProjectCreateTarget(
|
||||
flags: Map<string, string | boolean>,
|
||||
client: RuntimeClient
|
||||
): Promise<ProjectCreateTarget | undefined> {
|
||||
const projectHostSetupId = getPresentStringFlag(flags, 'project-host-setup')
|
||||
const projectId = getPresentStringFlag(flags, 'project')
|
||||
const hostId = getPresentStringFlag(flags, 'host')
|
||||
@@ -66,5 +78,8 @@ export async function resolveProjectCreateRepoSelector(
|
||||
: `Project is not set up on the selected host: ${projectId}${hostId ? ` on ${hostId}` : ''}`
|
||||
)
|
||||
}
|
||||
return `id:${setup.repoId}`
|
||||
return {
|
||||
repoSelector: `id:${setup.repoId}`,
|
||||
setup
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user