mirror of
https://github.com/stablyai/orca.git
synced 2026-10-23 16:03:49 +00:00
* feat(agents): add Prime Agent as a supported TUI agent with session history Wire Prime Intellect's prime-agent CLI (a Pi fork) into the desktop and mobile agent catalogs following the Trae registration pattern, and into the Agent Session History browser following the OMP pattern: - types.ts, tui-agent-config.ts: register 'prime-agent' with argv prompt injection behind a `--` separator (its own help documents `--` as "treat all following arguments as messages"; without it, prompts starting with `help`/`agents`/`-…` dispatch as subcommands or flags), plus csi-u Shift+Enter encoding matching the Pi TUI it embeds. - agent-kind.ts, telemetry-events.ts, agent-status-types.ts, agent-type-label.ts, tui-agent-display-names.ts, tui-agent-selection.ts, skills-cli-agent-keys.ts: standard per-agent registrations. - agent-headless-command.ts: `-p/--print` one-shot runs share the print-mode matcher with Claude/Trae so they are not mistaken for live interactive panes. - agent-process-recognition: the npm shim launches a generic bundled cli.js, so only the exact package path is an authoritative identity (same as Pi and cursor-agent). The three per-agent regex branches are now one table in agent-node-entrypoint-identities.ts — the module was at its max-lines budget and a table makes the next agent one entry. - AI Vault: sessions are Pi's message-graph JSONL under ~/.prime/agent/sessions (override PRIME_AGENT_CODING_AGENT_DIR — Prime Agent brands Pi's env contract instead of sharing PI_CODING_AGENT_DIR); parsed by the shared message-graph parser with incremental append-resume; discovered locally, in WSL homes, and over remote SSH; resumes by absolute transcript path (`prime-agent --resume <path>`) like OMP, with session-id fallback. - skill-discovery-sources.ts: ~/.prime/agent/skills home source. - Catalog, i18n (en/es/ja/ko/zh), mobile registries, and a bundled 64x64 favicon (required by mobile's offline-icon invariant). Scanner-test fixtures for OMP and Prime Agent move into session-scanner-test-fixtures.ts and the incremental fixture into its own module, keeping every touched file inside its max-lines budget without ratchet bumps. * fix(ai-vault): map custom Prime Agent roots to their sessions child PRIME_AGENT_CODING_AGENT_DIR is consumed verbatim by the CLI as its agent config dir, with transcripts always in <agentDir>/sessions — unlike PI_CODING_AGENT_DIR's <home>/agent/sessions shape the shared normalizer models. A custom root with a non-special basename (or a `.prime` leaf) was therefore scanned as-is instead of its sessions child. Dedicated normalizePrimeAgentSessionsDir appends `sessions` to every configured root, taking only an explicit `.../sessions` path as-is; the shared Pi/OMP normalizer drops the `.prime` widening it no longer needs. Raised in review on #12935. * fix(ai-vault): guard degenerate Prime Agent roots and cover the remote source normalizePrimeAgentSessionsDir stripped a filesystem-root value ('/' or '//') to '', which then joined into the relative root 'sessions' and would walk the main-process cwd. session-scanner-roots.ts already carries this guard for the OMP variant; apply the same fallback here. The remote SSH source had no test: deleting jsonlSource('prime-agent', ...) left the suite green, unlike the local path which is pinned by the AI_VAULT_AGENTS exhaustiveness assertion in session-scanner.test.ts. Add a case that fixes the .prime/agent/sessions root segments, the .jsonl extension, and parser routing. Raised in review on #12935. * fix(ai-vault): honor Prime Agent's sessions-root env and non-interactive modes Verified against upstream PrimeIntellect-ai/prime-agent source rather than inferred from the CLI's help text. config.ts getSessionsDir() reads PRIME_AGENT_SESSION_DIR (and its legacy PRIME_AGENT_CODING_AGENT_SESSION_DIR alias) ahead of the agent dir and uses it verbatim; setting either left the vault silently empty. It also appends `sessions` to the agent dir unconditionally, with no basename escape hatch, so PRIME_AGENT_CODING_AGENT_DIR=/data/sessions writes to /data/sessions/sessions while Orca scanned /data/sessions. getAgentDir() and the session-dir override both run through expandTildePath, so a `~` value set outside a shell resolves. cli/args.ts also spells the non-interactive runs `--mode json|rpc|acp|daemon`, which the shared print-mode matcher does not know, so those panes were counted as live interactive agents and the paste-submit path would write user text into a JSON-RPC/ACP stream. Match upstream exactly: only the space-separated form, since `--mode=json` is not parsed by the CLI and does start the TUI. Raised in review on #12935. * fix(ai-vault): keep Prime Agent roots absolute and remote segments posix Two holes in the previous commit. The degenerate-root guard only rejected pure-separator values, so a relative env value still resolved against the main-process cwd: PRIME_AGENT_CODING_AGENT_DIR='.' scanned '<cwd>/sessions' and, worse, PRIME_AGENT_SESSION_DIR='.' scanned the cwd itself. Require an absolute path in both branches and fall back to the default otherwise. remotePrimeAgentSessionsSegments() built its segments with the local-platform join, so on a Windows client scanning a posix SSH host it produced '\.prime\agent\sessions' and split('/') collapsed it to one bogus segment — remote discovery would have found nothing. Remote roots are posix regardless of client platform, so keep them literal. Pi and OMP are unaffected: their normalizer returns a '.../sessions' input unchanged and never joins. Raised in review on #12935. * test(ai-vault): pin Windows drive roots to the Prime Agent default fallback 'C:\' and 'C:/' strip to the drive-relative 'C:', which isAbsolute rejects on every platform — assert they land in the default fallback so a looser truthiness check can't reintroduce a 'C:sessions' scan root. Raised in review on #12935. * test(ai-vault): pin the drive-relative root form and state what the posix runner can assert --------- Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
100 lines
2.6 KiB
TypeScript
100 lines
2.6 KiB
TypeScript
import type { TuiAgent } from './types'
|
|
import { isTuiAgent } from './tui-agent-config'
|
|
|
|
// Keep this order in sync with the desktop agent catalog. It defines the
|
|
// automatic fallback priority when the user has not chosen a default agent.
|
|
export const TUI_AGENT_AUTO_PICK_ORDER = [
|
|
'claude',
|
|
'claude-agent-teams',
|
|
'openclaude',
|
|
'codex',
|
|
'grok',
|
|
'copilot',
|
|
'opencode',
|
|
'mimo-code',
|
|
'ante',
|
|
'trae',
|
|
'pi',
|
|
'omp',
|
|
'prime-agent',
|
|
'gemini',
|
|
'antigravity',
|
|
'aider',
|
|
'goose',
|
|
'amp',
|
|
'kilo',
|
|
'kiro',
|
|
'crush',
|
|
'aug',
|
|
'autohand',
|
|
'cline',
|
|
'codebuff',
|
|
'command-code',
|
|
'continue',
|
|
'cursor',
|
|
'droid',
|
|
'kimi',
|
|
'mistral-vibe',
|
|
'qwen-code',
|
|
'rovo',
|
|
'hermes',
|
|
'devin',
|
|
'openclaw'
|
|
] as const satisfies readonly TuiAgent[]
|
|
|
|
// Why: fresh installs should expose Claude Agent Teams in agent pickers; the
|
|
// persistence migration separately preserves the old hidden default for legacy profiles.
|
|
export const DEFAULT_DISABLED_TUI_AGENTS = [] as const satisfies readonly TuiAgent[]
|
|
|
|
export function pickTuiAgent(
|
|
preferred: TuiAgent | 'blank' | null | undefined,
|
|
detected: Iterable<TuiAgent>,
|
|
disabled?: Iterable<unknown> | null
|
|
): TuiAgent | null {
|
|
if (preferred === 'blank') {
|
|
return null
|
|
}
|
|
const disabledSet = new Set(normalizeDisabledTuiAgents(disabled))
|
|
const detectedSet = detected instanceof Set ? detected : new Set(detected)
|
|
if (preferred && detectedSet.has(preferred) && !disabledSet.has(preferred)) {
|
|
return preferred
|
|
}
|
|
for (const agent of TUI_AGENT_AUTO_PICK_ORDER) {
|
|
if (detectedSet.has(agent) && !disabledSet.has(agent)) {
|
|
return agent
|
|
}
|
|
}
|
|
return null
|
|
}
|
|
|
|
export function normalizeDisabledTuiAgents(value: unknown): TuiAgent[] {
|
|
if (!Array.isArray(value)) {
|
|
return []
|
|
}
|
|
const seen = new Set<TuiAgent>()
|
|
for (const item of value) {
|
|
if (isTuiAgent(item)) {
|
|
seen.add(item)
|
|
}
|
|
}
|
|
return [...seen]
|
|
}
|
|
|
|
export function haveSameDisabledTuiAgents(left: unknown, right: unknown): boolean {
|
|
const leftSet = new Set(normalizeDisabledTuiAgents(left))
|
|
const rightSet = new Set(normalizeDisabledTuiAgents(right))
|
|
return leftSet.size === rightSet.size && [...leftSet].every((agent) => rightSet.has(agent))
|
|
}
|
|
|
|
export function isTuiAgentEnabled(agent: TuiAgent, disabled?: Iterable<unknown> | null): boolean {
|
|
return !normalizeDisabledTuiAgents(disabled).includes(agent)
|
|
}
|
|
|
|
export function filterEnabledTuiAgents<T extends TuiAgent>(
|
|
agents: Iterable<T>,
|
|
disabled?: Iterable<unknown> | null
|
|
): T[] {
|
|
const disabledSet = new Set(normalizeDisabledTuiAgents(disabled))
|
|
return [...agents].filter((agent) => !disabledSet.has(agent))
|
|
}
|