mirror of
https://github.com/stablyai/orca.git
synced 2026-10-22 16:03:47 +00:00
* feat(native-chat): add model and effort pickers for grok Grok had no session-option catalog, so the native chat composer showed no pills and every launch ran the CLI's own defaults with no way to change them. Adds a `GROK_SESSION_OPTION_CATALOG` (model via `-m`/`/model`, reasoning effort via `--reasoning-effort`/`/effort`) and the discovery plumbing behind it. Grok's selectable ids depend on the signed-in account and on `[model.*]` config, so the seed carries only `grok-4.5` and a runtime `grok models` probe supplies the rest as authoritative — a retired id must be droppable, since launching one is a fatal exit rather than a warning. Because `grok models` publishes `Default model:` and marks the row `(default)`, the picker can name the model a fresh session is actually running: `defaultModelIsCliDefault` plus an untracked record means no `-m` was ever emitted, so the CLI is on its own default. That default scopes the effort row but is never written to persisted settings — that field is what authorizes `-m` on every later launch, and adopting a model the user never picked would pin today's default forever, fatally so on an account without it. `grok --help` publishes no default for `--reasoning-effort`, so the effort value stays unnamed until something sets it. Known gap: that refusal to persist is also a limit. An option set while on the CLI default is dispatched and honored in-session, but reaches no later launch — it persists under the default's id with `model` left unset, and both `resolveNativeChatSessionOptionDefaults` and `resolveAgentSessionOptionLaunch` bail without that key. Picking a model explicitly persists normally. Closing this means teaching both to resolve options from the default model while still refusing to emit `-m`, which is the launch-args path and wants its own review. Known gap: the picker infers "no `-m` was emitted" from its own in-memory record, so a model reaching argv from outside it — the user's own `agentDefaultArgs`, or a renderer reload that drops the record while the flagged PTY lives on — leaves the pill claiming the CLI default while another model runs. No wrong model is persisted. Extracts `hasFlag` and `labelFromModelId`, and splits the model-probe spec out of the commit-message registry so discovery no longer implies an agent can write commit messages. Co-authored-by: Orca <help@stably.ai> * docs(native-chat): note the invariant keeping modelIsCliDefault agent-safe The flag is computed without checking the catalog, so it reads as unsafe for the four agents with no CLI default. It is safe only because `persist` bails unless `modelId` is truthy, which for those agents implies a tracked model. Widening that guard would silently change persistence for every agent. Co-authored-by: Orca <help@stably.ai> * fix: retire persisted models on mount and handle -- terminator - When a pane mounts after model discovery has already settled, it now checks the cache and retires persisted models that are no longer available. - CLI flag detection now respects the `--` option terminator, treating everything after it as positional arguments rather than flags. * Fix: persist grok session options under probe-confirmed defaults Options set under the CLI default were silently lost on restart. Distinguish seed guesses from probe-confirmed defaults by renaming `modelIsCliDefault` to `modelIsUnverifiedDefault`. Once confirmed, adopt the default as a persisted flag so options survive restarts. * fix(native-chat): close the retired-model fatal-launch paths from counsel review Counsel report C1/C2 (High), C3, P1, C4: - Untrack a session model an authoritative discovery dropped and gate every persist path, so option writes can never re-adopt a retired id (C1). - Resolve launch defaults through the enrichment cache: a persisted model missing from every settled probe no longer becomes a fatal `-m` (C2). - Serialize retirement and picks on one settings write queue that re-reads live state at apply time (C3). - Stabilize onSwitchToTerminal so the session-option surface is not rebuilt every TerminalPane render (P1), and cap the enrichment host map (C4). Co-authored-by: Orca <help@stably.ai> * Store agent in enrichment entry and extract token utilities Refactor enrichment to store the agent field directly instead of parsing it from a composite key, and extract CLI flag token filtering into a shared utility. Use a dedicated function for tracked model ID lookup. Improves code reuse and reduces parsing overhead. * Rename modelIsUnverifiedDefault to adoptModelAsLaunchDefault Move the model adoption gate into the core session-options module, where probe confirmation and discovered-model status are known. This ensures adoption decisions are gate-checked before persisting to avoid fatal launch flags, and simplifies the picker surface by moving the logic to where it belongs. * Keep model probe evidence by agent, not host Store probed model IDs in agent-keyed cache independent of host cache, so evidence persists across host eviction. Prevents retired models from being treated as valid when host cache entries are evicted. * Store agent in enrichment entries instead of separate proof-evidence map Model probe evidence is now tied to enrichment entries rather than maintained in a separate per-agent map, eliminating the need for eviction logic that could disconnect proof from entries. --------- Co-authored-by: Orca <help@stably.ai>
146 lines
5.3 KiB
TypeScript
146 lines
5.3 KiB
TypeScript
import type { ClaudeRuntimeAuthPreparation } from '../claude-accounts/runtime-auth-service'
|
|
import { applyClaudeEnvPatch } from '../claude-accounts/environment'
|
|
import { readShellStartupEnvVar } from '../pty/shell-startup-env'
|
|
import { parseWslUncPath } from '../../shared/wsl-paths'
|
|
|
|
export type CommitMessageAgentEnvironmentResolvers = {
|
|
prepareForCodexLaunch?: (target?: CommitMessageAgentRuntimeTarget) => string | null
|
|
prepareForClaudeLaunch?: (
|
|
target?: CommitMessageAgentRuntimeTarget
|
|
) => Promise<ClaudeRuntimeAuthPreparation>
|
|
}
|
|
|
|
export type CommitMessageAgentRuntimeTarget = {
|
|
runtime?: 'host' | 'wsl'
|
|
wslDistro?: string | null
|
|
}
|
|
|
|
function cloneProcessEnv(): Record<string, string> {
|
|
const env: Record<string, string> = {}
|
|
for (const [key, value] of Object.entries(process.env)) {
|
|
if (value !== undefined) {
|
|
env[key] = value
|
|
}
|
|
}
|
|
return env
|
|
}
|
|
|
|
// Why: with system-default real-home routing, the headless Codex commit run
|
|
// must use the user's own ~/.codex. If Orca itself was launched from a nested
|
|
// Orca terminal it can inherit an Orca-owned CODEX_HOME override; strip only
|
|
// that (CODEX_HOME matching the private ORCA_CODEX_HOME marker), preserving a
|
|
// user-set CODEX_HOME.
|
|
function cloneProcessEnvWithoutOrcaCodexHomeOverride(): Record<string, string> {
|
|
const env = cloneProcessEnv()
|
|
if (env.ORCA_CODEX_HOME && env.CODEX_HOME === env.ORCA_CODEX_HOME) {
|
|
delete env.CODEX_HOME
|
|
}
|
|
delete env.ORCA_CODEX_HOME
|
|
return env
|
|
}
|
|
|
|
function readInheritedOrShellEnvVar(name: string, sourceName?: string): string | undefined {
|
|
return (
|
|
(sourceName ? process.env[sourceName] : undefined) ??
|
|
process.env[name] ??
|
|
readShellStartupEnvVar(name, process.env.HOME, process.env.SHELL)
|
|
)
|
|
}
|
|
|
|
function prepareShellConfigDirEnv(agentId: string): { ok: true; env?: NodeJS.ProcessEnv } | null {
|
|
const configVar =
|
|
agentId === 'opencode'
|
|
? 'OPENCODE_CONFIG_DIR'
|
|
: agentId === 'pi' || agentId === 'omp'
|
|
? 'PI_CODING_AGENT_DIR'
|
|
: agentId === 'grok'
|
|
? 'GROK_HOME'
|
|
: null
|
|
if (!configVar) {
|
|
return null
|
|
}
|
|
// Why: each kind owns a distinct ORCA_*_SOURCE_* shadow so a headless commit
|
|
// run from inside a legacy OMP overlay restores the OMP source dir, never
|
|
// the Pi one (and vice versa). PI_CODING_AGENT_DIR is the binary-facing var
|
|
// both kinds consume — see src/main/pi/titlebar-extension-service.ts.
|
|
const sourceVar =
|
|
agentId === 'opencode'
|
|
? 'ORCA_OPENCODE_SOURCE_CONFIG_DIR'
|
|
: agentId === 'pi'
|
|
? 'ORCA_PI_SOURCE_AGENT_DIR'
|
|
: agentId === 'omp'
|
|
? 'ORCA_OMP_SOURCE_AGENT_DIR'
|
|
: undefined
|
|
|
|
const value = readInheritedOrShellEnvVar(configVar, sourceVar)
|
|
if (!value) {
|
|
return { ok: true }
|
|
}
|
|
|
|
// Why: GUI-launched Orca may not inherit shell startup exports, but these
|
|
// vars point the headless CLI at the user's auth/config root. Nested Orca
|
|
// launches inherit PTY overlays, so prefer ORCA_*_SOURCE_* when present.
|
|
return { ok: true, env: { ...cloneProcessEnv(), [configVar]: value } }
|
|
}
|
|
|
|
export async function prepareLocalCommitMessageAgentEnv(
|
|
agentId: string,
|
|
resolvers: CommitMessageAgentEnvironmentResolvers | undefined,
|
|
target?: CommitMessageAgentRuntimeTarget
|
|
): Promise<{ ok: true; env?: NodeJS.ProcessEnv } | { ok: false; error: string }> {
|
|
// Why: a non-null result short-circuits the resolvers below, so any agent added
|
|
// to prepareShellConfigDirEnv must not also need a Codex/Claude-style resolver.
|
|
const shellConfigEnv = target?.runtime === 'wsl' ? null : prepareShellConfigDirEnv(agentId)
|
|
if (shellConfigEnv) {
|
|
return shellConfigEnv
|
|
}
|
|
if (!resolvers) {
|
|
return { ok: true }
|
|
}
|
|
|
|
try {
|
|
if (agentId === 'codex' && resolvers.prepareForCodexLaunch) {
|
|
const codexHomePath = resolvers.prepareForCodexLaunch(target)
|
|
const wslCodexHome = codexHomePath ? parseWslUncPath(codexHomePath) : null
|
|
if (target?.runtime === 'wsl') {
|
|
const codexHomeForTarget = wslCodexHome?.linuxPath ?? null
|
|
// Why: the fallback must still strip Orca-owned overrides, or a
|
|
// system-default WSL run inherits the managed CODEX_HOME.
|
|
return {
|
|
ok: true,
|
|
env: codexHomeForTarget
|
|
? { ...cloneProcessEnvWithoutOrcaCodexHomeOverride(), CODEX_HOME: codexHomeForTarget }
|
|
: cloneProcessEnvWithoutOrcaCodexHomeOverride()
|
|
}
|
|
}
|
|
if (codexHomePath && wslCodexHome) {
|
|
// Why: this local generation path spawns the host Codex binary. A WSL
|
|
// managed home is only valid when the process is routed through wsl.exe.
|
|
return { ok: true }
|
|
}
|
|
return {
|
|
ok: true,
|
|
env: codexHomePath
|
|
? { ...cloneProcessEnv(), CODEX_HOME: codexHomePath }
|
|
: cloneProcessEnvWithoutOrcaCodexHomeOverride()
|
|
}
|
|
}
|
|
|
|
if (agentId === 'claude' && resolvers.prepareForClaudeLaunch) {
|
|
const preparation = await resolvers.prepareForClaudeLaunch(target)
|
|
const env = applyClaudeEnvPatch(cloneProcessEnv(), preparation.envPatch, {
|
|
stripAuthEnv: preparation.stripAuthEnv
|
|
})
|
|
return { ok: true, env }
|
|
}
|
|
} catch (error) {
|
|
console.error('[commit-message] Failed to prepare agent environment:', error)
|
|
return {
|
|
ok: false,
|
|
error: 'Failed to prepare the selected agent account for commit message generation.'
|
|
}
|
|
}
|
|
|
|
return { ok: true }
|
|
}
|