mirror of
https://github.com/stablyai/orca.git
synced 2026-10-26 00:03:55 +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>
108 lines
3.4 KiB
TypeScript
108 lines
3.4 KiB
TypeScript
import { hasFlag } from './agent-cli-flag-detection'
|
|
import type {
|
|
AgentSessionOptionCatalog,
|
|
CatalogModel,
|
|
CatalogOption
|
|
} from './agent-session-option-catalog-types'
|
|
import { removeAgentArgOption } from './agent-session-option-agent-args'
|
|
|
|
const hasModelFlag = (tokens: readonly string[]): boolean => hasFlag(tokens, ['-m', '--model'])
|
|
|
|
export const GEMINI_SESSION_OPTION_CATALOG: AgentSessionOptionCatalog = {
|
|
models: [
|
|
{ id: 'gemini-3-pro-preview', label: 'Gemini 3 Pro Preview', options: [] },
|
|
{ id: 'gemini-3-flash-preview', label: 'Gemini 3 Flash Preview', options: [] },
|
|
{ id: 'gemini-2.5-pro', label: 'Gemini 2.5 Pro', options: [] },
|
|
{ id: 'gemini-2.5-flash', label: 'Gemini 2.5 Flash', options: [] }
|
|
],
|
|
modelApply: {
|
|
launchArgs: (value) => ['-m', String(value)],
|
|
agentArgsOverride: hasModelFlag,
|
|
midSession: { kind: 'agent-picker', command: '/model' }
|
|
}
|
|
}
|
|
|
|
const CURSOR_EFFORT: CatalogOption = {
|
|
id: 'effort',
|
|
label: 'Effort',
|
|
category: 'thought_level',
|
|
kind: {
|
|
type: 'select',
|
|
choices: [
|
|
{ value: 'low', label: 'Low' },
|
|
{ value: 'medium', label: 'Medium' },
|
|
{ value: 'high', label: 'High' }
|
|
],
|
|
defaultValue: 'high'
|
|
},
|
|
apply: { composedIntoModel: true }
|
|
}
|
|
|
|
const CURSOR_FAST: CatalogOption = {
|
|
id: 'fastMode',
|
|
label: 'Fast mode',
|
|
category: 'mode',
|
|
kind: { type: 'boolean', defaultValue: false },
|
|
apply: { composedIntoModel: true }
|
|
}
|
|
|
|
const CURSOR_THINKING: CatalogOption = {
|
|
id: 'thinking',
|
|
label: 'Thinking',
|
|
category: 'model_config',
|
|
kind: { type: 'boolean', defaultValue: true },
|
|
apply: { composedIntoModel: true }
|
|
}
|
|
|
|
function parseCursorModels(stdout: string): CatalogModel[] {
|
|
const seen = new Set<string>()
|
|
const models: CatalogModel[] = []
|
|
for (const line of stdout.split(/\r?\n/)) {
|
|
const match = line.trim().match(/^(?:[-*]\s+)?([a-z0-9][a-z0-9._-]*)(?:\s+\(.*\))?$/i)
|
|
const id = match?.[1]
|
|
if (!id || id.toLowerCase() === 'models' || seen.has(id)) {
|
|
continue
|
|
}
|
|
seen.add(id)
|
|
models.push({ id, label: id === 'auto' ? 'Auto' : id, options: [] })
|
|
}
|
|
return models
|
|
}
|
|
|
|
export const CURSOR_SESSION_OPTION_CATALOG: AgentSessionOptionCatalog = {
|
|
supportsWorkerLaunchPreferences: true,
|
|
models: [
|
|
{ id: 'auto', label: 'Auto', isDefault: true, options: [] },
|
|
{
|
|
id: 'gpt-5.3-codex',
|
|
label: 'GPT-5.3 Codex',
|
|
options: [CURSOR_EFFORT, CURSOR_FAST]
|
|
},
|
|
{
|
|
id: 'claude-opus-4-8',
|
|
label: 'Claude Opus 4.8',
|
|
options: [CURSOR_THINKING, CURSOR_EFFORT]
|
|
}
|
|
],
|
|
modelApply: {
|
|
launchArgs: (value) => ['--model', String(value)],
|
|
agentArgsOverride: hasModelFlag,
|
|
removeAgentArgs: (tokens) => removeAgentArgOption(tokens, ['-m', '--model']),
|
|
midSession: { kind: 'command', build: (value) => `/model ${String(value)}` }
|
|
},
|
|
composeModelValue: (modelId, values) => {
|
|
if (modelId === 'auto') {
|
|
return modelId
|
|
}
|
|
if (modelId.startsWith('claude-')) {
|
|
const thinking = values.thinking === true ? '-thinking' : ''
|
|
const effort = typeof values.effort === 'string' ? `-${values.effort}` : ''
|
|
return `${modelId}${thinking}${effort}`
|
|
}
|
|
const effort = typeof values.effort === 'string' ? `-${values.effort}` : ''
|
|
const fast = values.fastMode === true ? '-fast' : ''
|
|
return `${modelId}${effort}${fast}`
|
|
},
|
|
listModels: { command: 'cursor-agent models', parse: parseCursorModels }
|
|
}
|