From ce2d725460812e05bb84e8e0a2c782403c519ba0 Mon Sep 17 00:00:00 2001 From: Serhii Vecherenko Date: Sun, 2 Aug 2026 19:40:31 -0700 Subject: [PATCH] Update Grok for 0.2.118 --- .../components/providers/grok/index.tsx | 19 +++++----- src/supervisor/agents/commandBuilders.test.ts | 9 ++--- src/supervisor/agents/grok/argv.test.ts | 17 ++++++--- src/supervisor/agents/grok/argv.ts | 10 +++--- src/supervisor/agents/grok/detection.test.ts | 10 +++--- src/supervisor/agents/grok/detection.ts | 15 ++++---- src/supervisor/agents/grok/grok.test.ts | 19 +++++++++- src/supervisor/agents/grok/index.ts | 14 ++++---- .../agents/grok/sessionFiles.test.ts | 13 +++++++ src/supervisor/agents/grok/sessionFiles.ts | 36 ++++++++++++++----- 10 files changed, 106 insertions(+), 56 deletions(-) diff --git a/src/renderer/components/providers/grok/index.tsx b/src/renderer/components/providers/grok/index.tsx index b81f7f96a..8614a88a7 100644 --- a/src/renderer/components/providers/grok/index.tsx +++ b/src/renderer/components/providers/grok/index.tsx @@ -13,23 +13,20 @@ const PROVIDER_KIND = providerManifest.kind; registerProviderIcon(PROVIDER_KIND, GrokIcon); -// Grok has two models, both covered by the subscription (no per-token cost), so -// pick by fit: `grok-composer-2.5-fast` is Grok's own default — fast, ideal for -// the lightweight title/commit one-shots; the frontier `grok-4.5` takes the -// conflict resolver, which is a real code-editing session. (The old `grok-build` -// model id was retired upstream — the 0.2.x catalog is grok-4.5 + -// grok-composer-2.5-fast.) +// Grok 0.2.118 advertises grok-4.5 as its supported model. Keep utility +// defaults aligned with the live catalog so background generations do not +// fail with an unknown model id. registerCommitGenDefaults(PROVIDER_KIND, { label: "Grok", - hint: "Composer 2.5 Fast", - model: "grok-composer-2.5-fast", + hint: "Grok 4.5", + model: "grok-4.5", effort: "", }); registerTitleGenDefaults(PROVIDER_KIND, { label: "Grok", - hint: "Composer 2.5 Fast", - model: "grok-composer-2.5-fast", + hint: "Grok 4.5", + model: "grok-4.5", effort: "", }); @@ -42,7 +39,7 @@ registerConflictResolverDefaults(PROVIDER_KIND, { // Composer surface for Grok: a single Default ↔ Bypass Approvals toggle. // Plan mode is intentionally absent — it is not driveable from launch flags -// (re-verified on grok 0.2.93). Effort needs no control here: the shared +// (re-verified on grok 0.2.118). Effort needs no control here: the shared // model picker reads `capabilities.modelEfforts` filled by the ACP probe. // See `supervisor/agents/grok/detection.ts` and `supervisor/agents/grok/argv.ts`. registerComposerControls(PROVIDER_KIND, ({ capabilities, config, isDisabled, onConfigChange }) => { diff --git a/src/supervisor/agents/commandBuilders.test.ts b/src/supervisor/agents/commandBuilders.test.ts index ae98125e1..028e04938 100644 --- a/src/supervisor/agents/commandBuilders.test.ts +++ b/src/supervisor/agents/commandBuilders.test.ts @@ -454,14 +454,10 @@ describe("agent command builders", () => { it("builds a Grok one-shot command via the headless `grok -p` path", () => { expect( - createGrokAdapter().buildOneShotCommand?.( - "grok-composer-2.5-fast", - undefined, - "Summarize this diff", - ), + createGrokAdapter().buildOneShotCommand?.("grok-4.5", undefined, "Summarize this diff"), ).toEqual({ command: "grok", - args: ["-p", "Summarize this diff", "-m", "grok-composer-2.5-fast", "--always-approve"], + args: ["--no-auto-update", "-p", "Summarize this diff", "-m", "grok-4.5", "--always-approve"], stdin: "", }); }); @@ -538,6 +534,7 @@ describe("agent command builders", () => { ).toEqual({ command: "grok", args: [ + "--no-auto-update", "-p", "Summarize this diff", "-m", diff --git a/src/supervisor/agents/grok/argv.test.ts b/src/supervisor/agents/grok/argv.test.ts index 9f5cc6a3f..22a812ef4 100644 --- a/src/supervisor/agents/grok/argv.test.ts +++ b/src/supervisor/agents/grok/argv.test.ts @@ -98,8 +98,8 @@ describe("buildGrokArgs (TUI/PTY)", () => { }); describe("buildGrokAcpArgs (`grok agent stdio` prefix)", () => { - it("emits nothing for a bare default config", () => { - expect(buildGrokAcpArgs({} as any)).toEqual([]); + it("disables auto-update for a bare default config", () => { + expect(buildGrokAcpArgs({} as any)).toEqual(["--no-auto-update"]); }); it("never emits --permission-mode or --no-plan", () => { @@ -113,16 +113,25 @@ describe("buildGrokAcpArgs (`grok agent stdio` prefix)", () => { }); it("forwards config.effort as --reasoning-effort", () => { - expect(buildGrokAcpArgs({ effort: "medium" } as any)).toEqual(["--reasoning-effort", "medium"]); + expect(buildGrokAcpArgs({ effort: "medium" } as any)).toEqual([ + "--no-auto-update", + "--reasoning-effort", + "medium", + ]); }); it("adds --always-approve when approval policy bypasses permissions", () => { expect(buildGrokAcpArgs({ approvalPolicy: "bypassPermissions" } as any)).toEqual([ + "--no-auto-update", "--always-approve", ]); }); it("passes -m when set", () => { - expect(buildGrokAcpArgs({ model: "grok-4.5" } as any)).toEqual(["-m", "grok-4.5"]); + expect(buildGrokAcpArgs({ model: "grok-4.5" } as any)).toEqual([ + "--no-auto-update", + "-m", + "grok-4.5", + ]); }); }); diff --git a/src/supervisor/agents/grok/argv.ts b/src/supervisor/agents/grok/argv.ts index 39f5f73f7..d1768e1e0 100644 --- a/src/supervisor/agents/grok/argv.ts +++ b/src/supervisor/agents/grok/argv.ts @@ -2,7 +2,7 @@ import type { ThreadConfig } from "@/shared/contracts"; /** * Flag references — verified against `grok --help`, `grok agent --help`, and - * live PTY/ACP probes on grok 0.2.93 (2026-07-09): + * live PTY/ACP probes on grok 0.2.118 (2026-08-02): * https://docs.x.ai/build/cli/headless-scripting * https://docs.x.ai/build/modes-and-commands * @@ -21,10 +21,10 @@ import type { ThreadConfig } from "@/shared/contracts"; * • `--reasoning-effort ` is honored at TUI launch since 0.2.x * (verified live: the composer footer shows "Grok 4.5 (low)" and the * session's summary.json records `reasoning_effort`). Models that don't - * advertise `supportsReasoningEffort` (grok-composer-2.5-fast) simply - * ignore it, so we forward `config.effort` whenever it is set. + * advertise `supportsReasoningEffort` simply ignore it, so we forward + * `config.effort` whenever it is set. * • `--permission-mode ` is STILL silently ignored at launch on both - * surfaces (verified live on 0.2.93: booting the TUI with + * surfaces (verified live on 0.2.118: booting the TUI with * `--permission-mode plan` shows no "· plan" footer chip while Shift+Tab * does, and an ACP session created with it reports kind "build"). The * only approval control Grok honors at launch remains `--always-approve` @@ -103,7 +103,7 @@ export function buildGrokArgs( * Argv prefix for `grok [FLAGS] agent stdio` (ACP / GUI tab). */ export function buildGrokAcpArgs(config: ThreadConfig): string[] { - const args: string[] = []; + const args = ["--no-auto-update"]; pushSharedFlags(args, config); return args; } diff --git a/src/supervisor/agents/grok/detection.test.ts b/src/supervisor/agents/grok/detection.test.ts index e1c560573..d890e9241 100644 --- a/src/supervisor/agents/grok/detection.test.ts +++ b/src/supervisor/agents/grok/detection.test.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from "vitest"; import { buildGrokProviderMetadata, mapGrokEffortCapabilities } from "./detection"; -// Model `_meta` shapes as returned live by `grok agent stdio` 0.2.93 +// Model `_meta` shapes as returned live by `grok agent stdio` 0.2.118 // (initialize/_meta.modelState and session/new `models.availableModels[]._meta`). const GROK_45_META = { totalContextTokens: 500_000, @@ -15,7 +15,7 @@ const GROK_45_META = { ], }; -const COMPOSER_META = { +const MODEL_WITHOUT_EFFORT_META = { totalContextTokens: 200_000, agentType: "cursor", }; @@ -24,7 +24,7 @@ describe("mapGrokEffortCapabilities", () => { it("derives ascending effort tiers and the advertised default", () => { const caps = mapGrokEffortCapabilities({ "grok-4.5": GROK_45_META, - "grok-composer-2.5-fast": COMPOSER_META, + "model-without-effort": MODEL_WITHOUT_EFFORT_META, }); expect(caps.efforts).toEqual(["low", "medium", "high"]); expect(caps.defaultEffort).toBe("high"); @@ -33,11 +33,11 @@ describe("mapGrokEffortCapabilities", () => { it("gives models without tiers an explicit empty list so the picker hides effort", () => { const caps = mapGrokEffortCapabilities({ "grok-4.5": GROK_45_META, - "grok-composer-2.5-fast": COMPOSER_META, + "model-without-effort": MODEL_WITHOUT_EFFORT_META, }); expect(caps.modelEfforts).toEqual({ "grok-4.5": ["low", "medium", "high"], - "grok-composer-2.5-fast": [], + "model-without-effort": [], }); }); diff --git a/src/supervisor/agents/grok/detection.ts b/src/supervisor/agents/grok/detection.ts index d7cb993b8..0f4d78441 100644 --- a/src/supervisor/agents/grok/detection.ts +++ b/src/supervisor/agents/grok/detection.ts @@ -20,7 +20,7 @@ import { getAgentProbeCwd, resolveProbeSpawnCwd } from "../probeCwd"; // Approval policies surfaced to Poracode. Grok only honors `--always-approve` // (bypass) at launch — `--permission-mode ` is silently ignored by both -// the TUI and `grok agent stdio` (re-verified live on 0.2.93; see argv.ts). +// the TUI and `grok agent stdio` (re-verified live on 0.2.118; see argv.ts). // We therefore expose a single Default ↔ Bypass Approvals toggle in the // composer. const GROK_APPROVAL_POLICIES = [ @@ -30,7 +30,7 @@ const GROK_APPROVAL_POLICIES = [ // Plan mode is intentionally omitted from the composer surface: it cannot be // force-activated at launch on either Grok surface — `--permission-mode plan` -// is silently ignored (verified live on 0.2.93). Plan mode is entered in the +// is silently ignored (verified live on 0.2.118). Plan mode is entered in the // TUI via Shift+Tab or by the model calling `enter_plan_mode`, so a Plan/Work // toggle would falsely imply we drive it. // @@ -64,7 +64,7 @@ async function probeCapabilities( location: ProjectLocation, executablePath?: string, ): Promise { - const spec = buildGrokCommand(location, ["agent", "stdio"], executablePath); + const spec = buildGrokCommand(location, ["--no-auto-update", "agent", "stdio"], executablePath); const sessionCwd = getAgentProbeCwd(location); const processCwd = resolveProbeSpawnCwd(location, spec.cwd); const probe = await probeAcpCapabilities(spec.command, spec.args, sessionCwd, { @@ -79,7 +79,7 @@ async function probeCapabilities( }); // Extract context windows from model _meta (grok reports totalContextTokens - // per model, e.g. 500k for grok-4.5 and 200k for grok-composer-2.5-fast). + // per model, e.g. 500k for grok-4.5). let contextCaps: Pick = {}; if (probe?.modelMetadata) { const sizes = new Map(); @@ -145,10 +145,9 @@ type GrokReasoningEffortMeta = { id?: unknown; default?: unknown }; /** * Derive effort capabilities from the per-model `_meta.reasoningEfforts` the - * grok 0.2.x ACP handshake advertises (verified live on 0.2.93: grok-4.5 - * exposes high/medium/low with high as default; grok-composer-2.5-fast - * advertises none). Models without tiers get an explicit empty list so the - * shared model picker hides the effort dropdown for them. + * grok 0.2.x ACP handshake advertises (verified live on 0.2.118: grok-4.5 + * exposes high/medium/low with high as default). Models without tiers get an + * explicit empty list so the shared model picker hides the effort dropdown. */ export function mapGrokEffortCapabilities( modelMetadata: Record> | undefined, diff --git a/src/supervisor/agents/grok/grok.test.ts b/src/supervisor/agents/grok/grok.test.ts index 4de82c49e..4d3dd37f1 100644 --- a/src/supervisor/agents/grok/grok.test.ts +++ b/src/supervisor/agents/grok/grok.test.ts @@ -16,7 +16,7 @@ function oscNotify(body: string, code: 9 | 99 | 777 = 9): OscNotification { return { code, title: "", body, payload: undefined }; } -// Observed live from grok PTY captures (0.1.218, re-verified on 0.2.93 — +// Observed live from grok PTY captures (0.1.218, re-verified on 0.2.118 — // idle title is still plain "grok"): // OSC 0 "grok" (idle, frequent) // OSC 0 "⠴ - Waiting - grok" (working, braille frames ⠴ / ⠦) @@ -203,6 +203,23 @@ describe("createGrokAdapter buildLaunchArgv / buildResumeArgv session flags", () expect(result.sessionRef?.providerSessionId).toBe(SESSION_ID); }); + it("resumes a known UUID after the project directory moves", () => { + const originalProjectDir = join(tmpdir(), "grok-original-proj"); + mkdirSync(join(grokHome, "sessions", encodeURIComponent(originalProjectDir), SESSION_ID), { + recursive: true, + }); + + const adapter = createGrokAdapter(); + const result = adapter.buildLaunchArgv( + location, + config, + "", + createKnownSessionRef(SESSION_ID), + {}, + ); + expect(result.args.slice(0, 2)).toEqual(["-r", SESSION_ID]); + }); + it("buildResumeArgv applies the same materialization fallback", () => { const adapter = createGrokAdapter(); const fresh = adapter.buildResumeArgv(location, config, "", createKnownSessionRef(SESSION_ID)); diff --git a/src/supervisor/agents/grok/index.ts b/src/supervisor/agents/grok/index.ts index e043ddf09..b9bb4d41f 100644 --- a/src/supervisor/agents/grok/index.ts +++ b/src/supervisor/agents/grok/index.ts @@ -139,7 +139,7 @@ export function createGrokAdapter(): AgentAdapter { snapshotGrokPreSpawnSessions(location, cwd); // Resolve the session ID before spawning the PTY: resume a known one - // with `-r`, otherwise pre-assign a fresh UUID with `-s` (grok 0.2.93, + // with `-r`, otherwise pre-assign a fresh UUID with `-s` (grok 0.2.118, // works on native and WSL alike). Grok normally materializes the // session dir within ~1s of boot; a known id whose dir never appeared // (launch died at startup) is re-assigned via `-s` @@ -189,7 +189,7 @@ export function createGrokAdapter(): AgentAdapter { const location = detectProbeLocation(ctx); return buildGrokCommand( location, - ["agent", "stdio"], + ["--no-auto-update", "agent", "stdio"], resolveAgentBinaryPath(location, "grok"), ); }, @@ -222,7 +222,7 @@ export function createGrokAdapter(): AgentAdapter { if (t.includes("grok build")) return true; if (/type @|mention files|\/ commands/i.test(text)) return true; // 0.2.x composer footer ("Shift+Tab:mode") — present on both the - // welcome screen and resumed sessions (verified live on 0.2.93). + // welcome screen and resumed sessions (verified live on 0.2.118). if (t.includes("shift+tab")) return true; return false; }, @@ -247,12 +247,12 @@ export function createGrokAdapter(): AgentAdapter { // One-shot (title / commit) generation reuses Grok's documented headless // path: `grok -p `. `--always-approve` keeps the non-interactive run // from blocking on a tool-approval prompt it cannot answer (mirrors the - // launch/ACP bypass in argv.ts). The default is Grok's own default model — - // fast and subscription-covered, ideal for lightweight one-shots. - defaultOneShotModel: "grok-composer-2.5-fast", + // launch/ACP bypass in argv.ts). Grok 0.2.118 advertises grok-4.5 as its + // supported model, so utility runs use the same live catalog entry. + defaultOneShotModel: "grok-4.5", buildOneShotCommand(model, effort, prompt) { if (!prompt) return undefined; - const args = ["-p", prompt]; + const args = ["--no-auto-update", "-p", prompt]; if (model) args.push("-m", model); if (effort) args.push("--reasoning-effort", effort); args.push("--always-approve"); diff --git a/src/supervisor/agents/grok/sessionFiles.test.ts b/src/supervisor/agents/grok/sessionFiles.test.ts index 061b3842f..aef9ed1c1 100644 --- a/src/supervisor/agents/grok/sessionFiles.test.ts +++ b/src/supervisor/agents/grok/sessionFiles.test.ts @@ -38,6 +38,19 @@ describe("grok session materialization (native)", () => { expect(grokSessionDirMaterialized(location, projectDir, SESSION_ID)).toBe(true); }); + it("reports true after the project moves because UUID resumes are global", () => { + const originalProjectDir = join(tmpdir(), "grok-original-proj"); + mkdirSync(join(grokHome, "sessions", encodeURIComponent(originalProjectDir), SESSION_ID), { + recursive: true, + }); + + expect(grokSessionDirMaterialized(location, projectDir, SESSION_ID)).toBe(true); + expect(resolveGrokSessionArg(location, projectDir, SESSION_ID)).toEqual({ + kind: "resume", + sessionId: SESSION_ID, + }); + }); + it("resolveGrokSessionArg re-assigns unmaterialized ids with -s and resumes real ones with -r", () => { expect(resolveGrokSessionArg(location, projectDir, SESSION_ID)).toEqual({ kind: "new", diff --git a/src/supervisor/agents/grok/sessionFiles.ts b/src/supervisor/agents/grok/sessionFiles.ts index 3480996db..0e2f72337 100644 --- a/src/supervisor/agents/grok/sessionFiles.ts +++ b/src/supervisor/agents/grok/sessionFiles.ts @@ -58,6 +58,20 @@ function getGrokSessionsRoot(location: ProjectLocation): string | null { return getNativeGrokSessionsRoot(); } +function sessionExistsUnderAnyCwd( + sessionsRoot: string, + currentCwdKey: string, + sessionId: string, +): boolean { + if (existsSync(join(sessionsRoot, currentCwdKey, sessionId))) return true; + if (!existsSync(sessionsRoot)) return false; + + for (const entry of readdirSync(sessionsRoot, { withFileTypes: true })) { + if (entry.isDirectory() && existsSync(join(sessionsRoot, entry.name, sessionId))) return true; + } + return false; +} + /** * Call this from buildLaunchArgv (and optionally buildResumeArgv) immediately * before spawning the grok PTY. It records what sessions already exist for @@ -94,15 +108,17 @@ function isUuid(s: string): boolean { } /** - * True when the session directory for / exists on disk. + * True when the session directory exists anywhere in Grok's session store. * - * grok 0.2.93 normally writes the session dir within ~1s of TUI boot + * grok 0.2.118 normally writes the session dir within ~1s of TUI boot * (verified live), but a UUID we pre-assigned with `-s` can still be missing * when the launch died at startup (spawn failure, immediate kill) or the TUI * deferred to its welcome/resume menu. Callers use this to decide between * `-r ` (resume a real session) and `-s ` (re-assign the same id — * `-s` requires the session to not exist and exits 1 on a collision, so the - * choice must reflect live disk state, never a timing assumption). + * choice must reflect live disk state, never a timing assumption). Searching + * every cwd also preserves resume after a project folder moves, which Grok + * supports by locating the original session independently of the current cwd. * * Returns `undefined` when the check is unavailable (WSL distro home not * cached / UNC bridge unreachable); callers should then default to resume. @@ -115,17 +131,19 @@ export function grokSessionDirMaterialized( if (location.kind === "wsl") { const home = getCachedWslHomeDirectory(location.distro); if (!home) return undefined; - const linuxPath = `${home}/.grok/sessions/${encodeCwdKey(cwd)}/${sessionId}`; - const uncPath = `\\\\wsl.localhost\\${location.distro}${linuxPath.replaceAll("/", "\\")}`; + const linuxRoot = `${home}/.grok/sessions`; + const uncRoot = `\\\\wsl.localhost\\${location.distro}${linuxRoot.replaceAll("/", "\\")}`; try { - return existsSync(uncPath); + return sessionExistsUnderAnyCwd(uncRoot, encodeCwdKey(cwd), sessionId); } catch { return undefined; } } - const dir = getGrokCwdSessionsDir(location, cwd); - if (!dir) return undefined; - return existsSync(join(dir, sessionId)); + try { + return sessionExistsUnderAnyCwd(getNativeGrokSessionsRoot(), encodeCwdKey(cwd), sessionId); + } catch { + return undefined; + } } /**