mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 17:17:38 +00:00
* fix(claude): keep Orca's background Claude runs off the user's MCP servers and hooks The model picker and / menu listings, chat naming, and commit message, PR, branch and Jira text generation each start a short Claude run. Every one but the / probe loaded the user's MCP servers and hooks, so each run started every server (one cut off a Notion OAuth refresh and lost the sign-in) and fired SessionStart hooks. Every background run now passes --strict-mcp-config and disableAllHooks. A user's own inline --settings merges into the one settings value Claude keeps, and their --mcp-config still loads. When Claude refuses strict mode because an enterprise MCP config is present (which already keeps user servers out), the run is retried once without the flag. Tracked in STA-11095 and #30411. * fix: centralize background Claude generation and retry policy * fix: rebuild Claude background retries from generated arguments * test: include branch in generation retry fixture * fix: preserve user settings arguments in background generation * test: define retry fixture streams across runtimes
251 lines
8.1 KiB
TypeScript
251 lines
8.1 KiB
TypeScript
import { claudeBackgroundArgs } from './claude-background-run-isolation'
|
|
import type { TuiAgent } from './tui-agent'
|
|
import type {
|
|
CommitMessageAgentSpec,
|
|
CommitMessageModel,
|
|
ThinkingLevel
|
|
} from './commit-message-agent-spec'
|
|
import { CLAUDE_MODEL_LIST_ARGS, CLAUDE_MODEL_LIST_STDIN } from './claude-model-list-probe'
|
|
|
|
type PrimaryAgentSpecDeps = {
|
|
CLAUDE_THINKING_LEVELS: ThinkingLevel[]
|
|
OPENAI_THINKING_LEVELS: ThinkingLevel[]
|
|
parseClaudeModels: (stdout: string) => CommitMessageModel[]
|
|
parseCodexModels: (stdout: string) => CommitMessageModel[]
|
|
parseLineModels: (stdout: string) => CommitMessageModel[]
|
|
parsePiModels: (stdout: string) => CommitMessageModel[]
|
|
withOpenAiThinking: (
|
|
id: string
|
|
) => Pick<CommitMessageModel, 'thinkingLevels' | 'defaultThinkingLevel'>
|
|
}
|
|
|
|
export function buildPrimaryCommitMessageAgentSpecs({
|
|
CLAUDE_THINKING_LEVELS,
|
|
OPENAI_THINKING_LEVELS,
|
|
parseClaudeModels,
|
|
parseCodexModels,
|
|
parseLineModels,
|
|
parsePiModels,
|
|
withOpenAiThinking
|
|
}: PrimaryAgentSpecDeps): Partial<Record<TuiAgent, CommitMessageAgentSpec>> {
|
|
return {
|
|
claude: {
|
|
id: 'claude',
|
|
label: 'Claude',
|
|
binary: 'claude',
|
|
// Why: diffs can be large and `claude -p` reads from stdin natively when no
|
|
// positional prompt is provided.
|
|
promptDelivery: 'stdin',
|
|
buildArgs: ({ model, thinkingLevel, strictMcp, userArgs }) => [
|
|
'-p',
|
|
'--output-format',
|
|
'text',
|
|
'--model',
|
|
model,
|
|
'--permission-mode',
|
|
'plan',
|
|
...(thinkingLevel ? ['--effort', thinkingLevel] : []),
|
|
...claudeBackgroundArgs(strictMcp, userArgs)
|
|
],
|
|
singletonOptions: [['--model']],
|
|
modelSource: 'dynamic',
|
|
// Why: the Claude CLI has no listing subcommand; one list_models control
|
|
// request over --print stream-json returns the /model picker catalog.
|
|
// Older CLIs answer with a control error and exit 0, keeping the fallback.
|
|
modelDiscovery: {
|
|
binary: 'claude',
|
|
args: [...CLAUDE_MODEL_LIST_ARGS],
|
|
stdinPayload: CLAUDE_MODEL_LIST_STDIN,
|
|
parse: parseClaudeModels
|
|
},
|
|
models: [
|
|
{
|
|
// Why: Claude Code aliases track the account/provider's supported
|
|
// model IDs; hardcoded version IDs can be rejected by Bedrock/Vertex.
|
|
id: 'haiku',
|
|
label: 'Haiku'
|
|
},
|
|
{
|
|
id: 'sonnet',
|
|
label: 'Sonnet',
|
|
thinkingLevels: CLAUDE_THINKING_LEVELS,
|
|
defaultThinkingLevel: 'low'
|
|
},
|
|
{
|
|
id: 'opus',
|
|
label: 'Opus',
|
|
thinkingLevels: CLAUDE_THINKING_LEVELS,
|
|
defaultThinkingLevel: 'low'
|
|
}
|
|
],
|
|
defaultModelId: 'sonnet'
|
|
},
|
|
codex: {
|
|
id: 'codex',
|
|
label: 'Codex',
|
|
binary: 'codex',
|
|
// Why: `codex exec` reads stdin when no prompt arg is supplied. Commit
|
|
// prompts include large staged diffs, so argv would exceed Windows and
|
|
// some SSH/POSIX command-line limits.
|
|
promptDelivery: 'stdin',
|
|
buildArgs: ({ model, thinkingLevel }) => [
|
|
'exec',
|
|
// Why: commit-message generation needs text only, not a persisted agent
|
|
// session or workspace writes. Match the safe git-text mode used by
|
|
// local-first coding agents.
|
|
'--ephemeral',
|
|
'--skip-git-repo-check',
|
|
'-s',
|
|
'read-only',
|
|
'--model',
|
|
model,
|
|
...(thinkingLevel ? ['-c', `model_reasoning_effort=${thinkingLevel}`] : [])
|
|
],
|
|
// `-c` is intentionally absent: Codex accepts repeated overrides.
|
|
singletonOptions: [['--model', '-m']],
|
|
modelSource: 'dynamic',
|
|
modelDiscovery: {
|
|
binary: 'codex',
|
|
args: ['debug', 'models'],
|
|
parse: parseCodexModels
|
|
},
|
|
// Why: ordered to match the official `codex` model picker — descending
|
|
// by version so the frontier model lands on top and legacy models trail.
|
|
models: [
|
|
{
|
|
id: 'gpt-5.6-terra',
|
|
label: 'GPT-5.6 Terra',
|
|
thinkingLevels: OPENAI_THINKING_LEVELS,
|
|
defaultThinkingLevel: 'low'
|
|
},
|
|
{
|
|
id: 'gpt-5.5',
|
|
label: 'GPT-5.5',
|
|
thinkingLevels: OPENAI_THINKING_LEVELS,
|
|
defaultThinkingLevel: 'low'
|
|
},
|
|
{
|
|
id: 'gpt-5.4',
|
|
label: 'GPT-5.4',
|
|
thinkingLevels: OPENAI_THINKING_LEVELS,
|
|
defaultThinkingLevel: 'low'
|
|
},
|
|
{
|
|
id: 'gpt-5.4-mini',
|
|
label: 'GPT-5.4 Mini',
|
|
thinkingLevels: OPENAI_THINKING_LEVELS,
|
|
defaultThinkingLevel: 'low'
|
|
},
|
|
{
|
|
id: 'gpt-5.3-codex',
|
|
label: 'GPT-5.3 Codex',
|
|
thinkingLevels: OPENAI_THINKING_LEVELS,
|
|
defaultThinkingLevel: 'low'
|
|
},
|
|
{
|
|
// Why: Codex's Spark variant accepts `model_reasoning_effort` (the
|
|
// CLI banner reports "reasoning effort: medium" by default); the
|
|
// gating that surfaces "model not supported" is on the account
|
|
// tier, not the effort flag.
|
|
id: 'gpt-5.3-codex-spark',
|
|
label: 'GPT-5.3 Codex Spark',
|
|
thinkingLevels: OPENAI_THINKING_LEVELS,
|
|
defaultThinkingLevel: 'low'
|
|
},
|
|
{
|
|
id: 'gpt-5.2',
|
|
label: 'GPT-5.2',
|
|
thinkingLevels: OPENAI_THINKING_LEVELS,
|
|
defaultThinkingLevel: 'low'
|
|
}
|
|
],
|
|
defaultModelId: 'gpt-5.6-terra'
|
|
},
|
|
opencode: {
|
|
id: 'opencode',
|
|
label: 'OpenCode',
|
|
binary: 'opencode',
|
|
// Why: Source Control AI prompts can include large staged diffs; OpenCode
|
|
// accepts the prompt on stdin, which avoids cross-platform argv limits.
|
|
promptDelivery: 'stdin',
|
|
buildArgs: ({ model, thinkingLevel }) => [
|
|
'run',
|
|
...(model && model !== 'default' ? ['--model', model] : []),
|
|
'--agent',
|
|
'build',
|
|
'--format',
|
|
'json',
|
|
...(thinkingLevel ? ['--variant', thinkingLevel] : [])
|
|
],
|
|
singletonOptions: [['--model', '-m'], ['--agent'], ['--format'], ['--variant']],
|
|
modelSource: 'dynamic',
|
|
modelDiscovery: { binary: 'opencode', args: ['models'], parse: parseLineModels },
|
|
models: [
|
|
{ id: 'default', label: 'Config default' },
|
|
{
|
|
id: 'opencode/gpt-5.4-mini',
|
|
label: 'OpenCode GPT 5.4 Mini',
|
|
...withOpenAiThinking('gpt-5.4-mini')
|
|
}
|
|
],
|
|
defaultModelId: 'default'
|
|
},
|
|
opencode2: {
|
|
id: 'opencode2',
|
|
label: 'OpenCode 2',
|
|
binary: 'opencode2',
|
|
promptDelivery: 'stdin',
|
|
buildArgs: ({ model, thinkingLevel }) => [
|
|
'run',
|
|
...(model && model !== 'default'
|
|
? ['--model', thinkingLevel ? `${model}#${thinkingLevel}` : model]
|
|
: []),
|
|
'--agent',
|
|
'build',
|
|
'--format',
|
|
'json'
|
|
],
|
|
singletonOptions: [['--model', '-m'], ['--agent'], ['--format']],
|
|
modelSource: 'dynamic',
|
|
modelDiscovery: { binary: 'opencode2', args: ['models'], parse: parseLineModels },
|
|
models: [
|
|
{ id: 'default', label: 'Config default' },
|
|
{
|
|
id: 'opencode/gpt-5.4-mini',
|
|
label: 'OpenCode GPT 5.4 Mini',
|
|
...withOpenAiThinking('gpt-5.4-mini')
|
|
}
|
|
],
|
|
defaultModelId: 'default'
|
|
},
|
|
pi: {
|
|
id: 'pi',
|
|
label: 'Pi',
|
|
binary: 'pi',
|
|
promptDelivery: 'stdin',
|
|
buildArgs: ({ model, thinkingLevel }) => [
|
|
'--print',
|
|
'--no-session',
|
|
'--no-tools',
|
|
'--no-skills',
|
|
'--no-context-files',
|
|
'--mode',
|
|
'text',
|
|
...(model && model !== 'default' ? ['--model', model] : []),
|
|
...(thinkingLevel ? ['--thinking', thinkingLevel] : [])
|
|
],
|
|
modelSource: 'dynamic',
|
|
modelDiscovery: { binary: 'pi', args: ['--list-models'], parse: parsePiModels },
|
|
models: [
|
|
{
|
|
// Why: the unqualified choice lets Pi use its configured provider and
|
|
// avoids forcing GitHub Copilot credentials during automation.
|
|
id: 'default',
|
|
label: 'Config default'
|
|
}
|
|
],
|
|
defaultModelId: 'default'
|
|
}
|
|
}
|
|
}
|