mirror of
https://github.com/stablyai/orca.git
synced 2026-09-30 08:03:12 +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>
74 lines
2.0 KiB
TypeScript
74 lines
2.0 KiB
TypeScript
import type { CommandSpec } from './args'
|
|
|
|
// Why: predictable verbs prevent failed agent guesses; existing divergences stay
|
|
// grandfathered because renaming them would break compatibility.
|
|
|
|
type VerbFamily = {
|
|
name: string
|
|
offPolicyVerbs: Set<string>
|
|
canonical: string
|
|
allowlist: Set<string>
|
|
}
|
|
|
|
const FAMILIES: VerbFamily[] = [
|
|
{
|
|
name: 'deletion',
|
|
offPolicyVerbs: new Set(['remove', 'delete', 'destroy']),
|
|
canonical: 'rm',
|
|
allowlist: new Set([
|
|
'cookie delete',
|
|
'tab profile delete',
|
|
'automations remove',
|
|
'linear label remove'
|
|
])
|
|
},
|
|
{
|
|
name: 'single-item read',
|
|
offPolicyVerbs: new Set(['get']),
|
|
canonical: 'show',
|
|
allowlist: new Set(['get', 'cookie get', 'storage local get', 'storage session get'])
|
|
}
|
|
]
|
|
|
|
export type VocabularyViolation = {
|
|
command: string
|
|
verb: string
|
|
family: string
|
|
canonical: string
|
|
}
|
|
|
|
function terminalVerb(path: string[]): string {
|
|
return path.at(-1) ?? ''
|
|
}
|
|
|
|
function hasCanonicalAlias(spec: CommandSpec, canonical: string): boolean {
|
|
const expected = [...spec.path.slice(0, -1), canonical]
|
|
return (spec.aliases ?? []).some(
|
|
(alias) =>
|
|
alias.length === expected.length && alias.every((part, index) => part === expected[index])
|
|
)
|
|
}
|
|
|
|
export function findVocabularyViolations(specs: CommandSpec[]): VocabularyViolation[] {
|
|
const violations: VocabularyViolation[] = []
|
|
for (const spec of specs) {
|
|
const command = spec.path.join(' ')
|
|
const verb = terminalVerb(spec.path)
|
|
for (const family of FAMILIES) {
|
|
if (!family.offPolicyVerbs.has(verb)) {
|
|
continue
|
|
}
|
|
if (family.allowlist.has(command)) {
|
|
continue
|
|
}
|
|
// Why: an alias on the canonical verb makes the command reachable the
|
|
// canonical way, which satisfies the policy without a rename.
|
|
if (hasCanonicalAlias(spec, family.canonical)) {
|
|
continue
|
|
}
|
|
violations.push({ command, verb, family: family.name, canonical: family.canonical })
|
|
}
|
|
}
|
|
return violations
|
|
}
|