From af4c3e296278bc09df5aa5e157a73c370928ef34 Mon Sep 17 00:00:00 2001 From: Neil Date: Sat, 3 Oct 2026 01:31:15 -0700 Subject: [PATCH] fix(jcode): decompose the four files jcode pushed over max-lines Adding an agent tipped four modules past their line budget. AGENTS.md forbids a `max-lines` disable or a per-file bump, so each is split on a real seam rather than silenced: - agent-catalog.tsx keeps `AgentIcon`, which 70+ files import, and the rows move out. The rows alone exceed the 300-line budget a `.ts` file gets, so they follow the primary/secondary split this repo already uses for commit-message agent specs. - getAgentResumeArgv -> agent-resume-argv.ts, re-exported so the 18 call sites keep one import path. - isDiscoverableSessionFile/pathSegments -> session-file-discovery.ts. - remoteCodexSources -> remote-session-scanner-codex-sources.ts; Codex is the one remote agent with two CODEX_HOME roots. Also: - Records the readiness-census baseline jcode now needs. main added that gate while this branch was out; the fixture is the recorded 74-case matrix, not a hand-written one. - Restores two entries a rebase resolution silently dropped from config/tsconfig.cli.json (gitlab/project-ref-parser, startup/shell-path-probe). Nothing to do with jcode; losing them was a conflict-resolution mistake on this branch. Co-authored-by: czzczz --- .../remote-session-scanner-codex-sources.ts | 51 +++ .../remote-session-scanner-sources.ts | 45 +-- src/main/ai-vault/session-file-discovery.ts | 29 ++ .../ai-vault/session-scanner-agent-sources.ts | 27 +- .../readiness-census/synthetic--jcode.json | 79 +++++ .../src/lib/agent-catalog-entries-primary.ts | 159 +++++++++ .../lib/agent-catalog-entries-secondary.ts | 188 ++++++++++ src/renderer/src/lib/agent-catalog-entries.ts | 8 + src/renderer/src/lib/agent-catalog.tsx | 334 +----------------- src/shared/agent-resume-argv.ts | 80 +++++ src/shared/agent-session-resume.ts | 77 +--- 11 files changed, 602 insertions(+), 475 deletions(-) create mode 100644 src/main/ai-vault/remote-session-scanner-codex-sources.ts create mode 100644 src/main/ai-vault/session-file-discovery.ts create mode 100644 src/main/runtime/__fixtures__/readiness-census/synthetic--jcode.json create mode 100644 src/renderer/src/lib/agent-catalog-entries-primary.ts create mode 100644 src/renderer/src/lib/agent-catalog-entries-secondary.ts create mode 100644 src/renderer/src/lib/agent-catalog-entries.ts create mode 100644 src/shared/agent-resume-argv.ts diff --git a/src/main/ai-vault/remote-session-scanner-codex-sources.ts b/src/main/ai-vault/remote-session-scanner-codex-sources.ts new file mode 100644 index 00000000000..2e6175a445f --- /dev/null +++ b/src/main/ai-vault/remote-session-scanner-codex-sources.ts @@ -0,0 +1,51 @@ +import { joinRemotePath, type RemoteHostPlatform } from '../ssh/ssh-remote-platform' +import { parseCodexSessionContent } from './session-scanner-codex-parser' +import { remoteCodexIndexedTitleReader } from './remote-session-scanner-codex-index' +import type { RemoteSessionContent } from './remote-session-content-lines' +import type { FileWithMtime } from './session-scanner-types' +import type { RemoteScannerContext, RemoteSessionSource } from './remote-session-scanner-types' + +/** Codex is the one remote agent whose sessions live under two CODEX_HOME roots, so it + * yields a source per root. Split from `remote-session-scanner-sources.ts` so adding an + * agent does not push that module past its line budget. */ +export function remoteCodexSources( + remoteHome: string, + hostPlatform: RemoteHostPlatform +): RemoteSessionSource[] { + return [ + joinRemotePath(hostPlatform, remoteHome, '.codex'), + joinRemotePath( + hostPlatform, + remoteHome, + '.local', + 'share', + 'orca', + 'codex-runtime-home', + 'home' + ) + ].map((codexHome) => { + const parse = ( + file: FileWithMtime, + content: RemoteSessionContent, + context: RemoteScannerContext + ) => + parseCodexSessionContent({ + file, + content, + platform: context.hostPlatform.os, + codexHome, + executionHostId: context.executionHostId, + executionHostPlatform: context.hostPlatform.os, + signal: context.signal, + readIndexedTitle: remoteCodexIndexedTitleReader(codexHome, context) + }) + return { + agent: 'codex', + rootDir: joinRemotePath(hostPlatform, codexHome, 'sessions'), + codexHome, + extensions: ['.jsonl'], + parse, + parseLines: parse + } + }) +} diff --git a/src/main/ai-vault/remote-session-scanner-sources.ts b/src/main/ai-vault/remote-session-scanner-sources.ts index fcdcaca88b2..b5c4dc32084 100644 --- a/src/main/ai-vault/remote-session-scanner-sources.ts +++ b/src/main/ai-vault/remote-session-scanner-sources.ts @@ -10,7 +10,6 @@ import type { RemoteHostPlatform } from '../ssh/ssh-remote-platform' import { joinRemotePath } from '../ssh/ssh-remote-platform' import { parseAntigravitySessionContent } from './session-scanner-antigravity-parser' import { isAntigravityTranscriptPath } from './session-scanner-antigravity-paths' -import { parseCodexSessionContent } from './session-scanner-codex-parser' import { parseDroidSessionContent } from './session-scanner-droid-parser' import { parseClaudeSessionContent } from './session-scanner-primary-parsers' import { parseCodebuddySessionContent } from './session-scanner-codebuddy-parser' @@ -33,7 +32,7 @@ import { remotePrimeAgentSessionsSegments } from './remote-session-scanner-source-parsers' import type { FileWithMtime } from './session-scanner-types' -import { remoteCodexIndexedTitleReader } from './remote-session-scanner-codex-index' +import { remoteCodexSources } from './remote-session-scanner-codex-sources' import { remoteClineSource } from './remote-session-scanner-cline-source' import { remoteDevinSource } from './remote-session-scanner-devin-source' import type { @@ -258,48 +257,6 @@ function jsonlSource( } } -function remoteCodexSources( - remoteHome: string, - hostPlatform: RemoteHostPlatform -): RemoteSessionSource[] { - return [ - joinRemotePath(hostPlatform, remoteHome, '.codex'), - joinRemotePath( - hostPlatform, - remoteHome, - '.local', - 'share', - 'orca', - 'codex-runtime-home', - 'home' - ) - ].map((codexHome) => { - const parse = ( - file: FileWithMtime, - content: RemoteSessionContent, - context: RemoteScannerContext - ) => - parseCodexSessionContent({ - file, - content, - platform: context.hostPlatform.os, - codexHome, - executionHostId: context.executionHostId, - executionHostPlatform: context.hostPlatform.os, - signal: context.signal, - readIndexedTitle: remoteCodexIndexedTitleReader(codexHome, context) - }) - return { - agent: 'codex', - rootDir: joinRemotePath(hostPlatform, codexHome, 'sessions'), - codexHome, - extensions: ['.jsonl'], - parse, - parseLines: parse - } - }) -} - function remoteOpenClawSources( remoteHome: string, hostPlatform: RemoteHostPlatform diff --git a/src/main/ai-vault/session-file-discovery.ts b/src/main/ai-vault/session-file-discovery.ts new file mode 100644 index 00000000000..1839bfc5ec3 --- /dev/null +++ b/src/main/ai-vault/session-file-discovery.ts @@ -0,0 +1,29 @@ +import { dirname, extname, relative } from 'node:path' +import type { AiVaultAgentSource } from './session-scanner-agent-sources' + +/** Whether a walked path is a session file this source owns. Split from the source table + * so adding an agent does not push that module past its line budget. */ +export function isDiscoverableSessionFile( + source: AiVaultAgentSource, + rootDir: string, + filePath: string +): boolean { + if (!source.extensions.includes(extname(filePath).toLowerCase())) { + return false + } + if (source.filePredicate && !source.filePredicate(filePath)) { + return false + } + const { directoryPredicate } = source + if (!directoryPredicate) { + return true + } + // Indexed like walkSessionFiles: depth 0 is a child of rootDir. + return pathSegments(relative(rootDir, dirname(filePath))) + .filter(Boolean) + .every((name, depth) => directoryPredicate(name, depth)) +} + +export function pathSegments(filePath: string): string[] { + return filePath.split(/[\\/]/) +} diff --git a/src/main/ai-vault/session-scanner-agent-sources.ts b/src/main/ai-vault/session-scanner-agent-sources.ts index 225bbb5b052..a262595dbab 100644 --- a/src/main/ai-vault/session-scanner-agent-sources.ts +++ b/src/main/ai-vault/session-scanner-agent-sources.ts @@ -1,5 +1,6 @@ import { homedir } from 'node:os' -import { basename, dirname, extname, join, relative } from 'node:path' +import { basename, dirname, join } from 'node:path' +import { pathSegments } from './session-file-discovery' import { resolveAbsoluteDirOverride } from '../../shared/absolute-dir-override' import type { AiVaultAgent } from '../../shared/ai-vault-types' import type { AiVaultDeletableAgent } from '../../shared/ai-vault-session-deletion' @@ -341,27 +342,5 @@ export const AI_VAULT_AGENT_SOURCES: AiVaultAgentSourceTable = { * predicate. This is the delete validator's accept rule, so a path no scan * would ever list can't become a delete target either. */ -export function isDiscoverableSessionFile( - source: AiVaultAgentSource, - rootDir: string, - filePath: string -): boolean { - if (!source.extensions.includes(extname(filePath).toLowerCase())) { - return false - } - if (source.filePredicate && !source.filePredicate(filePath)) { - return false - } - const { directoryPredicate } = source - if (!directoryPredicate) { - return true - } - // Indexed like walkSessionFiles: depth 0 is a child of rootDir. - return pathSegments(relative(rootDir, dirname(filePath))) - .filter(Boolean) - .every((name, depth) => directoryPredicate(name, depth)) -} -function pathSegments(filePath: string): string[] { - return filePath.split(/[\\/]/) -} +export { isDiscoverableSessionFile } from './session-file-discovery' diff --git a/src/main/runtime/__fixtures__/readiness-census/synthetic--jcode.json b/src/main/runtime/__fixtures__/readiness-census/synthetic--jcode.json new file mode 100644 index 00000000000..e60f3c0276d --- /dev/null +++ b/src/main/runtime/__fixtures__/readiness-census/synthetic--jcode.json @@ -0,0 +1,79 @@ +{ + "description": "jcode: title x first-party status on a painted screen; screen x foreground under the titles that leave the low lanes open; dialog order x title. Each read clocked and clockless.", + "cases": { + "title=working-spinner status=none screen=present fg=agent clock=clocked": "now=working edge=working quiet=working wait=pending", + "title=working-spinner status=none screen=present fg=agent clock=clockless": "verdict=working wait=pending", + "title=working-spinner status=done-fresh screen=present fg=agent clock=clocked": "now=working edge=working quiet=working wait=pending", + "title=working-spinner status=done-fresh screen=present fg=agent clock=clockless": "verdict=working wait=pending", + "title=working-spinner status=done-stale screen=present fg=agent clock=clocked": "now=working edge=working quiet=working wait=pending", + "title=working-spinner status=done-stale screen=present fg=agent clock=clockless": "verdict=working wait=pending", + "title=working-spinner status=working-fresh screen=present fg=agent clock=clocked": "now=working edge=working quiet=working wait=pending", + "title=working-spinner status=working-fresh screen=present fg=agent clock=clockless": "verdict=working wait=pending", + "title=working-spinner status=working-stale screen=present fg=agent clock=clocked": "now=working edge=working quiet=working wait=pending", + "title=working-spinner status=working-stale screen=present fg=agent clock=clockless": "verdict=working wait=pending", + "title=working-spinner status=blocked-fresh screen=present fg=agent clock=clocked": "now=pending:closed edge=pending:closed quiet=pending:closed wait=pending", + "title=working-spinner status=blocked-fresh screen=present fg=agent clock=clockless": "verdict=pending:closed wait=pending", + "title=working-spinner status=blocked-stale screen=present fg=agent clock=clocked": "now=working edge=working quiet=working wait=pending", + "title=working-spinner status=blocked-stale screen=present fg=agent clock=clockless": "verdict=working wait=pending", + "title=name-only status=none screen=present fg=agent clock=clocked": "now=ready-weak edge=ready-weak quiet=ready-weak wait=ready@start", + "title=name-only status=none screen=present fg=agent clock=clockless": "verdict=ready-weak wait=ready@start", + "title=name-only status=done-fresh screen=present fg=agent clock=clocked": "now=ready-weak edge=ready-weak quiet=ready-weak wait=ready@start", + "title=name-only status=done-fresh screen=present fg=agent clock=clockless": "verdict=ready-weak wait=ready@start", + "title=name-only status=done-stale screen=present fg=agent clock=clocked": "now=ready-weak edge=ready-weak quiet=ready-weak wait=ready@start", + "title=name-only status=done-stale screen=present fg=agent clock=clockless": "verdict=ready-weak wait=ready@start", + "title=name-only status=working-fresh screen=present fg=agent clock=clocked": "now=working edge=working quiet=working wait=pending", + "title=name-only status=working-fresh screen=present fg=agent clock=clockless": "verdict=working wait=pending", + "title=name-only status=working-stale screen=present fg=agent clock=clocked": "now=ready-weak edge=ready-weak quiet=ready-weak wait=ready@start", + "title=name-only status=working-stale screen=present fg=agent clock=clockless": "verdict=ready-weak wait=ready@start", + "title=name-only status=blocked-fresh screen=present fg=agent clock=clocked": "now=pending:closed edge=pending:closed quiet=pending:closed wait=pending", + "title=name-only status=blocked-fresh screen=present fg=agent clock=clockless": "verdict=pending:closed wait=pending", + "title=name-only status=blocked-stale screen=present fg=agent clock=clocked": "now=ready-weak edge=ready-weak quiet=ready-weak wait=ready@start", + "title=name-only status=blocked-stale screen=present fg=agent clock=clockless": "verdict=ready-weak wait=ready@start", + "title=none status=none screen=present fg=agent clock=clocked": "now=pending:closed edge=pending:closed quiet=pending:closed wait=pending", + "title=none status=none screen=present fg=agent clock=clockless": "verdict=pending:closed wait=pending", + "title=none status=done-fresh screen=present fg=agent clock=clocked": "now=pending:closed edge=pending:closed quiet=pending:closed wait=pending", + "title=none status=done-fresh screen=present fg=agent clock=clockless": "verdict=pending:closed wait=pending", + "title=none status=done-stale screen=present fg=agent clock=clocked": "now=pending:closed edge=pending:closed quiet=pending:closed wait=pending", + "title=none status=done-stale screen=present fg=agent clock=clockless": "verdict=pending:closed wait=pending", + "title=none status=working-fresh screen=present fg=agent clock=clocked": "now=working edge=working quiet=working wait=pending", + "title=none status=working-fresh screen=present fg=agent clock=clockless": "verdict=working wait=pending", + "title=none status=working-stale screen=present fg=agent clock=clocked": "now=pending:closed edge=pending:closed quiet=pending:closed wait=pending", + "title=none status=working-stale screen=present fg=agent clock=clockless": "verdict=pending:closed wait=pending", + "title=none status=blocked-fresh screen=present fg=agent clock=clocked": "now=pending:closed edge=pending:closed quiet=pending:closed wait=pending", + "title=none status=blocked-fresh screen=present fg=agent clock=clockless": "verdict=pending:closed wait=pending", + "title=none status=blocked-stale screen=present fg=agent clock=clocked": "now=pending:closed edge=pending:closed quiet=pending:closed wait=pending", + "title=none status=blocked-stale screen=present fg=agent clock=clockless": "verdict=pending:closed wait=pending", + "title=name-only status=none screen=present fg=shell clock=clocked": "now=ready-weak edge=ready-weak quiet=ready-weak wait=ready@start", + "title=name-only status=none screen=present fg=shell clock=clockless": "verdict=ready-weak wait=ready@start", + "title=name-only status=none screen=untrusted fg=agent clock=clocked": "now=ready-weak edge=ready-weak quiet=ready-weak wait=ready@start", + "title=name-only status=none screen=untrusted fg=agent clock=clockless": "verdict=ready-weak wait=ready@start", + "title=name-only status=none screen=untrusted fg=shell clock=clocked": "now=ready-weak edge=ready-weak quiet=ready-weak wait=ready@start", + "title=name-only status=none screen=untrusted fg=shell clock=clockless": "verdict=ready-weak wait=ready@start", + "title=name-only status=none screen=absent fg=agent clock=clocked": "now=ready-weak edge=ready-weak quiet=ready-weak wait=ready@start", + "title=name-only status=none screen=absent fg=agent clock=clockless": "verdict=ready-weak wait=ready@start", + "title=name-only status=none screen=absent fg=shell clock=clocked": "now=ready-weak edge=ready-weak quiet=ready-weak wait=ready@start", + "title=name-only status=none screen=absent fg=shell clock=clockless": "verdict=ready-weak wait=ready@start", + "title=none status=none screen=present fg=shell clock=clocked": "now=pending:closed edge=pending:closed quiet=pending:closed wait=pending", + "title=none status=none screen=present fg=shell clock=clockless": "verdict=pending:closed wait=pending", + "title=none status=none screen=untrusted fg=agent clock=clocked": "now=pending:closed edge=pending:closed quiet=pending:closed wait=pending", + "title=none status=none screen=untrusted fg=agent clock=clockless": "verdict=pending:closed wait=pending", + "title=none status=none screen=untrusted fg=shell clock=clocked": "now=pending:closed edge=pending:closed quiet=pending:closed wait=pending", + "title=none status=none screen=untrusted fg=shell clock=clockless": "verdict=pending:closed wait=pending", + "title=none status=none screen=absent fg=agent clock=clocked": "now=pending:closed edge=pending:closed quiet=pending:closed wait=pending", + "title=none status=none screen=absent fg=agent clock=clockless": "verdict=pending:closed wait=pending", + "title=none status=none screen=absent fg=shell clock=clocked": "now=pending:closed edge=pending:closed quiet=pending:closed wait=pending", + "title=none status=none screen=absent fg=shell clock=clockless": "verdict=pending:closed wait=pending", + "title=working-spinner status=none screen=dialog-last fg=agent clock=clocked": "now=blocked:agent-trust-workspace edge=blocked:agent-trust-workspace quiet=blocked:agent-trust-workspace wait=blocked:agent-trust-workspace@start", + "title=working-spinner status=none screen=dialog-last fg=agent clock=clockless": "verdict=blocked:agent-trust-workspace wait=blocked:agent-trust-workspace@start", + "title=working-spinner status=none screen=ready-last fg=agent clock=clocked": "now=blocked:agent-trust-workspace edge=blocked:agent-trust-workspace quiet=blocked:agent-trust-workspace wait=blocked:agent-trust-workspace@start", + "title=working-spinner status=none screen=ready-last fg=agent clock=clockless": "verdict=blocked:agent-trust-workspace wait=blocked:agent-trust-workspace@start", + "title=name-only status=none screen=dialog-last fg=agent clock=clocked": "now=blocked:agent-trust-workspace edge=blocked:agent-trust-workspace quiet=blocked:agent-trust-workspace wait=blocked:agent-trust-workspace@start", + "title=name-only status=none screen=dialog-last fg=agent clock=clockless": "verdict=blocked:agent-trust-workspace wait=blocked:agent-trust-workspace@start", + "title=name-only status=none screen=ready-last fg=agent clock=clocked": "now=blocked:agent-trust-workspace edge=blocked:agent-trust-workspace quiet=blocked:agent-trust-workspace wait=blocked:agent-trust-workspace@start", + "title=name-only status=none screen=ready-last fg=agent clock=clockless": "verdict=blocked:agent-trust-workspace wait=blocked:agent-trust-workspace@start", + "title=none status=none screen=dialog-last fg=agent clock=clocked": "now=blocked:agent-trust-workspace edge=blocked:agent-trust-workspace quiet=blocked:agent-trust-workspace wait=blocked:agent-trust-workspace@start", + "title=none status=none screen=dialog-last fg=agent clock=clockless": "verdict=blocked:agent-trust-workspace wait=blocked:agent-trust-workspace@start", + "title=none status=none screen=ready-last fg=agent clock=clocked": "now=blocked:agent-trust-workspace edge=blocked:agent-trust-workspace quiet=blocked:agent-trust-workspace wait=blocked:agent-trust-workspace@start", + "title=none status=none screen=ready-last fg=agent clock=clockless": "verdict=blocked:agent-trust-workspace wait=blocked:agent-trust-workspace@start" + } +} diff --git a/src/renderer/src/lib/agent-catalog-entries-primary.ts b/src/renderer/src/lib/agent-catalog-entries-primary.ts new file mode 100644 index 00000000000..2d0801104df --- /dev/null +++ b/src/renderer/src/lib/agent-catalog-entries-primary.ts @@ -0,0 +1,159 @@ +import { getCatalogPlatform } from './agent-catalog-platform' +import openClaudeLogoUrl from '../../../../resources/openclaude-logo.png?url' +import { getTuiAgentLaunchCommand, TUI_AGENT_CONFIG } from '../../../shared/tui-agent-config' +import { translate } from '@/i18n/i18n' +import type { AgentCatalogEntry } from './agent-catalog' + +/** The agents Orca drives first-party. Split from the community tail so adding an agent + * does not push either module past its line budget; the two are concatenated in order + * by `buildAgentCatalogEntries`. */ +export function primaryAgentCatalogEntries(): AgentCatalogEntry[] { + return [ + { + id: 'claude', + label: translate('auto.lib.agent.catalog.0708ed89f1', 'Claude'), + cmd: 'claude', + homepageUrl: 'https://code.claude.com/docs' + }, + { + id: 'claude-agent-teams', + label: translate('auto.lib.agent.catalog.bf53f09bf8', 'Claude Agent Teams'), + cmd: getTuiAgentLaunchCommand(TUI_AGENT_CONFIG['claude-agent-teams'], getCatalogPlatform()), + homepageUrl: 'https://code.claude.com/docs/en/agent-teams' + }, + { + id: 'openclaude', + label: translate('auto.lib.agent.catalog.a5fc0cb622', 'OpenClaude'), + cmd: 'openclaude', + // Why: OpenClaude's published favicon has a padded 500px canvas; Orca + // uses a cropped derivative of that official asset so 12px tab icons stay legible. + iconUrl: openClaudeLogoUrl, + homepageUrl: 'https://openclaude.gitlawb.com/' + }, + { + id: 'codex', + label: translate('auto.lib.agent.catalog.760bc6883d', 'Codex'), + cmd: 'codex', + homepageUrl: 'https://github.com/openai/codex' + }, + { + id: 'grok', + label: translate('auto.lib.agent.catalog.0baad2d5d2', 'Grok'), + cmd: 'grok', + faviconDomain: 'x.ai', + homepageUrl: 'https://x.ai/cli' + }, + { + id: 'copilot', + label: translate('auto.lib.agent.catalog.706b0fe68b', 'GitHub Copilot'), + cmd: 'copilot', + homepageUrl: 'https://docs.github.com/en/copilot/how-tos/set-up/install-copilot-cli' + }, + { + id: 'opencode2', + label: translate('auto.lib.agent.catalog.opencode2_label', 'OpenCode 2'), + cmd: 'opencode2', + homepageUrl: 'https://opencode.ai/v2/docs/' + }, + { + id: 'opencode', + label: translate('auto.lib.agent.catalog.e7a4ca5103', 'OpenCode'), + cmd: 'opencode', + homepageUrl: 'https://opencode.ai/docs/cli/' + }, + { + id: 'mimo-code', + label: translate('auto.lib.agent.catalog.mimo_code_label', 'MiMo Code'), + cmd: 'mimo', + faviconDomain: 'mimo.xiaomi.com', + homepageUrl: 'https://mimo.xiaomi.com/coder' + }, + { + id: 'ante', + label: translate('auto.lib.agent.catalog.da41abbdd4', 'Ante'), + cmd: 'ante', + faviconDomain: 'antigma.ai', + homepageUrl: 'https://github.com/AntigmaLabs/ante-preview' + }, + { + id: 'trae', + label: translate('auto.lib.agent.catalog.060d152fb5', 'Trae'), + // Why: matches TUI_AGENT_CONFIG.trae.detectCmd, not the ambiguous `trae-cli` — see the Why there. + cmd: 'traecli', + // Why: bare `trae.cn` 404s on Google's favicon service. + faviconDomain: 'www.trae.cn', + homepageUrl: 'https://docs.trae.cn/cli_get-started-with-trae-cli' + }, + { + id: 'muse', + label: translate('auto.lib.agent.catalog.muse_label', 'Muse'), + cmd: 'muse', + faviconDomain: 'dev.meta.ai', + homepageUrl: 'https://dev.meta.ai/docs/muse-code' + }, + { + id: 'dsh', + label: translate('auto.lib.agent.catalog.dsh_label', 'DeepSeek Harness'), + cmd: 'dsh-tui', + searchAliases: ['deepseek', 'dsh', 'dst', 'deepseek harness'], + homepageUrl: 'https://deepseek-harness.github.io/deepseek-harness/' + }, + { + id: 'qoder', + label: translate('auto.lib.agent.catalog.qoder_label', 'Qoder CLI'), + cmd: 'qodercli', + faviconDomain: 'qoder.com', + homepageUrl: 'https://docs.qoder.com/cli/overview' + }, + { + id: 'qoder-cn', + label: translate('auto.lib.agent.catalog.qoder_cn_label', 'Qoder CLI China'), + cmd: 'qoderclicn', + faviconDomain: 'qoder.cn', + homepageUrl: 'https://docs.qoder.cn/cli/overview' + }, + { + id: 'zcode', + label: translate('auto.lib.agent.catalog.zcode_label', 'ZCode'), + cmd: 'zcode', + faviconDomain: 'zcode.z.ai', + homepageUrl: 'https://zcode.z.ai/en/docs' + }, + { + id: 'pi', + label: translate('auto.lib.agent.catalog.302934c5d9', 'Pi'), + cmd: 'pi', + homepageUrl: 'https://pi.dev' + }, + { + id: 'omp', + label: translate('auto.lib.agent.catalog.09973b4d84', 'OMP'), + cmd: 'omp', + searchAliases: ['oh-my-pi', 'oh my pi'], + // Why: no faviconDomain — omp renders the hand-authored OmpIcon glyph, so a + // 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'), + cmd: 'gemini', + faviconDomain: 'gemini.google.com', + homepageUrl: 'https://github.com/google-gemini/gemini-cli' + }, + { + id: 'antigravity', + label: translate('auto.lib.agent.catalog.691dd11789', 'Antigravity'), + cmd: 'agy', + faviconDomain: 'antigravity.google', + homepageUrl: 'https://antigravity.google/docs/cli-overview' + } + ] +} diff --git a/src/renderer/src/lib/agent-catalog-entries-secondary.ts b/src/renderer/src/lib/agent-catalog-entries-secondary.ts new file mode 100644 index 00000000000..2bdee7c1c43 --- /dev/null +++ b/src/renderer/src/lib/agent-catalog-entries-secondary.ts @@ -0,0 +1,188 @@ +import { translate } from '@/i18n/i18n' +import type { AgentCatalogEntry } from './agent-catalog' + +/** The community tail of the catalog; see `primaryAgentCatalogEntries`. */ +export function secondaryAgentCatalogEntries(): AgentCatalogEntry[] { + return [ + { + id: 'aider', + label: translate('auto.lib.agent.catalog.b32627f09b', 'Aider'), + cmd: 'aider', + homepageUrl: 'https://aider.chat/docs/' + }, + { + id: 'goose', + label: translate('auto.lib.agent.catalog.8da11d876c', 'Goose'), + cmd: 'goose', + faviconDomain: 'goose-docs.ai', + homepageUrl: 'https://block.github.io/goose/docs/quickstart/' + }, + { + id: 'amp', + label: translate('auto.lib.agent.catalog.c73c573939', 'Amp'), + cmd: 'amp', + faviconDomain: 'ampcode.com', + homepageUrl: 'https://ampcode.com/manual#install' + }, + { + id: 'kilo', + label: translate('auto.lib.agent.catalog.918ba4ffed', 'Kilocode'), + cmd: 'kilo', + homepageUrl: 'https://kilo.ai/docs/cli' + }, + { + id: 'kiro', + label: translate('auto.lib.agent.catalog.e0247254f2', 'Kiro'), + // Why: the Kiro installer (https://cli.kiro.dev/install) ships a binary + // named `kiro-cli`, not `kiro`. Match TUI_AGENT_CONFIG.kiro.detectCmd so + // the settings pane's "default command" hint aligns with what Orca + // actually looks for on PATH. + cmd: 'kiro-cli', + faviconDomain: 'kiro.dev', + homepageUrl: 'https://kiro.dev/docs/cli/' + }, + { + id: 'crush', + label: translate('auto.lib.agent.catalog.9477377a2a', 'Charm'), + cmd: 'crush', + faviconDomain: 'charm.sh', + homepageUrl: 'https://github.com/charmbracelet/crush' + }, + { + id: 'aug', + label: translate('auto.lib.agent.catalog.5e8eff11b3', 'Auggie'), + cmd: 'auggie', + faviconDomain: 'augmentcode.com', + homepageUrl: 'https://docs.augmentcode.com/cli/overview' + }, + { + id: 'autohand', + label: translate('auto.lib.agent.catalog.1f8a19e9ad', 'Autohand Code'), + cmd: 'autohand', + faviconDomain: 'autohand.ai', + homepageUrl: 'https://github.com/autohandai/code-cli' + }, + { + id: 'cline', + label: translate('auto.lib.agent.catalog.cbaf0c2e0b', 'Cline'), + cmd: 'cline', + faviconDomain: 'cline.bot', + homepageUrl: 'https://docs.cline.bot/cline-cli/overview' + }, + { + id: 'codebuff', + label: translate('auto.lib.agent.catalog.4238b771b5', 'Codebuff'), + cmd: 'codebuff', + faviconDomain: 'codebuff.com', + homepageUrl: 'https://www.codebuff.com/docs/help/quick-start' + }, + { + id: 'freebuff', + label: translate('auto.lib.agent.catalog.freebuff_label', 'Freebuff'), + cmd: 'freebuff', + faviconDomain: 'freebuff.com', + homepageUrl: 'https://freebuff.com/cli' + }, + { + id: 'command-code', + label: translate('auto.lib.agent.catalog.6f8056a565', 'Command Code'), + // Why: `npm i -g command-code` installs both `command-code` and the + // shorter alias `cmd`. Show the full name in the settings hint so it + // matches TUI_AGENT_CONFIG['command-code'].detectCmd and avoids any + // suggestion that Orca is looking for Windows' built-in `cmd.exe`. + cmd: 'command-code', + faviconDomain: 'commandcode.ai', + homepageUrl: 'https://commandcode.ai/docs/quickstart' + }, + { + id: 'continue', + label: translate('auto.lib.agent.catalog.9e2a9bb87b', 'Continue'), + // Why: Continue's terminal agent installs as `cn`; `continue` resolves to + // a shell builtin in common shells and is not a reliable executable hint. + cmd: 'cn', + faviconDomain: 'continue.dev', + homepageUrl: 'https://docs.continue.dev/guides/cli' + }, + { + id: 'cursor', + label: translate('auto.lib.agent.catalog.667c104cff', 'Cursor'), + cmd: 'cursor-agent', + faviconDomain: 'cursor.com', + homepageUrl: 'https://cursor.com/cli' + }, + { + id: 'droid', + label: translate('auto.lib.agent.catalog.739a930554', 'Droid'), + cmd: 'droid', + homepageUrl: 'https://docs.factory.ai/cli/getting-started/quickstart' + }, + { + id: 'kimi', + label: translate('auto.lib.agent.catalog.28810273af', 'Kimi'), + cmd: 'kimi', + faviconDomain: 'moonshot.cn', + homepageUrl: 'https://www.kimi.com/code/docs/en/kimi-code-cli/getting-started.html' + }, + { + id: 'mistral-vibe', + label: translate('auto.lib.agent.catalog.ca73055bd0', 'Mistral Vibe'), + // Why: `uv tool install mistral-vibe` exposes the interactive CLI as + // `vibe`; the package name is not the executable users put on PATH. + cmd: 'vibe', + faviconDomain: 'mistral.ai', + homepageUrl: 'https://github.com/mistralai/mistral-vibe' + }, + { + id: 'qwen-code', + label: translate('auto.lib.agent.catalog.bee242fe3d', 'Qwen Code'), + // Why: QwenLM/qwen-code installs its CLI executable as `qwen`; the package + // name is not the binary users put on PATH. Keep `id` for stable identity. + cmd: 'qwen', + faviconDomain: 'qwenlm.github.io', + homepageUrl: 'https://github.com/QwenLM/qwen-code' + }, + { + id: 'rovo', + label: translate('auto.lib.agent.catalog.4e63c7b956', 'Rovo Dev'), + cmd: 'rovo', + faviconDomain: 'atlassian.com', + homepageUrl: + 'https://support.atlassian.com/rovo/docs/install-and-run-rovo-dev-cli-on-your-device/' + }, + { + id: 'hermes', + label: translate('auto.lib.agent.catalog.8a9ba743cc', 'Hermes'), + cmd: 'hermes', + faviconDomain: 'nousresearch.com', + homepageUrl: 'https://hermes-agent.nousresearch.com/docs/' + }, + { + id: 'devin', + label: translate('auto.lib.agent.catalog.fc80296033', 'Devin'), + cmd: 'devin', + faviconDomain: 'devin.ai', + homepageUrl: 'https://devin.ai/cli' + }, + { + id: 'openclaw', + label: translate('auto.lib.agent.catalog.5dff448636', 'OpenClaw'), + cmd: 'openclaw', + faviconDomain: 'openclaw.ai', + homepageUrl: 'https://github.com/openclaw/openclaw' + }, + { + id: 'codebuddy', + label: translate('auto.lib.agent.catalog.codebuddy_label', 'CodeBuddy'), + cmd: 'codebuddy', + faviconDomain: 'codebuddy.ai', + homepageUrl: 'https://www.codebuddy.ai/cli' + }, + { + id: 'jcode', + label: translate('auto.lib.agent.catalog.jcode_label', 'Jcode'), + cmd: 'jcode', + faviconDomain: 'jcode.sh', + homepageUrl: 'https://github.com/1jehuang/jcode' + } + ] +} diff --git a/src/renderer/src/lib/agent-catalog-entries.ts b/src/renderer/src/lib/agent-catalog-entries.ts new file mode 100644 index 00000000000..44d0269fdb6 --- /dev/null +++ b/src/renderer/src/lib/agent-catalog-entries.ts @@ -0,0 +1,8 @@ +import { primaryAgentCatalogEntries } from './agent-catalog-entries-primary' +import { secondaryAgentCatalogEntries } from './agent-catalog-entries-secondary' +import type { AgentCatalogEntry } from './agent-catalog' + +/** The catalog rows, in display order. */ +export function buildAgentCatalogEntries(): AgentCatalogEntry[] { + return [...primaryAgentCatalogEntries(), ...secondaryAgentCatalogEntries()] +} diff --git a/src/renderer/src/lib/agent-catalog.tsx b/src/renderer/src/lib/agent-catalog.tsx index 507c1cbde19..9b9e929fcef 100644 --- a/src/renderer/src/lib/agent-catalog.tsx +++ b/src/renderer/src/lib/agent-catalog.tsx @@ -1,9 +1,6 @@ -import { getCatalogPlatform } from './agent-catalog-platform' import type React from 'react' import { ClaudeIcon, DroidIcon, OpenAIIcon } from '@/components/status-bar/icons' -import openClaudeLogoUrl from '../../../../resources/openclaude-logo.png?url' import type { TuiAgent } from '../../../shared/tui-agent' -import { getTuiAgentLaunchCommand, TUI_AGENT_CONFIG } from '../../../shared/tui-agent-config' import { AgentLetterIcon, AiderIcon, @@ -13,9 +10,9 @@ import { OpenCodeIcon, PiIcon } from './agent-icon-glyphs' -import { translate } from '@/i18n/i18n' import { createLocalizedCatalog } from '@/i18n/localized-catalog' import { AGENT_FAVICON_ASSETS } from './agent-favicon-assets' +import { buildAgentCatalogEntries } from './agent-catalog-entries' export type AgentCatalogEntry = { id: TuiAgent @@ -31,334 +28,7 @@ export type AgentCatalogEntry = { homepageUrl: string } -export const getAgentCatalog = createLocalizedCatalog((): AgentCatalogEntry[] => [ - { - id: 'claude', - label: translate('auto.lib.agent.catalog.0708ed89f1', 'Claude'), - cmd: 'claude', - homepageUrl: 'https://code.claude.com/docs' - }, - { - id: 'claude-agent-teams', - label: translate('auto.lib.agent.catalog.bf53f09bf8', 'Claude Agent Teams'), - cmd: getTuiAgentLaunchCommand(TUI_AGENT_CONFIG['claude-agent-teams'], getCatalogPlatform()), - homepageUrl: 'https://code.claude.com/docs/en/agent-teams' - }, - { - id: 'openclaude', - label: translate('auto.lib.agent.catalog.a5fc0cb622', 'OpenClaude'), - cmd: 'openclaude', - // Why: OpenClaude's published favicon has a padded 500px canvas; Orca - // uses a cropped derivative of that official asset so 12px tab icons stay legible. - iconUrl: openClaudeLogoUrl, - homepageUrl: 'https://openclaude.gitlawb.com/' - }, - { - id: 'codex', - label: translate('auto.lib.agent.catalog.760bc6883d', 'Codex'), - cmd: 'codex', - homepageUrl: 'https://github.com/openai/codex' - }, - { - id: 'grok', - label: translate('auto.lib.agent.catalog.0baad2d5d2', 'Grok'), - cmd: 'grok', - faviconDomain: 'x.ai', - homepageUrl: 'https://x.ai/cli' - }, - { - id: 'copilot', - label: translate('auto.lib.agent.catalog.706b0fe68b', 'GitHub Copilot'), - cmd: 'copilot', - homepageUrl: 'https://docs.github.com/en/copilot/how-tos/set-up/install-copilot-cli' - }, - { - id: 'opencode2', - label: translate('auto.lib.agent.catalog.opencode2_label', 'OpenCode 2'), - cmd: 'opencode2', - homepageUrl: 'https://opencode.ai/v2/docs/' - }, - { - id: 'opencode', - label: translate('auto.lib.agent.catalog.e7a4ca5103', 'OpenCode'), - cmd: 'opencode', - homepageUrl: 'https://opencode.ai/docs/cli/' - }, - { - id: 'mimo-code', - label: translate('auto.lib.agent.catalog.mimo_code_label', 'MiMo Code'), - cmd: 'mimo', - faviconDomain: 'mimo.xiaomi.com', - homepageUrl: 'https://mimo.xiaomi.com/coder' - }, - { - id: 'ante', - label: translate('auto.lib.agent.catalog.da41abbdd4', 'Ante'), - cmd: 'ante', - faviconDomain: 'antigma.ai', - homepageUrl: 'https://github.com/AntigmaLabs/ante-preview' - }, - { - id: 'trae', - label: translate('auto.lib.agent.catalog.060d152fb5', 'Trae'), - // Why: matches TUI_AGENT_CONFIG.trae.detectCmd, not the ambiguous `trae-cli` — see the Why there. - cmd: 'traecli', - // Why: bare `trae.cn` 404s on Google's favicon service. - faviconDomain: 'www.trae.cn', - homepageUrl: 'https://docs.trae.cn/cli_get-started-with-trae-cli' - }, - { - id: 'muse', - label: translate('auto.lib.agent.catalog.muse_label', 'Muse'), - cmd: 'muse', - faviconDomain: 'dev.meta.ai', - homepageUrl: 'https://dev.meta.ai/docs/muse-code' - }, - { - id: 'dsh', - label: translate('auto.lib.agent.catalog.dsh_label', 'DeepSeek Harness'), - cmd: 'dsh-tui', - searchAliases: ['deepseek', 'dsh', 'dst', 'deepseek harness'], - homepageUrl: 'https://deepseek-harness.github.io/deepseek-harness/' - }, - { - id: 'qoder', - label: translate('auto.lib.agent.catalog.qoder_label', 'Qoder CLI'), - cmd: 'qodercli', - faviconDomain: 'qoder.com', - homepageUrl: 'https://docs.qoder.com/cli/overview' - }, - { - id: 'qoder-cn', - label: translate('auto.lib.agent.catalog.qoder_cn_label', 'Qoder CLI China'), - cmd: 'qoderclicn', - faviconDomain: 'qoder.cn', - homepageUrl: 'https://docs.qoder.cn/cli/overview' - }, - { - id: 'zcode', - label: translate('auto.lib.agent.catalog.zcode_label', 'ZCode'), - cmd: 'zcode', - faviconDomain: 'zcode.z.ai', - homepageUrl: 'https://zcode.z.ai/en/docs' - }, - { - id: 'pi', - label: translate('auto.lib.agent.catalog.302934c5d9', 'Pi'), - cmd: 'pi', - homepageUrl: 'https://pi.dev' - }, - { - id: 'omp', - label: translate('auto.lib.agent.catalog.09973b4d84', 'OMP'), - cmd: 'omp', - searchAliases: ['oh-my-pi', 'oh my pi'], - // Why: no faviconDomain — omp renders the hand-authored OmpIcon glyph, so a - // 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'), - cmd: 'gemini', - faviconDomain: 'gemini.google.com', - homepageUrl: 'https://github.com/google-gemini/gemini-cli' - }, - { - id: 'antigravity', - label: translate('auto.lib.agent.catalog.691dd11789', 'Antigravity'), - cmd: 'agy', - faviconDomain: 'antigravity.google', - homepageUrl: 'https://antigravity.google/docs/cli-overview' - }, - { - id: 'aider', - label: translate('auto.lib.agent.catalog.b32627f09b', 'Aider'), - cmd: 'aider', - homepageUrl: 'https://aider.chat/docs/' - }, - { - id: 'goose', - label: translate('auto.lib.agent.catalog.8da11d876c', 'Goose'), - cmd: 'goose', - faviconDomain: 'goose-docs.ai', - homepageUrl: 'https://block.github.io/goose/docs/quickstart/' - }, - { - id: 'amp', - label: translate('auto.lib.agent.catalog.c73c573939', 'Amp'), - cmd: 'amp', - faviconDomain: 'ampcode.com', - homepageUrl: 'https://ampcode.com/manual#install' - }, - { - id: 'kilo', - label: translate('auto.lib.agent.catalog.918ba4ffed', 'Kilocode'), - cmd: 'kilo', - homepageUrl: 'https://kilo.ai/docs/cli' - }, - { - id: 'kiro', - label: translate('auto.lib.agent.catalog.e0247254f2', 'Kiro'), - // Why: the Kiro installer (https://cli.kiro.dev/install) ships a binary - // named `kiro-cli`, not `kiro`. Match TUI_AGENT_CONFIG.kiro.detectCmd so - // the settings pane's "default command" hint aligns with what Orca - // actually looks for on PATH. - cmd: 'kiro-cli', - faviconDomain: 'kiro.dev', - homepageUrl: 'https://kiro.dev/docs/cli/' - }, - { - id: 'crush', - label: translate('auto.lib.agent.catalog.9477377a2a', 'Charm'), - cmd: 'crush', - faviconDomain: 'charm.sh', - homepageUrl: 'https://github.com/charmbracelet/crush' - }, - { - id: 'aug', - label: translate('auto.lib.agent.catalog.5e8eff11b3', 'Auggie'), - cmd: 'auggie', - faviconDomain: 'augmentcode.com', - homepageUrl: 'https://docs.augmentcode.com/cli/overview' - }, - { - id: 'autohand', - label: translate('auto.lib.agent.catalog.1f8a19e9ad', 'Autohand Code'), - cmd: 'autohand', - faviconDomain: 'autohand.ai', - homepageUrl: 'https://github.com/autohandai/code-cli' - }, - { - id: 'cline', - label: translate('auto.lib.agent.catalog.cbaf0c2e0b', 'Cline'), - cmd: 'cline', - faviconDomain: 'cline.bot', - homepageUrl: 'https://docs.cline.bot/cline-cli/overview' - }, - { - id: 'codebuff', - label: translate('auto.lib.agent.catalog.4238b771b5', 'Codebuff'), - cmd: 'codebuff', - faviconDomain: 'codebuff.com', - homepageUrl: 'https://www.codebuff.com/docs/help/quick-start' - }, - { - id: 'freebuff', - label: translate('auto.lib.agent.catalog.freebuff_label', 'Freebuff'), - cmd: 'freebuff', - faviconDomain: 'freebuff.com', - homepageUrl: 'https://freebuff.com/cli' - }, - { - id: 'command-code', - label: translate('auto.lib.agent.catalog.6f8056a565', 'Command Code'), - // Why: `npm i -g command-code` installs both `command-code` and the - // shorter alias `cmd`. Show the full name in the settings hint so it - // matches TUI_AGENT_CONFIG['command-code'].detectCmd and avoids any - // suggestion that Orca is looking for Windows' built-in `cmd.exe`. - cmd: 'command-code', - faviconDomain: 'commandcode.ai', - homepageUrl: 'https://commandcode.ai/docs/quickstart' - }, - { - id: 'continue', - label: translate('auto.lib.agent.catalog.9e2a9bb87b', 'Continue'), - // Why: Continue's terminal agent installs as `cn`; `continue` resolves to - // a shell builtin in common shells and is not a reliable executable hint. - cmd: 'cn', - faviconDomain: 'continue.dev', - homepageUrl: 'https://docs.continue.dev/guides/cli' - }, - { - id: 'cursor', - label: translate('auto.lib.agent.catalog.667c104cff', 'Cursor'), - cmd: 'cursor-agent', - faviconDomain: 'cursor.com', - homepageUrl: 'https://cursor.com/cli' - }, - { - id: 'droid', - label: translate('auto.lib.agent.catalog.739a930554', 'Droid'), - cmd: 'droid', - homepageUrl: 'https://docs.factory.ai/cli/getting-started/quickstart' - }, - { - id: 'kimi', - label: translate('auto.lib.agent.catalog.28810273af', 'Kimi'), - cmd: 'kimi', - faviconDomain: 'moonshot.cn', - homepageUrl: 'https://www.kimi.com/code/docs/en/kimi-code-cli/getting-started.html' - }, - { - id: 'mistral-vibe', - label: translate('auto.lib.agent.catalog.ca73055bd0', 'Mistral Vibe'), - // Why: `uv tool install mistral-vibe` exposes the interactive CLI as - // `vibe`; the package name is not the executable users put on PATH. - cmd: 'vibe', - faviconDomain: 'mistral.ai', - homepageUrl: 'https://github.com/mistralai/mistral-vibe' - }, - { - id: 'qwen-code', - label: translate('auto.lib.agent.catalog.bee242fe3d', 'Qwen Code'), - // Why: QwenLM/qwen-code installs its CLI executable as `qwen`; the package - // name is not the binary users put on PATH. Keep `id` for stable identity. - cmd: 'qwen', - faviconDomain: 'qwenlm.github.io', - homepageUrl: 'https://github.com/QwenLM/qwen-code' - }, - { - id: 'rovo', - label: translate('auto.lib.agent.catalog.4e63c7b956', 'Rovo Dev'), - cmd: 'rovo', - faviconDomain: 'atlassian.com', - homepageUrl: - 'https://support.atlassian.com/rovo/docs/install-and-run-rovo-dev-cli-on-your-device/' - }, - { - id: 'hermes', - label: translate('auto.lib.agent.catalog.8a9ba743cc', 'Hermes'), - cmd: 'hermes', - faviconDomain: 'nousresearch.com', - homepageUrl: 'https://hermes-agent.nousresearch.com/docs/' - }, - { - id: 'devin', - label: translate('auto.lib.agent.catalog.fc80296033', 'Devin'), - cmd: 'devin', - faviconDomain: 'devin.ai', - homepageUrl: 'https://devin.ai/cli' - }, - { - id: 'openclaw', - label: translate('auto.lib.agent.catalog.5dff448636', 'OpenClaw'), - cmd: 'openclaw', - faviconDomain: 'openclaw.ai', - homepageUrl: 'https://github.com/openclaw/openclaw' - }, - { - id: 'codebuddy', - label: translate('auto.lib.agent.catalog.codebuddy_label', 'CodeBuddy'), - cmd: 'codebuddy', - faviconDomain: 'codebuddy.ai', - homepageUrl: 'https://www.codebuddy.ai/cli' - }, - { - id: 'jcode', - label: translate('auto.lib.agent.catalog.jcode_label', 'Jcode'), - cmd: 'jcode', - faviconDomain: 'jcode.sh', - homepageUrl: 'https://github.com/1jehuang/jcode' - } -]) +export const getAgentCatalog = createLocalizedCatalog(buildAgentCatalogEntries) // Why: tests and a few legacy call sites still import a catalog snapshot. export const AGENT_CATALOG: AgentCatalogEntry[] = getAgentCatalog() diff --git a/src/shared/agent-resume-argv.ts b/src/shared/agent-resume-argv.ts new file mode 100644 index 00000000000..79bbad55231 --- /dev/null +++ b/src/shared/agent-resume-argv.ts @@ -0,0 +1,80 @@ +import type { AgentProviderSessionMetadata, ResumableTuiAgent } from './agent-session-resume' + +/** The argv that re-enters an existing session, per agent. Split from + * `agent-session-resume.ts` so adding an agent does not push that module past its line + * budget; type-only import back, so there is no runtime cycle. */ +export function getAgentResumeArgv( + agent: ResumableTuiAgent, + providerSession: AgentProviderSessionMetadata, + ompResumeFilePath?: string | null +): string[] | null { + const id = providerSession.id + switch (agent) { + case 'codebuddy': + return providerSession.key === 'session_id' ? ['codebuddy', '--resume', id] : null + case 'claude': + return providerSession.key === 'session_id' ? ['claude', '--resume', id] : null + case 'cursor': + return providerSession.key === 'conversation_id' ? ['cursor-agent', '--resume', id] : null + case 'codex': + return providerSession.key === 'session_id' ? ['codex', 'resume', id] : null + case 'qoder-cn': + return providerSession.key === 'session_id' ? ['qoderclicn', '--resume', id] : null + case 'qwen-code': + return providerSession.key === 'session_id' ? ['qwen', '--resume', id] : null + case 'qoder': + return providerSession.key === 'session_id' ? ['qodercli', '--resume', id] : null + case 'gemini': + return providerSession.key === 'session_id' ? ['gemini', '--resume', id] : null + case 'antigravity': + return providerSession.key === 'conversation_id' ? ['agy', '--conversation', id] : null + case 'opencode': + return providerSession.key === 'session_id' ? ['opencode', '--session', id] : null + case 'opencode2': + return providerSession.key === 'session_id' + ? ['opencode2', '--standalone', '--session', id] + : null + case 'pi': + return providerSession.key === 'session_id' && providerSession.transcriptPath + ? ['pi', '--session', providerSession.transcriptPath] + : null + case 'prime-agent': + return providerSession.key === 'session_id' && providerSession.transcriptPath + ? ['prime-agent', '--resume', providerSession.transcriptPath] + : null + case 'mimo-code': + return providerSession.key === 'session_id' ? ['mimo', '--session', id] : null + case 'droid': + return providerSession.key === 'session_id' ? ['droid', '--resume', id] : null + case 'grok': + return providerSession.key === 'session_id' ? ['grok', '--resume', id] : null + case 'devin': + return providerSession.key === 'session_id' ? ['devin', '--resume', id] : null + case 'omp': + return providerSession.key === 'session_id' + ? [ + 'omp', + '--resume', + ompResumeFilePath?.trim() || providerSession.transcriptPath?.trim() || id + ] + : null + // Why: the joined form is the only one Copilot documents, and it matches the + // flag spelling buildAgentResumeInvocation bakes into persisted AI Vault + // resume commands, so local and remote resumes agree on one spelling. + case 'copilot': + return providerSession.key === 'session_id' ? ['copilot', `--resume=${id}`] : null + // Why: Kimi resumes by id with --session; sessions are work-dir-scoped (enforced by callers). + case 'kimi': + return providerSession.key === 'session_id' ? ['kimi', '--session', id] : null + case 'muse': + return providerSession.key === 'session_id' ? ['muse', 'resume', id] : null + case 'zcode': + return providerSession.key === 'session_id' ? ['zcode', '--resume', id] : null + // Why: `dsh-tui --resume ` re-enters the session the launcher recorded for this + // workspace. DSH keys sessions by workspace path, so callers must keep the cwd. + case 'dsh': + return providerSession.key === 'session_id' ? ['dsh-tui', '--resume', id] : null + case 'jcode': + return providerSession.key === 'session_id' ? ['jcode', '--resume', id] : null + } +} diff --git a/src/shared/agent-session-resume.ts b/src/shared/agent-session-resume.ts index e51761540c8..ba1f5574ab1 100644 --- a/src/shared/agent-session-resume.ts +++ b/src/shared/agent-session-resume.ts @@ -285,78 +285,5 @@ export function extractAgentProviderSession( } } -export function getAgentResumeArgv( - agent: ResumableTuiAgent, - providerSession: AgentProviderSessionMetadata, - ompResumeFilePath?: string | null -): string[] | null { - const id = providerSession.id - switch (agent) { - case 'codebuddy': - return providerSession.key === 'session_id' ? ['codebuddy', '--resume', id] : null - case 'claude': - return providerSession.key === 'session_id' ? ['claude', '--resume', id] : null - case 'cursor': - return providerSession.key === 'conversation_id' ? ['cursor-agent', '--resume', id] : null - case 'codex': - return providerSession.key === 'session_id' ? ['codex', 'resume', id] : null - case 'qoder-cn': - return providerSession.key === 'session_id' ? ['qoderclicn', '--resume', id] : null - case 'qwen-code': - return providerSession.key === 'session_id' ? ['qwen', '--resume', id] : null - case 'qoder': - return providerSession.key === 'session_id' ? ['qodercli', '--resume', id] : null - case 'gemini': - return providerSession.key === 'session_id' ? ['gemini', '--resume', id] : null - case 'antigravity': - return providerSession.key === 'conversation_id' ? ['agy', '--conversation', id] : null - case 'opencode': - return providerSession.key === 'session_id' ? ['opencode', '--session', id] : null - case 'opencode2': - return providerSession.key === 'session_id' - ? ['opencode2', '--standalone', '--session', id] - : null - case 'pi': - return providerSession.key === 'session_id' && providerSession.transcriptPath - ? ['pi', '--session', providerSession.transcriptPath] - : null - case 'prime-agent': - return providerSession.key === 'session_id' && providerSession.transcriptPath - ? ['prime-agent', '--resume', providerSession.transcriptPath] - : null - case 'mimo-code': - return providerSession.key === 'session_id' ? ['mimo', '--session', id] : null - case 'droid': - return providerSession.key === 'session_id' ? ['droid', '--resume', id] : null - case 'grok': - return providerSession.key === 'session_id' ? ['grok', '--resume', id] : null - case 'devin': - return providerSession.key === 'session_id' ? ['devin', '--resume', id] : null - case 'omp': - return providerSession.key === 'session_id' - ? [ - 'omp', - '--resume', - ompResumeFilePath?.trim() || providerSession.transcriptPath?.trim() || id - ] - : null - // Why: the joined form is the only one Copilot documents, and it matches the - // flag spelling buildAgentResumeInvocation bakes into persisted AI Vault - // resume commands, so local and remote resumes agree on one spelling. - case 'copilot': - return providerSession.key === 'session_id' ? ['copilot', `--resume=${id}`] : null - // Why: Kimi resumes by id with --session; sessions are work-dir-scoped (enforced by callers). - case 'kimi': - return providerSession.key === 'session_id' ? ['kimi', '--session', id] : null - case 'muse': - return providerSession.key === 'session_id' ? ['muse', 'resume', id] : null - case 'zcode': - return providerSession.key === 'session_id' ? ['zcode', '--resume', id] : null - // Why: `dsh-tui --resume ` re-enters the session the launcher recorded for this - // workspace. DSH keys sessions by workspace path, so callers must keep the cwd. - case 'dsh': - return providerSession.key === 'session_id' ? ['dsh-tui', '--resume', id] : null - case 'jcode': - return providerSession.key === 'session_id' ? ['jcode', '--resume', id] : null - } -} +// Re-exported so the 18 existing call sites keep one import path. +export { getAgentResumeArgv } from './agent-resume-argv'