mirror of
https://github.com/stablyai/orca.git
synced 2026-10-19 08:03:13 +00:00
* feat(cli): make the CLI self-correcting and self-describing for agents Agents build a generalized model of how CLIs work and apply it to every tool. When orca diverged — `rm` where git uses `remove` — a reasonable first guess (`orca worktree remove`) dead-ended on a bare "Unknown command" with no path forward. This makes the CLI degrade gracefully when the orca-cli skill isn't loaded in context. - First-class CommandSpec.aliases, resolved to the canonical path before dispatch (no new handler registrations). `worktree remove`/`delete` now resolve to `rm`; the ad-hoc `terminal focus` duplicate spec/handler is migrated onto the mechanism. - Did-you-mean suggestions on unknown commands and unknown flags, ranked by edit distance over the live registry, surfaced in both stderr and --json error.data (reusing the existing nextSteps channel). - `orca agent-context [--json]`: a versioned, machine-readable dump of the command schema. Pure local read (no RPC), so it works over SSH and when the app isn't running. - CI guards: specs<->handlers parity, and a vocabulary policy that fails on new off-policy deletion/read verbs (existing ones grandfathered). * Address PR review feedback (#6303) - agent-context now emits each command's effective flag set (globals + conditional --page), not just allowedFlags, so the schema no longer under-reports --json/--help. Shared as effectiveAllowedFlags() between validation and the schema. - Collision check now covers alias paths too, so a duplicate alias that would silently shadow a real command fails the build. * fix(cli): harden agent recovery and introspection Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com> Co-authored-by: Orca <help@stably.ai>
90 lines
3.2 KiB
TypeScript
90 lines
3.2 KiB
TypeScript
import type { RuntimeClient } from './runtime-client'
|
|
import { RuntimeClientError } from './runtime-client'
|
|
import { CORE_HANDLERS } from './handlers/core'
|
|
import { AUTOMATION_HANDLERS } from './handlers/automations'
|
|
import { PROJECT_HANDLERS } from './handlers/project'
|
|
import { REPO_HANDLERS } from './handlers/repo'
|
|
import { WORKTREE_HANDLERS } from './handlers/worktree'
|
|
import { FILE_HANDLERS } from './handlers/file'
|
|
import { TERMINAL_HANDLERS } from './handlers/terminal'
|
|
import { BROWSER_NAV_HANDLERS } from './handlers/browser-nav'
|
|
import { BROWSER_INTERACT_HANDLERS } from './handlers/browser-interact'
|
|
import { BROWSER_TAB_HANDLERS } from './handlers/browser-tab'
|
|
import { BROWSER_PROFILE_HANDLERS } from './handlers/browser-profile'
|
|
import { BROWSER_COOKIE_HANDLERS } from './handlers/browser-cookie'
|
|
import { BROWSER_CAPTURE_HANDLERS } from './handlers/browser-capture'
|
|
import { BROWSER_ENV_HANDLERS } from './handlers/browser-env'
|
|
import { BROWSER_STORAGE_HANDLERS } from './handlers/browser-storage'
|
|
import { ORCHESTRATION_HANDLERS } from './handlers/orchestration'
|
|
import { COMPUTER_HANDLERS } from './handlers/computer'
|
|
import { ENVIRONMENT_HANDLERS } from './handlers/environment'
|
|
import { AGENT_HOOK_HANDLERS } from './handlers/agent-hooks'
|
|
import { DIAGNOSTICS_HANDLERS } from './handlers/diagnostics'
|
|
import { INTROSPECTION_HANDLERS } from './handlers/introspection'
|
|
import { EMULATOR_HANDLERS } from './handlers/emulator'
|
|
import { LINEAR_HANDLERS } from './handlers/linear'
|
|
import { VM_HANDLERS } from './handlers/vm'
|
|
|
|
export type HandlerContext = {
|
|
flags: Map<string, string | boolean>
|
|
client: RuntimeClient
|
|
cwd: string
|
|
json: boolean
|
|
rawArgs?: string[]
|
|
}
|
|
|
|
export type CommandHandler = (ctx: HandlerContext) => Promise<void>
|
|
|
|
function buildHandlers(): Map<string, CommandHandler> {
|
|
const table = new Map<string, CommandHandler>()
|
|
const groups = [
|
|
CORE_HANDLERS,
|
|
AUTOMATION_HANDLERS,
|
|
PROJECT_HANDLERS,
|
|
REPO_HANDLERS,
|
|
WORKTREE_HANDLERS,
|
|
FILE_HANDLERS,
|
|
TERMINAL_HANDLERS,
|
|
BROWSER_NAV_HANDLERS,
|
|
BROWSER_INTERACT_HANDLERS,
|
|
BROWSER_TAB_HANDLERS,
|
|
BROWSER_PROFILE_HANDLERS,
|
|
BROWSER_COOKIE_HANDLERS,
|
|
BROWSER_CAPTURE_HANDLERS,
|
|
BROWSER_ENV_HANDLERS,
|
|
BROWSER_STORAGE_HANDLERS,
|
|
ORCHESTRATION_HANDLERS,
|
|
EMULATOR_HANDLERS,
|
|
COMPUTER_HANDLERS,
|
|
AGENT_HOOK_HANDLERS,
|
|
DIAGNOSTICS_HANDLERS,
|
|
INTROSPECTION_HANDLERS,
|
|
ENVIRONMENT_HANDLERS,
|
|
LINEAR_HANDLERS,
|
|
VM_HANDLERS
|
|
]
|
|
for (const group of groups) {
|
|
for (const [key, handler] of Object.entries(group)) {
|
|
if (table.has(key)) {
|
|
throw new Error(`Duplicate CLI handler registration for "${key}"`)
|
|
}
|
|
table.set(key, handler)
|
|
}
|
|
}
|
|
return table
|
|
}
|
|
|
|
const HANDLERS = buildHandlers()
|
|
|
|
// Why: exposes only the canonical command keys (not the handler internals) so the
|
|
// registry-parity guard can check specs↔handlers without rebuilding the table.
|
|
export const HANDLER_COMMAND_KEYS: ReadonlySet<string> = new Set(HANDLERS.keys())
|
|
|
|
export async function dispatch(commandPath: string[], ctx: HandlerContext): Promise<void> {
|
|
const handler = HANDLERS.get(commandPath.join(' '))
|
|
if (!handler) {
|
|
throw new RuntimeClientError('invalid_argument', `Unknown command: ${commandPath.join(' ')}`)
|
|
}
|
|
await handler(ctx)
|
|
}
|