mirror of
https://github.com/stablyai/orca.git
synced 2026-10-05 08:02:33 +00:00
* fix(cli): resolve host names across both kinds, and stop ssh: answering empty `--host ssh:<id>` was never validated. An unknown target filtered to nothing and returned ok:true with an empty list — the same silent wrong-machine answer that unknown `runtime:` ids gave before they were rejected. And because SSH target ids are machine-generated (`ssh-<timestamp>-<random>`) while the name anyone actually knows is the label, this fired on the ordinary spelling rather than a rare typo: every human-typed SSH name missed. The two kinds of remote machine are also reached on different axes. A paired Orca server is a connection (`--environment <name>`); an SSH target is a machine the connected host reaches (`--host ssh:<id>`). A caller only knows "the machine called X", so naming X on the wrong axis was the common failure and produced either an empty answer or a dead-end "unknown environment". Now: `ssh:` resolves labels as well as ids and rejects an unknown target with the known ones listed; `runtime:` accepts the environment name as well as its id, matching --environment, and canonicalizes to the id so stored host ids still compare; and when a name misses on one axis but exists on the other, the error says which and gives the exact flag. Candidates ride along in error.data so an agent can recover without parsing prose. `orca host list` is the discovery surface that was missing entirely — nothing in the CLI listed SSH targets, so a caller told to use one had nowhere to look. It prints this machine, the SSH targets registered on the connected host, and the paired servers, each with the selector to use. * fix(cli): give --environment the same cross-kind hint, and validate the ssh host on setup-create Two gaps a follow-up survey found in the first pass. `--environment openclaw` still dead-ended with a bare "Unknown environment" while an SSH target by that name sat right there — the inverse of the case just fixed, and the direction the report actually hit. The store's own error cannot carry the hint: translateStoreError forwards code and message and drops data. So the selector is resolved before the client is built, where the payload survives. Only the explicit flag is asserted eagerly; an ambient ORCA_ENVIRONMENT stays lazy, because failing local-only commands over stale background config would be a regression. `project setup-create` records independent metadata and, unlike the other setup paths, is not covered by the runtime's ssh rejection — so an unknown target persisted a row pointing at a machine that does not exist. It now resolves the host. `local` and `runtime:` still pass through untouched: this is also the provisioning path, where a runtime host legitimately may not exist yet when its metadata is written. `setup-existing-folder` and `setup-clone` deliberately keep the unresolved id. The runtime rejects every ssh host for those operations regardless of whether it exists, so resolving first would answer "no such target" and imply the command would have worked with the right id. * fix(cli): refuse an ambiguous host name instead of resolving the first match Name lookup took the first match while the environment store itself refuses an ambiguous name rather than guessing. That put the guess back, in the selector whose entire purpose is to stop a command reaching a machine the caller did not choose — and it applied to both spellings: two SSH targets sharing a label, and two paired servers sharing a name. Both now resolve to nothing and report every candidate with its id, so the caller picks. An exact id still resolves past a colliding name, since an id is never ambiguous. Also pins the property that makes accepting a name safe at all: `runtime:<id>` is a persisted token that lands in ProjectHostSetup.hostId and is embedded in generated setup ids, so the name is canonicalized to the id before anything downstream sees it. A test now asserts a name never reaches the wire. * fix(cli): fall back to the older ssh listing so an old host is not read as having no targets Hosts predating ssh.listTargetSummaries still answer ssh.listTargets, and both are served by the same summariser. Swallowing the method_not_found made such a host indistinguishable from one with no SSH targets registered, which would reject a target id that is valid there — a new-client/old-host regression on a path that previously passed the id through unvalidated.
255 lines
7.1 KiB
TypeScript
255 lines
7.1 KiB
TypeScript
import type { CommandHandler } from './dispatch'
|
|
import { BROWSER_HANDLER_GROUPS } from './browser-handler-groups'
|
|
|
|
export type HandlerGroup = {
|
|
name: string
|
|
// Why: eager string keys let dispatch build (and duplicate-check) the whole
|
|
// command table without loading any group's transitive module graph.
|
|
keys: readonly string[]
|
|
load: () => Promise<Record<string, CommandHandler>>
|
|
}
|
|
|
|
// Why: `keys` mirrors each group's exported record and is verified against the
|
|
// real exports by handler-group-manifest.test.ts, so drift fails CI, not dispatch.
|
|
export const HANDLER_GROUPS: readonly HandlerGroup[] = [
|
|
{
|
|
name: 'core',
|
|
keys: ['claude-teams', 'open', 'serve', 'status'],
|
|
load: async () => (await import('./handlers/core.js')).CORE_HANDLERS
|
|
},
|
|
{
|
|
name: 'account',
|
|
keys: ['account add', 'account list'],
|
|
load: async () => (await import('./handlers/account.js')).ACCOUNT_HANDLERS
|
|
},
|
|
{
|
|
name: 'artifacts',
|
|
keys: [
|
|
'artifacts list',
|
|
'artifacts share',
|
|
'artifacts update',
|
|
'artifacts unshare',
|
|
'artifacts delete'
|
|
],
|
|
load: async () => (await import('./handlers/artifacts.js')).ARTIFACT_HANDLERS
|
|
},
|
|
{
|
|
name: 'automations',
|
|
keys: [
|
|
'automations list',
|
|
'automations show',
|
|
'automations create',
|
|
'automations edit',
|
|
'automations remove',
|
|
'automations run',
|
|
'automations runs'
|
|
],
|
|
load: async () => (await import('./handlers/automations.js')).AUTOMATION_HANDLERS
|
|
},
|
|
{
|
|
name: 'project',
|
|
keys: [
|
|
'project list',
|
|
'project setups',
|
|
'project setup-existing-folder',
|
|
'project setup-clone',
|
|
'project setup-create',
|
|
'project setup-update',
|
|
'project setup-delete'
|
|
],
|
|
load: async () => (await import('./handlers/project.js')).PROJECT_HANDLERS
|
|
},
|
|
{
|
|
name: 'repo',
|
|
keys: ['repo list', 'repo add', 'repo show', 'repo set-base-ref', 'repo search-refs'],
|
|
load: async () => (await import('./handlers/repo.js')).REPO_HANDLERS
|
|
},
|
|
{
|
|
name: 'worktree',
|
|
keys: [
|
|
'worktree ps',
|
|
'worktree list',
|
|
'worktree show',
|
|
'worktree current',
|
|
'worktree create',
|
|
'worktree set',
|
|
'worktree rm'
|
|
],
|
|
load: async () => (await import('./handlers/worktree.js')).WORKTREE_HANDLERS
|
|
},
|
|
{
|
|
name: 'file',
|
|
keys: ['file open', 'file diff', 'file open-changed'],
|
|
load: async () => (await import('./handlers/file.js')).FILE_HANDLERS
|
|
},
|
|
{
|
|
name: 'terminal',
|
|
keys: [
|
|
'terminal list',
|
|
'terminal show',
|
|
'terminal read',
|
|
'terminal send',
|
|
'terminal wait',
|
|
'terminal stop',
|
|
'terminal rename',
|
|
'terminal create',
|
|
'terminal switch',
|
|
'terminal close',
|
|
'terminal split'
|
|
],
|
|
load: async () => (await import('./handlers/terminal.js')).TERMINAL_HANDLERS
|
|
},
|
|
...BROWSER_HANDLER_GROUPS,
|
|
{
|
|
name: 'orchestration',
|
|
keys: [
|
|
'orchestration run-create',
|
|
'orchestration run-use',
|
|
'orchestration run-current',
|
|
'orchestration run-list',
|
|
'orchestration run-show',
|
|
'orchestration send',
|
|
'orchestration check',
|
|
'orchestration reply',
|
|
'orchestration inbox',
|
|
'orchestration task-create',
|
|
'orchestration task-list',
|
|
'orchestration task-update',
|
|
'orchestration worker-start',
|
|
'orchestration worker-show',
|
|
'orchestration worker-read',
|
|
'orchestration worker-stop',
|
|
'orchestration worker-abandon',
|
|
'orchestration worker-release',
|
|
'orchestration worker-retain',
|
|
'orchestration worker-list',
|
|
'orchestration dispatch',
|
|
'orchestration ask',
|
|
'orchestration dispatch-show',
|
|
'orchestration coordinator-start',
|
|
'orchestration coordinator-stop',
|
|
'orchestration gate-create',
|
|
'orchestration gate-resolve',
|
|
'orchestration gate-list',
|
|
'orchestration reset'
|
|
],
|
|
load: async () => (await import('./handlers/orchestration.js')).ORCHESTRATION_HANDLERS
|
|
},
|
|
{
|
|
name: 'emulator',
|
|
keys: [
|
|
'emulator list',
|
|
'emulator devices',
|
|
'emulator attach',
|
|
'emulator tap',
|
|
'emulator type',
|
|
'emulator gesture',
|
|
'emulator button',
|
|
'emulator rotate',
|
|
'emulator exec',
|
|
'emulator kill',
|
|
'emulator shutdown',
|
|
'emulator install',
|
|
'emulator launch',
|
|
'emulator permissions',
|
|
'emulator ax',
|
|
'emulator logcat'
|
|
],
|
|
load: async () => (await import('./handlers/emulator.js')).EMULATOR_HANDLERS
|
|
},
|
|
{
|
|
name: 'computer',
|
|
keys: [
|
|
'computer capabilities',
|
|
'computer list-apps',
|
|
'computer permissions',
|
|
'computer list-windows',
|
|
'computer get-app-state',
|
|
'computer click',
|
|
'computer perform-secondary-action',
|
|
'computer scroll',
|
|
'computer drag',
|
|
'computer type-text',
|
|
'computer press-key',
|
|
'computer hotkey',
|
|
'computer paste-text',
|
|
'computer set-value'
|
|
],
|
|
load: async () => (await import('./handlers/computer.js')).COMPUTER_HANDLERS
|
|
},
|
|
{
|
|
name: 'agent-hooks',
|
|
keys: ['agent hooks prepare-codex', 'agent hooks status', 'agent hooks off', 'agent hooks on'],
|
|
load: async () => (await import('./handlers/agent-hooks.js')).AGENT_HOOK_HANDLERS
|
|
},
|
|
{
|
|
name: 'diagnostics',
|
|
keys: ['diagnostics memory'],
|
|
load: async () => (await import('./handlers/diagnostics.js')).DIAGNOSTICS_HANDLERS
|
|
},
|
|
{
|
|
name: 'introspection',
|
|
keys: ['agent-context'],
|
|
load: async () => (await import('./handlers/introspection.js')).INTROSPECTION_HANDLERS
|
|
},
|
|
{
|
|
name: 'environment',
|
|
keys: [
|
|
'host list',
|
|
'environment add',
|
|
'environment list',
|
|
'environment show',
|
|
'environment rm'
|
|
],
|
|
load: async () => (await import('./handlers/environment.js')).ENVIRONMENT_HANDLERS
|
|
},
|
|
{
|
|
name: 'linear',
|
|
keys: [
|
|
'linear save-issue',
|
|
'linear list-issues',
|
|
'linear relation add',
|
|
'linear relation remove',
|
|
'linear issue',
|
|
'linear search',
|
|
'linear team list',
|
|
'linear team members',
|
|
'linear team states',
|
|
'linear team labels',
|
|
'linear project list',
|
|
'linear list',
|
|
'linear status set',
|
|
'linear assignee set',
|
|
'linear assignee clear',
|
|
'linear priority set',
|
|
'linear priority clear',
|
|
'linear estimate set',
|
|
'linear estimate clear',
|
|
'linear due-date set',
|
|
'linear due-date clear',
|
|
'linear label add',
|
|
'linear label remove',
|
|
'linear label set',
|
|
'linear comment add',
|
|
'linear attach',
|
|
'linear create'
|
|
],
|
|
load: async () => (await import('./handlers/linear.js')).LINEAR_HANDLERS
|
|
},
|
|
{
|
|
name: 'vm',
|
|
keys: ['vm recipe doctor'],
|
|
load: async () => (await import('./handlers/vm.js')).VM_HANDLERS
|
|
},
|
|
{
|
|
name: 'skill-sharing',
|
|
keys: ['skills installed', 'skills share'],
|
|
load: async () => (await import('./handlers/skill-sharing.js')).SKILL_SHARING_HANDLERS
|
|
},
|
|
{
|
|
name: 'skills',
|
|
keys: ['skills list', 'skills get', 'skills install', 'skills update'],
|
|
load: async () => (await import('./handlers/skills.js')).SKILL_HANDLERS
|
|
}
|
|
]
|