feat(agents): add Prime Agent as a supported TUI agent with session history (#12935)
* 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>
This commit is contained in:
parent
45c1cb979a
commit
63af0bdd24
|
|
@ -10279,7 +10279,7 @@ snapshots:
|
|||
dependencies:
|
||||
'@types/yargs-parser': 21.0.3
|
||||
|
||||
'@typescript-eslint/eslint-plugin@8.59.2(@typescript-eslint/parser@8.59.2(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3)':
|
||||
'@typescript-eslint/eslint-plugin@8.59.2(@typescript-eslint/parser@8.59.2(eslint@9.39.4)(typescript@6.0.3))(eslint@9.39.4)(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@eslint-community/regexpp': 4.12.2
|
||||
'@typescript-eslint/parser': 8.59.2(eslint@9.39.4)(typescript@6.0.3)
|
||||
|
|
@ -10299,7 +10299,7 @@ snapshots:
|
|||
dependencies:
|
||||
'@typescript-eslint/scope-manager': 8.59.2
|
||||
'@typescript-eslint/types': 8.59.2
|
||||
'@typescript-eslint/typescript-estree': 8.59.2(typescript@5.9.3)
|
||||
'@typescript-eslint/typescript-estree': 8.59.2(typescript@6.0.3)
|
||||
'@typescript-eslint/visitor-keys': 8.59.2
|
||||
debug: 4.4.3
|
||||
eslint: 9.39.4
|
||||
|
|
@ -10316,6 +10316,15 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/project-service@8.59.2(typescript@6.0.3)':
|
||||
dependencies:
|
||||
'@typescript-eslint/tsconfig-utils': 8.59.2(typescript@6.0.3)
|
||||
'@typescript-eslint/types': 8.59.2
|
||||
debug: 4.4.3
|
||||
typescript: 6.0.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/scope-manager@8.59.2':
|
||||
dependencies:
|
||||
'@typescript-eslint/types': 8.59.2
|
||||
|
|
@ -10325,6 +10334,10 @@ snapshots:
|
|||
dependencies:
|
||||
typescript: 5.9.3
|
||||
|
||||
'@typescript-eslint/tsconfig-utils@8.59.2(typescript@6.0.3)':
|
||||
dependencies:
|
||||
typescript: 6.0.3
|
||||
|
||||
'@typescript-eslint/type-utils@8.59.2(eslint@9.39.4)(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@typescript-eslint/types': 8.59.2
|
||||
|
|
@ -10354,6 +10367,21 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/typescript-estree@8.59.2(typescript@6.0.3)':
|
||||
dependencies:
|
||||
'@typescript-eslint/project-service': 8.59.2(typescript@6.0.3)
|
||||
'@typescript-eslint/tsconfig-utils': 8.59.2(typescript@6.0.3)
|
||||
'@typescript-eslint/types': 8.59.2
|
||||
'@typescript-eslint/visitor-keys': 8.59.2
|
||||
debug: 4.4.3
|
||||
minimatch: 10.2.5
|
||||
semver: 7.7.4
|
||||
tinyglobby: 0.2.17
|
||||
ts-api-utils: 2.5.0(typescript@6.0.3)
|
||||
typescript: 6.0.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@typescript-eslint/utils@8.59.2(eslint@9.39.4)(typescript@5.9.3)':
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4)
|
||||
|
|
@ -11633,11 +11661,11 @@ snapshots:
|
|||
|
||||
eslint-config-universe@15.0.4(eslint@9.39.4)(prettier@2.8.8)(typescript@5.9.3):
|
||||
dependencies:
|
||||
'@typescript-eslint/eslint-plugin': 8.59.2(@typescript-eslint/parser@8.59.2(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3)
|
||||
'@typescript-eslint/eslint-plugin': 8.59.2(@typescript-eslint/parser@8.59.2(eslint@9.39.4)(typescript@6.0.3))(eslint@9.39.4)(typescript@5.9.3)
|
||||
'@typescript-eslint/parser': 8.59.2(eslint@9.39.4)(typescript@6.0.3)
|
||||
eslint: 9.39.4
|
||||
eslint-config-prettier: 9.1.2(eslint@9.39.4)
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)
|
||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4)(typescript@6.0.3))(eslint@9.39.4)
|
||||
eslint-plugin-n: 17.24.0(eslint@9.39.4)(typescript@5.9.3)
|
||||
eslint-plugin-node: 11.1.0(eslint@9.39.4)
|
||||
eslint-plugin-prettier: 5.5.5(eslint-config-prettier@9.1.2(eslint@9.39.4))(eslint@9.39.4)(prettier@2.8.8)
|
||||
|
|
@ -11661,7 +11689,7 @@ snapshots:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-module-utils@2.12.1(@typescript-eslint/parser@8.59.2(eslint@9.39.4)(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint@9.39.4):
|
||||
eslint-module-utils@2.12.1(@typescript-eslint/parser@8.59.2(eslint@9.39.4)(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@9.39.4):
|
||||
dependencies:
|
||||
debug: 3.2.7
|
||||
optionalDependencies:
|
||||
|
|
@ -11684,7 +11712,7 @@ snapshots:
|
|||
eslint-utils: 2.1.0
|
||||
regexpp: 3.2.0
|
||||
|
||||
eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4):
|
||||
eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.59.2(eslint@9.39.4)(typescript@6.0.3))(eslint@9.39.4):
|
||||
dependencies:
|
||||
'@rtsao/scc': 1.1.0
|
||||
array-includes: 3.1.9
|
||||
|
|
@ -11695,7 +11723,7 @@ snapshots:
|
|||
doctrine: 2.1.0
|
||||
eslint: 9.39.4
|
||||
eslint-import-resolver-node: 0.3.10
|
||||
eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.59.2(eslint@9.39.4)(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint@9.39.4)
|
||||
eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.59.2(eslint@9.39.4)(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@9.39.4)
|
||||
hasown: 2.0.3
|
||||
is-core-module: 2.16.2
|
||||
is-glob: 4.0.3
|
||||
|
|
@ -14948,6 +14976,10 @@ snapshots:
|
|||
dependencies:
|
||||
typescript: 5.9.3
|
||||
|
||||
ts-api-utils@2.5.0(typescript@6.0.3):
|
||||
dependencies:
|
||||
typescript: 6.0.3
|
||||
|
||||
ts-declaration-location@1.0.7(typescript@5.9.3):
|
||||
dependencies:
|
||||
picomatch: 4.0.4
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ export const MOBILE_AGENT_ICON_ASSETS: Partial<Record<TuiAgent, ImageSourcePropT
|
|||
'mimo-code': require('../../../src/shared/agent-icons/mimo-code.png'),
|
||||
ante: require('../../../src/shared/agent-icons/ante.png'),
|
||||
trae: require('../../../src/shared/agent-icons/trae.png'),
|
||||
'prime-agent': require('../../../src/shared/agent-icons/prime-agent.png'),
|
||||
gemini: require('../../../src/shared/agent-icons/gemini.png'),
|
||||
antigravity: require('../../../src/shared/agent-icons/antigravity.png'),
|
||||
goose: require('../../../src/shared/agent-icons/goose.png'),
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ export const MOBILE_TUI_AGENT_AUTO_PICK_ORDER = [
|
|||
'trae',
|
||||
'pi',
|
||||
'omp',
|
||||
'prime-agent',
|
||||
'gemini',
|
||||
'antigravity',
|
||||
'aider',
|
||||
|
|
@ -54,6 +55,7 @@ export const MOBILE_TUI_AGENT_LABELS: Record<TuiAgent, string> = {
|
|||
trae: 'Trae',
|
||||
pi: 'Pi',
|
||||
omp: 'OMP',
|
||||
'prime-agent': 'Prime Agent',
|
||||
gemini: 'Gemini',
|
||||
antigravity: 'Antigravity',
|
||||
aider: 'Aider',
|
||||
|
|
@ -88,6 +90,7 @@ export const MOBILE_TUI_AGENT_FAVICON_DOMAINS: Partial<Record<TuiAgent, string>>
|
|||
ante: 'antigma.ai',
|
||||
trae: 'www.trae.cn',
|
||||
omp: 'omp.sh',
|
||||
'prime-agent': 'primeintellect.ai',
|
||||
gemini: 'gemini.google.com',
|
||||
antigravity: 'antigravity.google',
|
||||
goose: 'goose-docs.ai',
|
||||
|
|
|
|||
|
|
@ -101,6 +101,13 @@ export function remoteSessionSources(
|
|||
// partition keeps the children out of the top-level list (#9330).
|
||||
partitionSubagentTranscripts: partitionOmpSubagentTranscriptPaths
|
||||
},
|
||||
jsonlSource(
|
||||
'prime-agent',
|
||||
remoteHome,
|
||||
hostPlatform,
|
||||
remotePrimeAgentSessionsSegments(),
|
||||
primeAgentParser
|
||||
),
|
||||
jsonlSource(
|
||||
'droid',
|
||||
remoteHome,
|
||||
|
|
@ -264,6 +271,16 @@ function ompParser(
|
|||
return parseMessageGraphSessionContent('omp', file, content, platform, options, signal)
|
||||
}
|
||||
|
||||
function primeAgentParser(
|
||||
file: FileWithMtime,
|
||||
content: string,
|
||||
platform: NodeJS.Platform,
|
||||
options: RemoteParserOptions,
|
||||
signal?: AbortSignal
|
||||
): Promise<AiVaultSession | null> {
|
||||
return parseMessageGraphSessionContent('prime-agent', file, content, platform, options, signal)
|
||||
}
|
||||
|
||||
function openClawParser(
|
||||
file: FileWithMtime,
|
||||
content: string,
|
||||
|
|
@ -285,3 +302,10 @@ function remotePiSessionsSegments(): string[] {
|
|||
function remoteOmpSessionsSegments(): string[] {
|
||||
return normalizeAgentSessionsDir('/.omp/agent/sessions', '.omp').split('/').filter(Boolean)
|
||||
}
|
||||
|
||||
// Why: remote roots are posix regardless of the client platform, so these stay literal
|
||||
// rather than round-tripping through a local-platform path join that would emit
|
||||
// backslashes on a Windows client and collapse into a single bogus segment.
|
||||
function remotePrimeAgentSessionsSegments(): string[] {
|
||||
return ['.prime', 'agent', 'sessions']
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { describe, expect, it } from 'vitest'
|
|||
import { getRemoteHostPlatform } from '../ssh/ssh-remote-platform'
|
||||
import { scanRemoteAiVaultSessions } from './remote-session-scanner'
|
||||
import { MemoryRemoteProvider, jsonLines } from './remote-session-scanner-test-fixtures'
|
||||
import { primeAgentFixture } from './session-scanner-prime-agent-fixtures'
|
||||
|
||||
describe('scanRemoteAiVaultSessions', () => {
|
||||
it('parses remote default and Orca-managed Codex homes with SSH host ids', async () => {
|
||||
|
|
@ -160,6 +161,34 @@ describe('scanRemoteAiVaultSessions', () => {
|
|||
})
|
||||
})
|
||||
|
||||
it('discovers Prime Agent transcripts under the remote home sessions root', async () => {
|
||||
const provider = new MemoryRemoteProvider()
|
||||
const fixture = primeAgentFixture()
|
||||
provider.addFile(
|
||||
`/home/ada/.prime/agent/sessions/${fixture.fileName}`,
|
||||
[...fixture.seedLines, ...fixture.appendLines].join('\n'),
|
||||
40
|
||||
)
|
||||
|
||||
const result = await scanRemoteAiVaultSessions({
|
||||
provider,
|
||||
executionHostId: 'ssh:dev-box',
|
||||
remoteHome: '/home/ada',
|
||||
hostPlatform: getRemoteHostPlatform('linux-x64')
|
||||
})
|
||||
|
||||
expect(result.issues).toEqual([])
|
||||
expect(result.sessions).toHaveLength(1)
|
||||
expect(result.sessions[0]).toMatchObject({
|
||||
executionHostId: 'ssh:dev-box',
|
||||
executionHostPlatform: 'linux',
|
||||
agent: 'prime-agent',
|
||||
title: 'prime-agent seed question',
|
||||
model: 'inference/big-model',
|
||||
filePath: `/home/ada/.prime/agent/sessions/${fixture.fileName}`
|
||||
})
|
||||
})
|
||||
|
||||
it('parses only canonical Antigravity transcripts on SSH hosts', async () => {
|
||||
const provider = new MemoryRemoteProvider()
|
||||
const sessionId = 'aaaaaaaa-bbbb-4ccc-8ddd-eeeeeeeeeeee'
|
||||
|
|
|
|||
|
|
@ -68,6 +68,8 @@ export async function parseAgentSessionFile(
|
|||
return parseMessageGraphSessionFile('pi', candidate.file, platform)
|
||||
case 'omp':
|
||||
return parseMessageGraphSessionFile('omp', candidate.file, platform)
|
||||
case 'prime-agent':
|
||||
return parseMessageGraphSessionFile('prime-agent', candidate.file, platform)
|
||||
case 'droid':
|
||||
return parseDroidSessionFile(candidate.file, platform)
|
||||
case 'devin':
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ import { OMP_SESSION_ARTIFACT_DIR_PATTERN } from './session-scanner-omp-subagent
|
|||
import { claudeProjectsRootDirs, OMP_SESSIONS_DIR, sessionRootDirs } from './session-scanner-roots'
|
||||
import { SUBAGENT_DIR_NAME } from './session-scanner-subagent-transcripts'
|
||||
import type { AiVaultScanOptions } from './session-scanner-types'
|
||||
import { normalizeAgentSessionsDir } from './session-scanner-values'
|
||||
import { normalizeAgentSessionsDir, primeAgentSessionsDirFromEnv } from './session-scanner-values'
|
||||
|
||||
export const DEFAULT_CODEX_HOME_DIR = join(homedir(), '.codex')
|
||||
const CODEX_SESSIONS_DIR = join(
|
||||
|
|
@ -29,6 +29,10 @@ const PI_SESSIONS_DIR = normalizeAgentSessionsDir(
|
|||
process.env.PI_CODING_AGENT_DIR?.trim() || join(homedir(), '.pi', 'agent', 'sessions'),
|
||||
'.pi'
|
||||
)
|
||||
// Why: Prime Agent brands Pi's env contract with its own prefix and adds a
|
||||
// dedicated sessions-root override, so resolution differs from Pi/OMP in shape
|
||||
// as well as in variable name.
|
||||
const PRIME_AGENT_SESSIONS_DIR = primeAgentSessionsDirFromEnv()
|
||||
// Why: Devin ATIF transcripts are stored under <DEVIN_HOME>/transcripts.
|
||||
const DEVIN_TRANSCRIPTS_DIR = join(
|
||||
process.env.DEVIN_HOME?.trim() || join(homedir(), '.local', 'share', 'devin', 'cli'),
|
||||
|
|
@ -180,6 +184,15 @@ export const AI_VAULT_AGENT_SOURCES: AiVaultAgentSourceTable = {
|
|||
// Depth 0 is the workspace dir, which is never an artifact dir.
|
||||
directoryPredicate: (name, depth) => depth === 0 || !OMP_SESSION_ARTIFACT_DIR_PATTERN.test(name)
|
||||
},
|
||||
'prime-agent': {
|
||||
rootDirs: (options, wslHomeDirs) =>
|
||||
sessionRootDirs(options.primeAgentSessionsDir ?? PRIME_AGENT_SESSIONS_DIR, wslHomeDirs, [
|
||||
'.prime',
|
||||
'agent',
|
||||
'sessions'
|
||||
]),
|
||||
extensions: ['.jsonl']
|
||||
},
|
||||
openclaw: {
|
||||
// Sessions live under <stateDir>/agents; a stateDir already ending in
|
||||
// `agents` is used as-is. The current and legacy state dirs are the same
|
||||
|
|
|
|||
|
|
@ -119,6 +119,7 @@ describe('scanAiVaultSessions Codex worker sessions', () => {
|
|||
openclawLegacyStateDir: join(root, 'openclaw-legacy-state'),
|
||||
piSessionsDir: join(root, 'pi-sessions'),
|
||||
ompSessionsDir: join(root, 'omp-sessions'),
|
||||
primeAgentSessionsDir: join(root, 'prime-agent-sessions'),
|
||||
droidSessionsDir: join(root, 'droid-sessions'),
|
||||
droidProjectsDir: join(root, 'droid-projects'),
|
||||
kimiSessionsDir: join(root, 'kimi-sessions'),
|
||||
|
|
|
|||
|
|
@ -166,8 +166,9 @@ export function rovoPartsText(parts: unknown[], role: 'user' | 'assistant'): str
|
|||
}
|
||||
|
||||
// Agents whose transcripts are append-only message-graph JSONL (session +
|
||||
// model_change + message records). OMP is a Pi fork and shares the format.
|
||||
export type MessageGraphAgent = 'openclaw' | 'pi' | 'omp'
|
||||
// model_change + message records). OMP and Prime Agent are Pi forks and
|
||||
// share the format.
|
||||
export type MessageGraphAgent = 'openclaw' | 'pi' | 'omp' | 'prime-agent'
|
||||
|
||||
export async function parseMessageGraphSessionFile(
|
||||
agent: MessageGraphAgent,
|
||||
|
|
@ -246,7 +247,7 @@ export function createMessageGraphSessionResumeState(
|
|||
)
|
||||
// Why: only OMP materializes task-subagent transcripts beside its sessions
|
||||
// (in the same-named artifact dir); the row UI shows the count without
|
||||
// expanding details. Pi/OpenClaw have no such layout — skip the readdir.
|
||||
// expanding details. Pi/OpenClaw/Prime Agent have no such layout — skip the readdir.
|
||||
return agent === 'omp' ? withOmpSubagentTranscriptCount(state, file.path) : state
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import type { AiVaultAgent } from '../../shared/ai-vault-types'
|
|||
|
||||
import { antigravityFixture } from './session-scanner-antigravity-fixtures'
|
||||
import { codexFixture } from './session-scanner-codex-fixtures'
|
||||
import { primeAgentFixture } from './session-scanner-prime-agent-fixtures'
|
||||
|
||||
// Line builders for the incremental-parse differential tests: each agent gets
|
||||
// a seed transcript, an appended continuation, and a truncated rewrite, all in
|
||||
|
|
@ -287,6 +288,7 @@ export function allIncrementalAgentFixtures(): IncrementalAgentFixture[] {
|
|||
openclawFixture(),
|
||||
piFixture(),
|
||||
ompFixture(),
|
||||
primeAgentFixture(),
|
||||
geminiJsonlFixture(),
|
||||
antigravityFixture()
|
||||
]
|
||||
|
|
|
|||
|
|
@ -51,7 +51,8 @@ function isolatedScanRoots(root: string) {
|
|||
droidSessionsDir: join(root, 'droid-sessions'),
|
||||
droidProjectsDir: join(root, 'droid-projects'),
|
||||
kimiSessionsDir: join(root, 'kimi-sessions'),
|
||||
ompSessionsDir: join(root, 'omp-sessions')
|
||||
ompSessionsDir: join(root, 'omp-sessions'),
|
||||
primeAgentSessionsDir: join(root, 'prime-agent-sessions')
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -60,7 +60,8 @@ function resumableStateFactoryFor(
|
|||
return () => createDroidSessionResumeState(candidate.file)
|
||||
case 'openclaw':
|
||||
case 'pi':
|
||||
case 'omp': {
|
||||
case 'omp':
|
||||
case 'prime-agent': {
|
||||
const agent = candidate.agent
|
||||
return () => createMessageGraphSessionResumeState(agent, candidate.file)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
import type { IncrementalAgentFixture } from './session-scanner-incremental-fixtures'
|
||||
|
||||
// Prime Agent is a Pi fork sharing the message-graph format and Pi's
|
||||
// `modelId` key; its own fixture exercises the registry's 'prime-agent'
|
||||
// branch with the v3 tree-session header its releases write.
|
||||
export function primeAgentFixture(): IncrementalAgentFixture {
|
||||
return {
|
||||
agent: 'prime-agent',
|
||||
fileName: 'dddddddd-eeee-4fff-8aaa-111111111111.jsonl',
|
||||
seedLines: [
|
||||
JSON.stringify({
|
||||
type: 'session',
|
||||
version: 3,
|
||||
id: 'dddddddd-eeee-4fff-8aaa-111111111111',
|
||||
cwd: '/repo/app',
|
||||
timestamp: '2026-05-01T10:00:00.000Z'
|
||||
}),
|
||||
JSON.stringify({
|
||||
type: 'model_change',
|
||||
provider: 'prime-intellect',
|
||||
modelId: 'inference/big-model',
|
||||
timestamp: '2026-05-01T10:00:01.000Z'
|
||||
}),
|
||||
JSON.stringify({
|
||||
type: 'message',
|
||||
message: { role: 'user', content: [{ type: 'text', text: 'prime-agent seed question' }] },
|
||||
timestamp: '2026-05-01T10:00:05.000Z'
|
||||
})
|
||||
],
|
||||
appendLines: [
|
||||
JSON.stringify({
|
||||
type: 'message',
|
||||
message: {
|
||||
role: 'assistant',
|
||||
content: [{ type: 'text', text: 'prime-agent incremental answer' }],
|
||||
model: 'inference/big-model',
|
||||
usage: { input: 30, output: 10, totalTokens: 40 }
|
||||
},
|
||||
timestamp: '2026-05-01T10:01:00.000Z'
|
||||
})
|
||||
],
|
||||
truncatedLines: [
|
||||
JSON.stringify({
|
||||
type: 'session',
|
||||
id: 'dddddddd-eeee-4fff-8aaa-111111111111',
|
||||
timestamp: '2026-05-01T10:00:00.000Z'
|
||||
})
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -21,6 +21,7 @@ export function isolatedScanRoots(root: string) {
|
|||
openclawLegacyStateDir: join(root, 'openclaw-legacy-state'),
|
||||
piSessionsDir: join(root, 'pi-sessions'),
|
||||
ompSessionsDir: join(root, 'omp-sessions'),
|
||||
primeAgentSessionsDir: join(root, 'prime-agent-sessions'),
|
||||
droidSessionsDir: join(root, 'droid-sessions'),
|
||||
droidProjectsDir: join(root, 'droid-projects'),
|
||||
kimiSessionsDir: join(root, 'kimi-sessions')
|
||||
|
|
@ -50,6 +51,72 @@ export function writeAntigravityHistory(brainDir: string, records: unknown[]): P
|
|||
return writeJsonlFile(join(dirname(brainDir), 'history.jsonl'), records)
|
||||
}
|
||||
|
||||
// Message-graph fixtures for the Pi forks: each writes one session transcript
|
||||
// and returns its path, since both agents resume by absolute transcript path.
|
||||
export async function writeOmpScannerFixture(sessionsDir: string): Promise<string> {
|
||||
const sessionFile = join(sessionsDir, 'omp-session.jsonl')
|
||||
await writeJsonlFile(sessionFile, [
|
||||
{
|
||||
type: 'session',
|
||||
version: 3,
|
||||
id: 'omp-session',
|
||||
title: 'OMP session title',
|
||||
timestamp: '2026-05-01T10:08:30.000Z',
|
||||
cwd: '/tmp/omp'
|
||||
},
|
||||
{
|
||||
type: 'model_change',
|
||||
model: 'gpt-5.4-mini',
|
||||
timestamp: '2026-05-01T10:08:30.500Z'
|
||||
},
|
||||
{
|
||||
type: 'message',
|
||||
timestamp: '2026-05-01T10:08:31.000Z',
|
||||
message: { role: 'user', content: [{ type: 'text', text: 'OMP title' }] }
|
||||
},
|
||||
{
|
||||
type: 'message',
|
||||
timestamp: '2026-05-01T10:08:32.000Z',
|
||||
message: {
|
||||
role: 'assistant',
|
||||
content: [{ type: 'text', text: 'OMP answer' }],
|
||||
model: 'gpt-5.4-mini',
|
||||
// totalTokens deliberately != input+output so the assertion proves
|
||||
// the explicit-total field is read, not an input/output sum.
|
||||
usage: { input: 10, output: 5, totalTokens: 160 }
|
||||
}
|
||||
}
|
||||
])
|
||||
return sessionFile
|
||||
}
|
||||
|
||||
// Prime Agent shares Pi's message-graph format (and its `modelId` key) but
|
||||
// reads its own ~/.prime/agent/sessions root.
|
||||
export async function writePrimeAgentScannerFixture(sessionsDir: string): Promise<string> {
|
||||
const sessionFile = join(sessionsDir, 'prime-agent-session.jsonl')
|
||||
await writeJsonlFile(sessionFile, [
|
||||
{
|
||||
type: 'session',
|
||||
version: 3,
|
||||
id: 'prime-agent-session',
|
||||
timestamp: '2026-05-01T10:08:40.000Z',
|
||||
cwd: '/tmp/prime-agent'
|
||||
},
|
||||
{
|
||||
type: 'model_change',
|
||||
provider: 'prime-intellect',
|
||||
modelId: 'inference/big-model',
|
||||
timestamp: '2026-05-01T10:08:40.500Z'
|
||||
},
|
||||
{
|
||||
type: 'message',
|
||||
timestamp: '2026-05-01T10:08:41.000Z',
|
||||
message: { role: 'user', content: [{ type: 'text', text: 'Prime Agent title' }] }
|
||||
}
|
||||
])
|
||||
return sessionFile
|
||||
}
|
||||
|
||||
export function writeAntigravityScannerFixture(
|
||||
brainDir: string,
|
||||
sessionId: string
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ export type AiVaultScanOptions = {
|
|||
openclawLegacyStateDir?: string
|
||||
piSessionsDir?: string
|
||||
ompSessionsDir?: string
|
||||
primeAgentSessionsDir?: string
|
||||
droidSessionsDir?: string
|
||||
droidProjectsDir?: string
|
||||
kimiSessionsDir?: string
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
import { homedir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import {
|
||||
extractFullFirstUserPromptText,
|
||||
extractPreviewContentText,
|
||||
normalizeAgentSessionsDir,
|
||||
normalizeFullFirstUserPromptText,
|
||||
normalizePrimeAgentSessionsDir,
|
||||
primeAgentSessionsDirFromEnv,
|
||||
normalizePreviewText,
|
||||
normalizeTitleText
|
||||
} from './session-scanner-values'
|
||||
|
|
@ -87,4 +91,97 @@ describe('AI Vault session scanner text values', () => {
|
|||
'/agents/.omp/agent/sessions'
|
||||
)
|
||||
})
|
||||
|
||||
// Prime Agent's env var is its agent dir verbatim and the CLI writes to
|
||||
// `<agentDir>/sessions`, so every configured dir maps to that child.
|
||||
it('maps any Prime Agent agent dir to its sessions child', () => {
|
||||
expect(normalizePrimeAgentSessionsDir('/tmp/prime-agent')).toBe('/tmp/prime-agent/sessions')
|
||||
expect(normalizePrimeAgentSessionsDir('/tmp/prime-agent///')).toBe('/tmp/prime-agent/sessions')
|
||||
expect(normalizePrimeAgentSessionsDir('/agents/.prime')).toBe('/agents/.prime/sessions')
|
||||
expect(normalizePrimeAgentSessionsDir('/agents/.prime/agent')).toBe(
|
||||
'/agents/.prime/agent/sessions'
|
||||
)
|
||||
})
|
||||
|
||||
// Why: the CLI appends `sessions` unconditionally, so an agent dir that is itself
|
||||
// named `sessions` nests one deeper rather than being taken as the transcripts root.
|
||||
it('still appends sessions when the agent dir is itself named sessions', () => {
|
||||
expect(normalizePrimeAgentSessionsDir('/data/sessions')).toBe('/data/sessions/sessions')
|
||||
})
|
||||
|
||||
it('expands a leading tilde in the agent dir', () => {
|
||||
expect(normalizePrimeAgentSessionsDir('~/work/prime')).toBe(
|
||||
join(homedir(), 'work', 'prime', 'sessions')
|
||||
)
|
||||
})
|
||||
|
||||
// Why: any non-absolute root would resolve against the main-process cwd. The
|
||||
// drive-shaped values ('C:foo' is the form that resolves against a per-drive
|
||||
// cwd on Windows) are non-absolute on posix too, so they assert the same
|
||||
// fallback here; real win32 semantics cannot be pinned on a posix runner.
|
||||
it('falls back to the default root for every non-absolute agent dir', () => {
|
||||
const fallback = join(homedir(), '.prime', 'agent', 'sessions')
|
||||
for (const value of [
|
||||
'/',
|
||||
'//',
|
||||
' ',
|
||||
'',
|
||||
'.',
|
||||
'..',
|
||||
'sessions',
|
||||
'rel/path',
|
||||
'C:\\',
|
||||
'C:/',
|
||||
'C:foo'
|
||||
]) {
|
||||
expect(normalizePrimeAgentSessionsDir(value)).toBe(fallback)
|
||||
}
|
||||
})
|
||||
|
||||
describe('primeAgentSessionsDirFromEnv', () => {
|
||||
const defaultDir = join(homedir(), '.prime', 'agent', 'sessions')
|
||||
|
||||
it('defaults to the home agent dir when nothing is configured', () => {
|
||||
expect(primeAgentSessionsDirFromEnv({})).toBe(defaultDir)
|
||||
})
|
||||
|
||||
it('appends sessions to a configured agent dir', () => {
|
||||
expect(primeAgentSessionsDirFromEnv({ PRIME_AGENT_CODING_AGENT_DIR: '/opt/prime' })).toBe(
|
||||
'/opt/prime/sessions'
|
||||
)
|
||||
})
|
||||
|
||||
// Why: upstream reads the sessions-root overrides before the agent dir and uses
|
||||
// them verbatim; ignoring them left the vault silently empty.
|
||||
it('prefers the sessions-root overrides verbatim over the agent dir', () => {
|
||||
expect(
|
||||
primeAgentSessionsDirFromEnv({
|
||||
PRIME_AGENT_CODING_AGENT_DIR: '/opt/prime',
|
||||
PRIME_AGENT_SESSION_DIR: '/mnt/transcripts'
|
||||
})
|
||||
).toBe('/mnt/transcripts')
|
||||
expect(
|
||||
primeAgentSessionsDirFromEnv({
|
||||
PRIME_AGENT_CODING_AGENT_DIR: '/opt/prime',
|
||||
PRIME_AGENT_CODING_AGENT_SESSION_DIR: '/mnt/legacy'
|
||||
})
|
||||
).toBe('/mnt/legacy')
|
||||
expect(
|
||||
primeAgentSessionsDirFromEnv({
|
||||
PRIME_AGENT_SESSION_DIR: '/mnt/wins',
|
||||
PRIME_AGENT_CODING_AGENT_SESSION_DIR: '/mnt/legacy'
|
||||
})
|
||||
).toBe('/mnt/wins')
|
||||
})
|
||||
|
||||
it('expands a tilde and rejects a non-absolute sessions-root override', () => {
|
||||
expect(primeAgentSessionsDirFromEnv({ PRIME_AGENT_SESSION_DIR: '~/t' })).toBe(
|
||||
join(homedir(), 't')
|
||||
)
|
||||
// Why: '.' would otherwise scan the main-process cwd outright.
|
||||
for (const value of ['/', '.', '..', 'rel/path', ' ', 'C:foo']) {
|
||||
expect(primeAgentSessionsDirFromEnv({ PRIME_AGENT_SESSION_DIR: value })).toBe(defaultDir)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { homedir } from 'node:os'
|
||||
import { basename, dirname, join } from 'node:path'
|
||||
import { basename, dirname, isAbsolute, join } from 'node:path'
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { asRecord } from './session-scanner-record-value'
|
||||
|
||||
|
|
@ -153,6 +153,41 @@ export function normalizeAgentSessionsDir(
|
|||
return normalized
|
||||
}
|
||||
|
||||
function defaultPrimeAgentSessionsDir(): string {
|
||||
return join(homedir(), '.prime', 'agent', 'sessions')
|
||||
}
|
||||
|
||||
// Why: the CLI expands a leading `~` itself, so a value set outside a shell
|
||||
// (config file, plist, quoted assignment) still resolves against the home dir.
|
||||
// Returns null for anything that is not an absolute root, since a relative value
|
||||
// ('', '.', '..', 'sessions') would resolve against the main-process cwd.
|
||||
function absoluteConfiguredDir(rawValue: string): string | null {
|
||||
const expanded = rawValue === '~' ? homedir() : rawValue.replace(/^~(?=[\\/])/, homedir())
|
||||
const normalized = expanded.replace(/[\\/]+$/, '')
|
||||
return normalized && isAbsolute(normalized) ? normalized : null
|
||||
}
|
||||
|
||||
// Prime Agent takes PRIME_AGENT_CODING_AGENT_DIR verbatim as its agent config dir
|
||||
// (no `/agent` suffixing) and always writes transcripts to `<agentDir>/sessions` —
|
||||
// unconditionally, so a root that is itself named `sessions` still nests one deeper.
|
||||
export function normalizePrimeAgentSessionsDir(rawAgentDir: string): string {
|
||||
const agentDir = absoluteConfiguredDir(rawAgentDir.trim())
|
||||
return agentDir ? join(agentDir, 'sessions') : defaultPrimeAgentSessionsDir()
|
||||
}
|
||||
|
||||
// PRIME_AGENT_SESSION_DIR (and its legacy PRIME_AGENT_CODING_AGENT_SESSION_DIR alias)
|
||||
// point straight at the transcripts root and outrank the agent dir upstream, so they
|
||||
// are used verbatim with no `sessions` child.
|
||||
export function primeAgentSessionsDirFromEnv(env: NodeJS.ProcessEnv = process.env): string {
|
||||
const sessionDir =
|
||||
env.PRIME_AGENT_SESSION_DIR?.trim() || env.PRIME_AGENT_CODING_AGENT_SESSION_DIR?.trim()
|
||||
if (sessionDir) {
|
||||
return absoluteConfiguredDir(sessionDir) ?? defaultPrimeAgentSessionsDir()
|
||||
}
|
||||
const agentDir = env.PRIME_AGENT_CODING_AGENT_DIR?.trim()
|
||||
return agentDir ? normalizePrimeAgentSessionsDir(agentDir) : defaultPrimeAgentSessionsDir()
|
||||
}
|
||||
|
||||
export function clampPositiveInteger(value: number | undefined, fallback: number): number {
|
||||
return typeof value === 'number' && Number.isInteger(value) && value > 0 ? value : fallback
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@ import { scanAiVaultSessions } from './session-scanner'
|
|||
import {
|
||||
isolatedScanRoots,
|
||||
jsonLines,
|
||||
writeAntigravityScannerFixture
|
||||
writeAntigravityScannerFixture,
|
||||
writeOmpScannerFixture,
|
||||
writePrimeAgentScannerFixture
|
||||
} from './session-scanner-test-fixtures'
|
||||
|
||||
let tempRoots: string[] = []
|
||||
|
|
@ -595,43 +597,8 @@ describe('scanAiVaultSessions', () => {
|
|||
])
|
||||
)
|
||||
|
||||
const ompSessionFile = join(roots.ompSessionsDir, 'omp-session.jsonl')
|
||||
await mkdir(roots.ompSessionsDir, { recursive: true })
|
||||
await writeFile(
|
||||
ompSessionFile,
|
||||
jsonLines([
|
||||
{
|
||||
type: 'session',
|
||||
version: 3,
|
||||
id: 'omp-session',
|
||||
title: 'OMP session title',
|
||||
timestamp: '2026-05-01T10:08:30.000Z',
|
||||
cwd: '/tmp/omp'
|
||||
},
|
||||
{
|
||||
type: 'model_change',
|
||||
model: 'gpt-5.4-mini',
|
||||
timestamp: '2026-05-01T10:08:30.500Z'
|
||||
},
|
||||
{
|
||||
type: 'message',
|
||||
timestamp: '2026-05-01T10:08:31.000Z',
|
||||
message: { role: 'user', content: [{ type: 'text', text: 'OMP title' }] }
|
||||
},
|
||||
{
|
||||
type: 'message',
|
||||
timestamp: '2026-05-01T10:08:32.000Z',
|
||||
message: {
|
||||
role: 'assistant',
|
||||
content: [{ type: 'text', text: 'OMP answer' }],
|
||||
model: 'gpt-5.4-mini',
|
||||
// totalTokens deliberately != input+output so the assertion proves
|
||||
// the explicit-total field is read, not an input/output sum.
|
||||
usage: { input: 10, output: 5, totalTokens: 160 }
|
||||
}
|
||||
}
|
||||
])
|
||||
)
|
||||
const ompSessionFile = await writeOmpScannerFixture(roots.ompSessionsDir)
|
||||
const primeAgentSessionFile = await writePrimeAgentScannerFixture(roots.primeAgentSessionsDir)
|
||||
|
||||
await mkdir(roots.devinTranscriptsDir, { recursive: true })
|
||||
await writeFile(
|
||||
|
|
@ -767,6 +734,10 @@ describe('scanAiVaultSessions', () => {
|
|||
expect(commandByAgent.get('pi')).toBe("cd '/tmp/pi' && pi --session 'pi-session'")
|
||||
// OMP resumes by absolute transcript path, not by internal session id.
|
||||
expect(commandByAgent.get('omp')).toBe(`cd '/tmp/omp' && omp --resume '${ompSessionFile}'`)
|
||||
// Prime Agent's `--resume <path|id>` takes the same absolute-path form as OMP.
|
||||
expect(commandByAgent.get('prime-agent')).toBe(
|
||||
`cd '/tmp/prime-agent' && prime-agent --resume '${primeAgentSessionFile}'`
|
||||
)
|
||||
expect(commandByAgent.get('devin')).toBe("cd '/tmp/devin' && devin --resume 'devin-session'")
|
||||
expect(commandByAgent.get('droid')).toBe("cd '/tmp/droid' && droid --resume 'droid-session'")
|
||||
expect(commandByAgent.get('kimi')).toBe(
|
||||
|
|
@ -776,6 +747,12 @@ describe('scanAiVaultSessions', () => {
|
|||
const ompSession = result.sessions.find((session) => session.agent === 'omp')
|
||||
expect(ompSession?.model).toBe('gpt-5.4-mini')
|
||||
expect(ompSession?.totalTokens).toBe(160)
|
||||
|
||||
// Prime Agent keeps Pi's `model_change.modelId` key, so the pre-reply model
|
||||
// must come through even though no assistant message was written yet.
|
||||
const primeAgentSession = result.sessions.find((session) => session.agent === 'prime-agent')
|
||||
expect(primeAgentSession?.model).toBe('inference/big-model')
|
||||
expect(primeAgentSession?.title).toBe('Prime Agent title')
|
||||
})
|
||||
|
||||
it('captures an in-progress OMP model from model_change before any assistant reply', async () => {
|
||||
|
|
|
|||
|
|
@ -139,6 +139,14 @@ export function buildSkillDiscoverySources(
|
|||
['agent-skills'],
|
||||
'omp'
|
||||
),
|
||||
source(
|
||||
'home-prime-agent',
|
||||
'Prime Agent home',
|
||||
pathApi.join(home, '.prime', 'agent', 'skills'),
|
||||
'home',
|
||||
['agent-skills'],
|
||||
'prime-agent'
|
||||
),
|
||||
source(
|
||||
'home-gemini',
|
||||
'Gemini home',
|
||||
|
|
|
|||
|
|
@ -419,6 +419,7 @@
|
|||
"fc80296033": "Devin",
|
||||
"da41abbdd4": "Ante",
|
||||
"060d152fb5": "Trae",
|
||||
"d443a47995": "Prime Agent",
|
||||
"mimo_code_label": "MiMo Code"
|
||||
},
|
||||
"skill": {
|
||||
|
|
|
|||
|
|
@ -344,6 +344,7 @@
|
|||
"fc80296033": "Devin",
|
||||
"da41abbdd4": "Ante",
|
||||
"060d152fb5": "Trae",
|
||||
"d443a47995": "Prime Agent",
|
||||
"mimo_code_label": "MiMo Code"
|
||||
},
|
||||
"skill": {
|
||||
|
|
|
|||
|
|
@ -344,6 +344,7 @@
|
|||
"fc80296033": "Devin",
|
||||
"da41abbdd4": "Ante",
|
||||
"060d152fb5": "Trae",
|
||||
"d443a47995": "Prime Agent",
|
||||
"mimo_code_label": "MiMo Code"
|
||||
},
|
||||
"skill": {
|
||||
|
|
|
|||
|
|
@ -344,6 +344,7 @@
|
|||
"fc80296033": "Devin",
|
||||
"da41abbdd4": "Ante",
|
||||
"060d152fb5": "Trae",
|
||||
"d443a47995": "Prime Agent",
|
||||
"mimo_code_label": "MiMo Code"
|
||||
},
|
||||
"skill": {
|
||||
|
|
|
|||
|
|
@ -344,6 +344,7 @@
|
|||
"fc80296033": "Devin",
|
||||
"da41abbdd4": "Ante",
|
||||
"060d152fb5": "Trae",
|
||||
"d443a47995": "Prime Agent",
|
||||
"mimo_code_label": "MiMo Code"
|
||||
},
|
||||
"skill": {
|
||||
|
|
|
|||
|
|
@ -127,6 +127,13 @@ export const getAgentCatalog = createLocalizedCatalog((): AgentCatalogEntry[] =>
|
|||
// favicon fallback would never be reached.
|
||||
homepageUrl: 'https://omp.sh'
|
||||
},
|
||||
{
|
||||
id: 'prime-agent',
|
||||
label: translate('auto.lib.agent.catalog.d443a47995', 'Prime Agent'),
|
||||
cmd: 'prime-agent',
|
||||
faviconDomain: 'primeintellect.ai',
|
||||
homepageUrl: 'https://github.com/PrimeIntellect-ai/prime-agent'
|
||||
},
|
||||
{
|
||||
id: 'gemini',
|
||||
label: translate('auto.lib.agent.catalog.12e6baa4f7', 'Gemini'),
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import grokUrl from '../../../shared/agent-icons/grok.png?url'
|
|||
import mimoCodeUrl from '../../../shared/agent-icons/mimo-code.png?url'
|
||||
import anteUrl from '../../../shared/agent-icons/ante.png?url'
|
||||
import traeUrl from '../../../shared/agent-icons/trae.png?url'
|
||||
import primeAgentUrl from '../../../shared/agent-icons/prime-agent.png?url'
|
||||
import geminiUrl from '../../../shared/agent-icons/gemini.png?url'
|
||||
import antigravityUrl from '../../../shared/agent-icons/antigravity.png?url'
|
||||
import gooseUrl from '../../../shared/agent-icons/goose.png?url'
|
||||
|
|
@ -36,6 +37,7 @@ export const AGENT_FAVICON_ASSETS: Partial<Record<TuiAgent, string>> = {
|
|||
'mimo-code': mimoCodeUrl,
|
||||
ante: anteUrl,
|
||||
trae: traeUrl,
|
||||
'prime-agent': primeAgentUrl,
|
||||
gemini: geminiUrl,
|
||||
antigravity: antigravityUrl,
|
||||
goose: gooseUrl,
|
||||
|
|
|
|||
|
|
@ -864,6 +864,10 @@ describe('formatAgentTypeLabel', () => {
|
|||
expect(formatAgentTypeLabel('trae')).toBe('Trae')
|
||||
})
|
||||
|
||||
it("maps 'prime-agent' to 'Prime Agent'", () => {
|
||||
expect(formatAgentTypeLabel('prime-agent')).toBe('Prime Agent')
|
||||
})
|
||||
|
||||
it('passes through arbitrary custom agent names as-is', () => {
|
||||
expect(formatAgentTypeLabel('weirdo')).toBe('weirdo')
|
||||
})
|
||||
|
|
@ -889,6 +893,7 @@ describe('agentTypeToIconAgent', () => {
|
|||
expect(agentTypeToIconAgent('command-code')).toBe('command-code')
|
||||
expect(agentTypeToIconAgent('ante')).toBe('ante')
|
||||
expect(agentTypeToIconAgent('trae')).toBe('trae')
|
||||
expect(agentTypeToIconAgent('prime-agent')).toBe('prime-agent')
|
||||
})
|
||||
|
||||
it('returns null for arbitrary non-iconable strings', () => {
|
||||
|
|
|
|||
|
|
@ -105,6 +105,7 @@ const ICONABLE_AGENT_TYPES: Record<TuiAgent, true> = {
|
|||
'mimo-code': true,
|
||||
pi: true,
|
||||
omp: true,
|
||||
'prime-agent': true,
|
||||
gemini: true,
|
||||
antigravity: true,
|
||||
aider: true,
|
||||
|
|
|
|||
|
|
@ -144,6 +144,35 @@ describe('buildAgentStartupPlan', () => {
|
|||
).toBe("traecli -- 'help me name this config'")
|
||||
})
|
||||
|
||||
it('passes the prompt to Prime Agent as a positional argv behind a `--` separator', () => {
|
||||
expect(
|
||||
buildAgentStartupPlan({
|
||||
agent: 'prime-agent',
|
||||
prompt: 'Summarize the failing tests',
|
||||
cmdOverrides: {},
|
||||
platform: 'linux'
|
||||
})
|
||||
).toEqual({
|
||||
agent: 'prime-agent',
|
||||
launchCommand: "prime-agent -- 'Summarize the failing tests'",
|
||||
expectedProcess: 'prime-agent',
|
||||
followupPrompt: null,
|
||||
launchConfig: emptyLaunchConfig('prime-agent')
|
||||
})
|
||||
})
|
||||
|
||||
// Why: without the separator these dispatch to Prime Agent's `help`/`agents` subcommands instead.
|
||||
it('keeps subcommand-shaped Prime Agent prompts as the positional prompt', () => {
|
||||
expect(
|
||||
buildAgentStartupPlan({
|
||||
agent: 'prime-agent',
|
||||
prompt: 'help me name this config',
|
||||
cmdOverrides: {},
|
||||
platform: 'linux'
|
||||
})?.launchCommand
|
||||
).toBe("prime-agent -- 'help me name this config'")
|
||||
})
|
||||
|
||||
it('uses cursor-agent as the actual launch binary', () => {
|
||||
expect(
|
||||
buildAgentStartupPlan({
|
||||
|
|
|
|||
|
|
@ -1,14 +1,17 @@
|
|||
import { isAnteHeadlessOneShotCommand } from './ante-headless-command'
|
||||
import { isPrimeAgentHeadlessOneShotCommand } from './prime-agent-headless-command'
|
||||
import { isPrintModeHeadlessOneShotCommand } from './print-mode-headless-command'
|
||||
import type { TuiAgent } from './types'
|
||||
|
||||
// Why: a table (not an if-chain) so adding an agent is one entry; Claude and Trae share
|
||||
// the same `--print` one-shot contract, Ante's `--prompt` form needs its own matcher.
|
||||
// the same `--print` one-shot contract, Ante's `--prompt` form and Prime Agent's
|
||||
// `--mode` forms need their own matchers.
|
||||
const HEADLESS_ONE_SHOT_MATCHERS: Partial<
|
||||
Record<TuiAgent, (tokens: readonly string[]) => boolean>
|
||||
> = {
|
||||
claude: isPrintModeHeadlessOneShotCommand,
|
||||
trae: isPrintModeHeadlessOneShotCommand,
|
||||
'prime-agent': isPrimeAgentHeadlessOneShotCommand,
|
||||
ante: isAnteHeadlessOneShotCommand
|
||||
}
|
||||
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 644 B |
|
|
@ -23,6 +23,7 @@ const TUI_AGENT_KIND_BY_AGENT = {
|
|||
'mimo-code': 'mimo-code',
|
||||
pi: 'pi',
|
||||
omp: 'omp',
|
||||
'prime-agent': 'prime-agent',
|
||||
gemini: 'gemini',
|
||||
antigravity: 'antigravity',
|
||||
aider: 'aider',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,30 @@
|
|||
import type { TuiAgent } from './types'
|
||||
|
||||
// Node CLIs whose shims launch a generic script (cli.js, versioned index.js), so
|
||||
// only the exact install path is an authoritative identity signal.
|
||||
export const EXACT_NODE_ENTRYPOINT_IDENTITIES: readonly {
|
||||
pattern: RegExp
|
||||
agent: TuiAgent
|
||||
processName: string
|
||||
}[] = [
|
||||
// Why: Cursor's native Windows launcher runs a generic versioned index.js,
|
||||
// so its install path is the only stable identity that avoids ordinary Node apps.
|
||||
{
|
||||
pattern: /(?:^|\/)cursor-agent\/versions\/[^/]+\/index\.js$/,
|
||||
agent: 'cursor',
|
||||
processName: 'cursor-agent'
|
||||
},
|
||||
// Why: Pi's npm shim launches a generic cli.js; only the exact package path is authoritative.
|
||||
{
|
||||
pattern:
|
||||
/(?:^|\/)node_modules\/@(?:earendil-works|mariozechner)\/pi-coding-agent\/dist\/cli\.js$/,
|
||||
agent: 'pi',
|
||||
processName: 'pi'
|
||||
},
|
||||
// Why: same shape as Pi — Prime Agent's npm shim launches a generic bundled cli.js.
|
||||
{
|
||||
pattern: /(?:^|\/)node_modules\/prime-agent\/dist\/bundle\/cli\.js$/,
|
||||
agent: 'prime-agent',
|
||||
processName: 'prime-agent'
|
||||
}
|
||||
]
|
||||
|
|
@ -242,6 +242,61 @@ describe('agent process recognition', () => {
|
|||
).toEqual({ agent: 'pi', processName: 'pi' })
|
||||
})
|
||||
|
||||
it('recognizes Prime Agent by its binary and npm entrypoint', () => {
|
||||
expect(recognizeAgentProcess('prime-agent')).toEqual({
|
||||
agent: 'prime-agent',
|
||||
processName: 'prime-agent'
|
||||
})
|
||||
expect(recognizeAgentProcess('/opt/homebrew/bin/prime-agent')).toEqual({
|
||||
agent: 'prime-agent',
|
||||
processName: 'prime-agent'
|
||||
})
|
||||
expect(
|
||||
recognizeAgentProcessFromCommandLine(
|
||||
'node /opt/homebrew/lib/node_modules/prime-agent/dist/bundle/cli.js'
|
||||
)
|
||||
).toEqual({ agent: 'prime-agent', processName: 'prime-agent' })
|
||||
expect(
|
||||
recognizeAgentProcessFromCommandLine(
|
||||
String.raw`node.exe C:\Users\dev\AppData\Roaming\npm\node_modules\prime-agent\dist\bundle\cli.js`
|
||||
)
|
||||
).toEqual({ agent: 'prime-agent', processName: 'prime-agent' })
|
||||
})
|
||||
|
||||
it('does not recognize Prime Agent headless one-shot commands as interactive agents', () => {
|
||||
expect(recognizeAgentProcessFromCommandLine('prime-agent -p "summarize this diff"')).toBeNull()
|
||||
expect(recognizeAgentProcessFromCommandLine('prime-agent --print "review this"')).toBeNull()
|
||||
expect(recognizeAgentProcessFromCommandLine('prime-agent --resume abc123')).toEqual({
|
||||
agent: 'prime-agent',
|
||||
processName: 'prime-agent'
|
||||
})
|
||||
// Why: past `--` nothing is a flag, so this is the interactive pane Orca itself launches.
|
||||
expect(
|
||||
recognizeAgentProcessFromCommandLine('prime-agent -- "--print the release notes"')
|
||||
).toEqual({ agent: 'prime-agent', processName: 'prime-agent' })
|
||||
})
|
||||
|
||||
it('does not recognize Prime Agent non-interactive --mode runs as interactive agents', () => {
|
||||
for (const mode of ['json', 'rpc', 'acp', 'daemon']) {
|
||||
expect(recognizeAgentProcessFromCommandLine(`prime-agent --mode ${mode}`)).toBeNull()
|
||||
}
|
||||
// Why: `text` is the interactive TUI mode Orca hosts.
|
||||
expect(recognizeAgentProcessFromCommandLine('prime-agent --mode text')).toEqual({
|
||||
agent: 'prime-agent',
|
||||
processName: 'prime-agent'
|
||||
})
|
||||
// Why: the CLI only parses `--mode <value>` as separate tokens, so `--mode=json`
|
||||
// is ignored by it and still starts the interactive mode.
|
||||
expect(recognizeAgentProcessFromCommandLine('prime-agent --mode=json')).toEqual({
|
||||
agent: 'prime-agent',
|
||||
processName: 'prime-agent'
|
||||
})
|
||||
expect(recognizeAgentProcessFromCommandLine('prime-agent -- --mode rpc')).toEqual({
|
||||
agent: 'prime-agent',
|
||||
processName: 'prime-agent'
|
||||
})
|
||||
})
|
||||
|
||||
it('recognizes only the agent subcommand of the generic Orca CLI', () => {
|
||||
expect(recognizeAgentProcessFromCommandLine('orca claude-teams')).toEqual({
|
||||
agent: 'claude-agent-teams',
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { getTuiAgentDetectCommands, TUI_AGENT_CONFIG } from './tui-agent-config'
|
||||
import { EXACT_NODE_ENTRYPOINT_IDENTITIES } from './agent-node-entrypoint-identities'
|
||||
import type { AgentType } from './agent-status-types'
|
||||
import type { TuiAgent } from './types'
|
||||
import { filterHeadlessOneShotAgentCommand } from './agent-headless-command'
|
||||
|
|
@ -52,9 +53,6 @@ const NODE_PACKAGE_SCRIPT_ENTRYPOINTS: Record<string, readonly string[]> = {
|
|||
codex: ['node_modules/@openai/codex/'],
|
||||
gemini: ['node_modules/@google/gemini-cli/']
|
||||
}
|
||||
const CURSOR_AGENT_NODE_ENTRYPOINT_RE = /(?:^|\/)cursor-agent\/versions\/[^/]+\/index\.js$/
|
||||
const PI_AGENT_NODE_ENTRYPOINT_RE =
|
||||
/(?:^|\/)node_modules\/@(?:earendil-works|mariozechner)\/pi-coding-agent\/dist\/cli\.js$/
|
||||
const PYTHON_SCRIPT_ENTRYPOINT_DIRECTORIES = ['/bin/', '/scripts/', '/site-packages/']
|
||||
|
||||
const PROCESS_TO_AGENT = new Map<string, TuiAgent>()
|
||||
|
|
@ -206,14 +204,10 @@ function comparablePath(token: string): string {
|
|||
|
||||
function recognizeNodeScriptEntrypoint(token: string): RecognizedAgentProcess | null {
|
||||
const path = comparablePath(token)
|
||||
// Why: Cursor's native Windows launcher runs a generic versioned index.js,
|
||||
// so its install path is the only stable identity that avoids ordinary Node apps.
|
||||
if (CURSOR_AGENT_NODE_ENTRYPOINT_RE.test(path)) {
|
||||
return { agent: 'cursor', processName: 'cursor-agent' }
|
||||
}
|
||||
// Why: Pi's npm shim launches a generic cli.js; only the exact package path is authoritative.
|
||||
if (PI_AGENT_NODE_ENTRYPOINT_RE.test(path)) {
|
||||
return { agent: 'pi', processName: 'pi' }
|
||||
for (const identity of EXACT_NODE_ENTRYPOINT_IDENTITIES) {
|
||||
if (identity.pattern.test(path)) {
|
||||
return { agent: identity.agent, processName: identity.processName }
|
||||
}
|
||||
}
|
||||
const normalized = normalizeProcessName(token, { stripInterpreterScriptExtension: true })
|
||||
const markers = NODE_PACKAGE_SCRIPT_ENTRYPOINTS[normalized]
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ export type WellKnownAgentType =
|
|||
| 'aider'
|
||||
| 'pi'
|
||||
| 'omp'
|
||||
| 'prime-agent'
|
||||
| 'droid'
|
||||
| 'command-code'
|
||||
| 'grok'
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ const WELL_KNOWN_LABELS: Record<string, string> = {
|
|||
aider: 'Aider',
|
||||
pi: 'Pi',
|
||||
omp: 'OMP',
|
||||
'prime-agent': 'Prime Agent',
|
||||
droid: 'Droid',
|
||||
'command-code': 'Command Code',
|
||||
grok: 'Grok',
|
||||
|
|
|
|||
|
|
@ -122,4 +122,31 @@ describe('buildAiVaultResumeCommand', () => {
|
|||
})
|
||||
).toBe("cd '/Users/ada/repo' && omp --resume '019f27cd-4268-7000-96e7-62f42a55c144'")
|
||||
})
|
||||
|
||||
it('resumes Prime Agent by absolute transcript path like OMP', () => {
|
||||
expect(
|
||||
buildAiVaultResumeCommand({
|
||||
agent: 'prime-agent',
|
||||
sessionId: 'dddddddd-eeee-4fff-8aaa-111111111111',
|
||||
resumeFilePath:
|
||||
'/Users/ada/.prime/agent/sessions/dddddddd-eeee-4fff-8aaa-111111111111.jsonl',
|
||||
cwd: '/Users/ada/repo',
|
||||
platform: 'darwin'
|
||||
})
|
||||
).toBe(
|
||||
"cd '/Users/ada/repo' && prime-agent --resume '/Users/ada/.prime/agent/sessions/dddddddd-eeee-4fff-8aaa-111111111111.jsonl'"
|
||||
)
|
||||
})
|
||||
|
||||
it('falls back to the session id when no Prime Agent transcript path is known', () => {
|
||||
expect(
|
||||
buildAiVaultResumeCommand({
|
||||
agent: 'prime-agent',
|
||||
sessionId: 'dddddddd-eeee-4fff-8aaa-111111111111',
|
||||
resumeFilePath: null,
|
||||
cwd: '/Users/ada/repo',
|
||||
platform: 'darwin'
|
||||
})
|
||||
).toBe("cd '/Users/ada/repo' && prime-agent --resume 'dddddddd-eeee-4fff-8aaa-111111111111'")
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -22,11 +22,15 @@ export function buildAiVaultResumeCommand(args: {
|
|||
const { agent, sessionId, cwd, platform, commandOverride, codexHome, resumeFilePath, shell } =
|
||||
args
|
||||
const baseCommand = commandOverride?.trim() || defaultAiVaultResumeCommandBase(agent)
|
||||
// Why: OMP's `--resume` accepts an absolute transcript path, which resolves
|
||||
// regardless of which session-dir root (custom OMP_CODING_AGENT_DIR / WSL
|
||||
// home) the file was discovered under, where an id-prefix lookup scoped to
|
||||
// the default store would miss it. Falls back to the id if no path is known.
|
||||
const resumeTarget = agent === 'omp' && resumeFilePath?.trim() ? resumeFilePath.trim() : sessionId
|
||||
// Why: OMP's and Prime Agent's `--resume` accept an absolute transcript path,
|
||||
// which resolves regardless of which session-dir root (custom
|
||||
// OMP_CODING_AGENT_DIR / PRIME_AGENT_CODING_AGENT_DIR / WSL home) the file was
|
||||
// discovered under, where an id-prefix lookup scoped to the default store
|
||||
// would miss it. Falls back to the id if no path is known.
|
||||
const resumeTarget =
|
||||
(agent === 'omp' || agent === 'prime-agent') && resumeFilePath?.trim()
|
||||
? resumeFilePath.trim()
|
||||
: sessionId
|
||||
const sessionArg =
|
||||
shell === 'cmd'
|
||||
? quoteWindowsCmdArg(resumeTarget)
|
||||
|
|
@ -167,10 +171,12 @@ function buildAgentResumeInvocation(
|
|||
case 'devin':
|
||||
case 'openclaw':
|
||||
case 'droid':
|
||||
// Why: OMP resumes by absolute transcript path (see buildAiVaultResumeCommand),
|
||||
// but the `--resume <arg>` invocation form is identical to the others here.
|
||||
// Why: OMP and Prime Agent resume by absolute transcript path (see
|
||||
// buildAiVaultResumeCommand), but the `--resume <arg>` invocation form is
|
||||
// identical to the others here.
|
||||
// falls through
|
||||
case 'omp':
|
||||
case 'prime-agent':
|
||||
return `${baseCommand} --resume ${sessionArg}`
|
||||
case 'antigravity':
|
||||
return `${baseCommand} --conversation ${sessionArg}`
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ export const AI_VAULT_AGENTS = [
|
|||
'hermes',
|
||||
'pi',
|
||||
'omp',
|
||||
'prime-agent',
|
||||
'cursor',
|
||||
'gemini',
|
||||
'antigravity',
|
||||
|
|
@ -47,6 +48,7 @@ export const AI_VAULT_AGENT_LABELS = {
|
|||
hermes: 'Hermes',
|
||||
pi: 'Pi',
|
||||
omp: 'OMP',
|
||||
'prime-agent': 'Prime Agent',
|
||||
cursor: 'Cursor',
|
||||
gemini: 'Gemini',
|
||||
antigravity: 'Antigravity',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
import { isPrintModeHeadlessOneShotCommand } from './print-mode-headless-command'
|
||||
|
||||
// Why: Prime Agent's non-interactive runs are `--mode <json|rpc|acp|daemon>` as well as
|
||||
// `--print`; only `text` hosts the TUI. Matching upstream, the value must be a separate
|
||||
// token — `--mode=json` is not parsed by the CLI, so it still starts the interactive mode.
|
||||
const NON_INTERACTIVE_MODES = new Set(['json', 'rpc', 'acp', 'daemon'])
|
||||
|
||||
export function isPrimeAgentHeadlessOneShotCommand(tokens: readonly string[]): boolean {
|
||||
if (isPrintModeHeadlessOneShotCommand(tokens)) {
|
||||
return true
|
||||
}
|
||||
for (let index = 1; index < tokens.length; index += 1) {
|
||||
// Why: `--` ends option parsing, so a prompt that reads like `--mode` is still a prompt.
|
||||
if (tokens[index] === '--') {
|
||||
return false
|
||||
}
|
||||
if (tokens[index] === '--mode' && NON_INTERACTIVE_MODES.has(tokens[index + 1] ?? '')) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
@ -22,6 +22,7 @@ export const SKILLS_CLI_AGENT_KEY_BY_TUI_AGENT = {
|
|||
'mimo-code': null,
|
||||
pi: 'pi',
|
||||
omp: null,
|
||||
'prime-agent': null,
|
||||
gemini: 'gemini-cli',
|
||||
antigravity: 'antigravity',
|
||||
aider: null,
|
||||
|
|
|
|||
|
|
@ -77,6 +77,7 @@ export const AGENT_KIND_VALUES = [
|
|||
'mimo-code',
|
||||
'pi',
|
||||
'omp',
|
||||
'prime-agent',
|
||||
'gemini',
|
||||
'antigravity',
|
||||
'aider',
|
||||
|
|
|
|||
|
|
@ -144,6 +144,18 @@ export const TUI_AGENT_CONFIG: Record<TuiAgent, TuiAgentConfig> = {
|
|||
promptInjectionMode: 'argv',
|
||||
draftPromptEnvVar: 'ORCA_OMP_PREFILL'
|
||||
},
|
||||
'prime-agent': {
|
||||
detectCmd: 'prime-agent',
|
||||
launchCmd: 'prime-agent',
|
||||
expectedProcess: 'prime-agent',
|
||||
// Why: `prime-agent [options] [@files...] [message...]` takes the task as positional argv.
|
||||
promptInjectionMode: 'argv',
|
||||
// Why: separator so prompts starting with `help`/`agents`/`-…` aren't parsed as a
|
||||
// subcommand or flag — its help documents `--` as "treat all following arguments as messages".
|
||||
argvPromptSeparator: '--',
|
||||
// Why: Prime Agent embeds Pi's TUI and decodes CSI-u the same way (see pi above).
|
||||
windowsShiftEnterEncoding: 'csi-u'
|
||||
},
|
||||
gemini: {
|
||||
detectCmd: 'gemini',
|
||||
launchCmd: 'gemini',
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ export const TUI_AGENT_DISPLAY_NAMES: Record<TuiAgent, string> = {
|
|||
'mimo-code': 'MiMo Code',
|
||||
pi: 'Pi',
|
||||
omp: 'OMP',
|
||||
'prime-agent': 'Prime Agent',
|
||||
gemini: 'Gemini',
|
||||
antigravity: 'Antigravity',
|
||||
aider: 'Aider',
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ export const TUI_AGENT_AUTO_PICK_ORDER = [
|
|||
'trae',
|
||||
'pi',
|
||||
'omp',
|
||||
'prime-agent',
|
||||
'gemini',
|
||||
'antigravity',
|
||||
'aider',
|
||||
|
|
|
|||
|
|
@ -2626,6 +2626,7 @@ export type TuiAgent =
|
|||
| 'devin' // Devin CLI
|
||||
| 'ante' // Ante (Antigma Labs)
|
||||
| 'trae' // Trae CLI
|
||||
| 'prime-agent' // Prime Agent (Prime Intellect)
|
||||
|
||||
export type TaskViewPresetId = 'all' | 'issues' | 'review' | 'my-issues' | 'my-prs' | 'prs'
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue