FN-7761: fix Grok CLI auth to use logged-in CLI instead of requiring API key

Packaged fn serve/daemon/dashboard hosts previously failed with a misleading missing-API-key error for grok-cli agents even though the operator was already logged in via the Grok CLI. This fixes routing so those hosts eagerly ensure the bundled Grok Runtime plugin is installed/loaded before session creation, and no longer silently falls back to the key-requiring direct endpoint when no key is visible.

- Eagerly ensure the bundled fusion-plugin-grok-runtime in serve, daemon, and dashboard commands before loadAllPlugins() so runtime id "grok" is available on fresh installs without manual plugin-settings setup.
- agent-session-helpers.ts: deriveGrokRuntimeHintForNoVisibleKey now throws an actionable error (naming both remediations: install/enable the Grok CLI runtime plugin, or set GROK_API_KEY) instead of silently falling through to the key-requiring pi/openai-completions path when the runtime can't be loaded.
- Update docs/grok-cli-contract.md to document the FN-7761 packaged-host wiring and new no-silent-fallback behavior.
- Add regression tests for the packaged bootstrap behavior and bundled-plugin install path.
- Add changeset for @runfusion/fusion (patch, category: fix).

Files changed:
 .changeset/fn-7761-grok-cli-packaged-routing.md    |  7 +++++
 docs/grok-cli-contract.md                          | 19 +++++++++----
 .../__tests__/grok-runtime-bootstrap.test.ts       | 31 ++++++++++++++++++++++
 packages/cli/src/commands/daemon.ts                | 17 +++++++++++-
 packages/cli/src/commands/dashboard.ts             | 20 +++++++++++++-
 packages/cli/src/commands/serve.ts                 | 19 +++++++++++--
 .../__tests__/bundled-plugin-install.test.ts       | 17 ++++++++++++
 .../src/__tests__/grok-runtime-routing.test.ts     | 17 ++++++------
 packages/engine/src/agent-session-helpers.ts       | 15 +++++++++--
 9 files changed, 142 insertions(+), 20 deletions(-)

Fusion-Task-Id: FN-7761
Fusion-Task-Lineage: 3be5f054-965c-4e8a-ad91-6e61d4dc4a42
Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
This commit is contained in:
gsxdsm
2026-07-09 23:43:15 -07:00
parent f6fd6aced4
commit 18841d76a0
9 changed files with 142 additions and 20 deletions

View File

@@ -0,0 +1,7 @@
---
"@runfusion/fusion": patch
---
summary: Route Grok CLI models through the logged-in grok CLI in packaged hosts without requiring GROK_API_KEY.
category: fix
dev: Eagerly ensures the bundled Grok runtime in serve/daemon/dashboard and blocks silent direct-endpoint fallback when no key is visible.

View File

@@ -134,7 +134,7 @@ Notes:
`GrokCliProviderCard.tsx`) is out of scope for this task and is not `GrokCliProviderCard.tsx`) is out of scope for this task and is not
modified here. modified here.
## Wiring (resolved — FN-7725, extended by FN-7753/FN-7758) ## Wiring (resolved — FN-7725, extended by FN-7753/FN-7758/FN-7761)
<!-- <!--
FNXC:GrokCli 2026-07-09-00:00: FNXC:GrokCli 2026-07-09-00:00:
@@ -160,7 +160,7 @@ via the dashboard's agent **Runtime Source → Runtime** picker
plugin runtime, including the bundled Grok Runtime plugin's `runtimeId: plugin runtime, including the bundled Grok Runtime plugin's `runtimeId:
"grok"`, with no Grok-specific code required). "grok"`, with no Grok-specific code required).
**Automatic no-key fallback (FN-7753/FN-7758):** when `createResolvedAgentSession()` sees **Automatic no-key fallback (FN-7753/FN-7758/FN-7761):** when `createResolvedAgentSession()` sees
all of the following, it derives the same effective `runtimeHint: "grok"` before all of the following, it derives the same effective `runtimeHint: "grok"` before
calling `resolveRuntime()`: calling `resolveRuntime()`:
@@ -172,6 +172,11 @@ calling `resolveRuntime()`:
`~/.grok/user-settings.json`'s `apiKey` field; and `~/.grok/user-settings.json`'s `apiKey` field; and
4. the bundled Grok Runtime plugin has registered runtime id `"grok"`. 4. the bundled Grok Runtime plugin has registered runtime id `"grok"`.
<!--
FNXC:GrokCliRouting 2026-07-09-23:05:
FN-7761 closes the packaged-host availability gap. Packaged `fn serve`, `fn daemon`, and `fn dashboard` now eagerly ensure the bundled `fusion-plugin-grok-runtime` before `loadAllPlugins()`, so fresh installs expose runtime id `"grok"` to the shared session seam without requiring the operator to first open plugin settings. If the runtime still cannot be loaded, Fusion fails before pi session creation with a dual-remediation error: install/enable the Grok CLI runtime plugin, or set `GROK_API_KEY` to use the direct xAI endpoint.
-->
FN-7758 also requires dashboard Chat/QuickChat and room responders to forward FN-7758 also requires dashboard Chat/QuickChat and room responders to forward
the configured default provider/model into this same session seam when a send has the configured default provider/model into this same session seam when a send has
no explicit model and no bound agent runtime model. That keeps the no-key routing no explicit model and no bound agent runtime model. That keeps the no-key routing
@@ -180,8 +185,12 @@ chat, QuickChat, and room responder surfaces instead of letting model-less chat
bypass the auto-derive by omitting `defaultProvider`. bypass the auto-derive by omitting `defaultProvider`.
If a Fusion-visible key exists, the direct xAI OpenAI-compatible endpoint remains If a Fusion-visible key exists, the direct xAI OpenAI-compatible endpoint remains
the default. If the Grok runtime is not registered, Fusion leaves the session on the default. If the Grok runtime is not registered or cannot be loaded in the
the existing PI/direct path rather than inventing a separate routing mode. no-visible-key `grok-cli` case, Fusion no longer leaves the session on the
existing PI/direct path because that path produces the misleading missing-key
error. Instead it raises an actionable error that names both supported recovery
paths: install/enable the Grok CLI runtime plugin so the logged-in `grok` CLI
owns auth, or set `GROK_API_KEY` so the direct xAI endpoint can authenticate.
**Exact seam:** `packages/engine/src/agent-session-helpers.ts`'s **Exact seam:** `packages/engine/src/agent-session-helpers.ts`'s
`extractRuntimeHint(runtimeConfig)` reads that hint from the assigned agent's `extractRuntimeHint(runtimeConfig)` reads that hint from the assigned agent's
@@ -215,7 +224,7 @@ additive change," formalizing + testing + documenting the already-working
path is lower risk and closes the actual gap (an *exercised* path, not just path is lower risk and closes the actual gap (an *exercised* path, not just
an implemented adapter) without adding new user-facing config surface. an implemented adapter) without adding new user-facing config surface.
**Model plumbing (FN-7753/FN-7758):** for the automatic no-key fallback, the selected **Model plumbing (FN-7753/FN-7758/FN-7761):** for the automatic no-key fallback, the selected
`grok-cli/*` model id is preserved through `AgentRuntimeOptions.defaultModelId` `grok-cli/*` model id is preserved through `AgentRuntimeOptions.defaultModelId`
(or promoted from `fallbackModelId` when the fallback provider is the grok-cli (or promoted from `fallbackModelId` when the fallback provider is the grok-cli
selection), normalized by stripping a leading `grok-cli/` (or `grok/`) prefix, selection), normalized by stripping a leading `grok-cli/` (or `grok/`) prefix,

View File

@@ -0,0 +1,31 @@
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname, resolve } from "node:path";
import { describe, expect, it } from "vitest";
const __dirname = dirname(fileURLToPath(import.meta.url));
const commandsDir = resolve(__dirname, "..");
function readCommand(command: "serve" | "daemon" | "dashboard"): string {
return readFileSync(resolve(commandsDir, `${command}.ts`), "utf8");
}
/*
* FNXC:GrokCliRouting 2026-07-09-23:05:
* FN-7761 regression guard for packaged host bootstrap. The Grok helper must run before loadAllPlugins() in each long-lived CLI host so the enabled bundled runtime is loaded and getRuntimeById("grok") can resolve before chat/executor sessions try to route grok-cli/no-key messages.
*/
describe("Grok CLI runtime packaged bootstrap", () => {
for (const command of ["serve", "daemon", "dashboard"] as const) {
it(`${command} eagerly ensures the bundled Grok runtime before loading enabled plugins`, () => {
const source = readCommand(command);
const importIndex = source.indexOf("ensureBundledGrokRuntimePluginInstalled");
const ensureIndex = source.indexOf("ensureBundledGrokRuntimePluginInstalled(pluginStore, pluginLoader)");
const loadIndex = source.indexOf("pluginLoader.loadAllPlugins()");
expect(importIndex).toBeGreaterThanOrEqual(0);
expect(ensureIndex).toBeGreaterThanOrEqual(0);
expect(loadIndex).toBeGreaterThanOrEqual(0);
expect(ensureIndex).toBeLessThan(loadIndex);
});
}
});

View File

@@ -76,7 +76,7 @@ import { resolveSelfExtension } from "./self-extension.js";
import { wrapAuthStorageWithApiKeyProviders } from "./provider-auth.js"; import { wrapAuthStorageWithApiKeyProviders } from "./provider-auth.js";
import { getModelRegistryModelsPath, getPackageManagerAgentDir } from "./auth-paths.js"; import { getModelRegistryModelsPath, getPackageManagerAgentDir } from "./auth-paths.js";
import { resolveProject } from "../project-context.js"; import { resolveProject } from "../project-context.js";
import { ensureBundledDependencyGraphPluginInstalled } from "../plugins/bundled-plugin-install.js"; import { ensureBundledDependencyGraphPluginInstalled, ensureBundledGrokRuntimePluginInstalled } from "../plugins/bundled-plugin-install.js";
import { handleOpencodeGoApiKeySaved, syncStartupModels } from "./startup-model-sync.js"; import { handleOpencodeGoApiKeySaved, syncStartupModels } from "./startup-model-sync.js";
import { registerCustomProviders, reregisterCustomProviders } from "./custom-provider-registry.js"; import { registerCustomProviders, reregisterCustomProviders } from "./custom-provider-registry.js";
import { ensureCwdProjectRegistered } from "./ensure-project-registered.js"; import { ensureCwdProjectRegistered } from "./ensure-project-registered.js";
@@ -521,6 +521,21 @@ export async function runDaemon(opts: DaemonOptions = {}) {
console.warn(`[plugins] Failed to auto-install bundled Dependency Graph plugin: ${err instanceof Error ? err.message : err}`); console.warn(`[plugins] Failed to auto-install bundled Dependency Graph plugin: ${err instanceof Error ? err.message : err}`);
} }
/*
* FNXC:GrokCliRouting 2026-07-09-23:05:
* FN-7761: packaged daemon sessions must load the bundled Grok CLI runtime before executors/reviewers create sessions, so grok-cli/no-key routing uses the logged-in `grok` CLI instead of pi's key-requiring direct endpoint.
*/
try {
const installStatus = await ensureBundledGrokRuntimePluginInstalled(pluginStore, pluginLoader);
if (installStatus === "installed") {
console.log("[plugins] Installed bundled Grok CLI runtime plugin");
} else if (installStatus === "missing-bundle") {
console.warn("[plugins] Bundled Grok CLI runtime plugin was not found in this build");
}
} catch (err) {
console.warn(`[plugins] Failed to auto-install bundled Grok CLI runtime plugin: ${err instanceof Error ? err.message : err}`);
}
// Auto-load all enabled plugins so runtime UI (NewAgentDialog, AgentDetailView) // Auto-load all enabled plugins so runtime UI (NewAgentDialog, AgentDetailView)
// can discover installed runtimes like Hermes and OpenClaw. // can discover installed runtimes like Hermes and OpenClaw.
try { try {

View File

@@ -98,7 +98,7 @@ import {
} from "./llama-cpp-extension.js"; } from "./llama-cpp-extension.js";
import { getCachedUpdateStatus, isUpdateCheckEnabled } from "../update-cache.js"; import { getCachedUpdateStatus, isUpdateCheckEnabled } from "../update-cache.js";
import { resolveSelfExtension } from "./self-extension.js"; import { resolveSelfExtension } from "./self-extension.js";
import { ensureBundledDependencyGraphPluginInstalled, ensureBundledPluginInstalled, isBundledPluginId } from "../plugins/bundled-plugin-install.js"; import { ensureBundledDependencyGraphPluginInstalled, ensureBundledGrokRuntimePluginInstalled, ensureBundledPluginInstalled, isBundledPluginId } from "../plugins/bundled-plugin-install.js";
import { registerCustomProviders, reregisterCustomProviders } from "./custom-provider-registry.js"; import { registerCustomProviders, reregisterCustomProviders } from "./custom-provider-registry.js";
import { handleOpencodeGoApiKeySaved, syncStartupModels } from "./startup-model-sync.js"; import { handleOpencodeGoApiKeySaved, syncStartupModels } from "./startup-model-sync.js";
import { DashboardTUI, DashboardLogSink, isTTYAvailable, type SystemInfo, type GitStatus, type GitCommit, type GitCommitDetail, type GitBranch, type GitWorktree, type FileEntry, type FileReadResult, type TaskStep as TUITaskStep, type TaskLogEntry as TUITaskLogEntry, type TaskDetailData, type TaskEvent } from "./dashboard-tui/index.js"; import { DashboardTUI, DashboardLogSink, isTTYAvailable, type SystemInfo, type GitStatus, type GitCommit, type GitCommitDetail, type GitBranch, type GitWorktree, type FileEntry, type FileReadResult, type TaskStep as TUITaskStep, type TaskLogEntry as TUITaskLogEntry, type TaskDetailData, type TaskEvent } from "./dashboard-tui/index.js";
@@ -1320,6 +1320,24 @@ export async function runDashboard(port: number, opts: { paused?: boolean; dev?:
); );
} }
/*
* FNXC:GrokCliRouting 2026-07-09-23:05:
* FN-7761: packaged `fn dashboard` must make fusion-plugin-grok-runtime enabled and loadable before chat sends. Without this eager Grok-scoped bootstrap, grok-cli/no-key messages bypass the logged-in `grok` CLI and hit pi's direct endpoint missing-key path.
*/
try {
const installStatus = await ensureBundledGrokRuntimePluginInstalled(pluginStore, pluginLoader);
if (installStatus === "installed") {
logSink.log("Installed bundled Grok CLI runtime plugin", "plugins");
} else if (installStatus === "missing-bundle") {
logSink.log("Bundled Grok CLI runtime plugin was not found in this build", "plugins");
}
} catch (err) {
logSink.log(
`Failed to auto-install bundled Grok CLI runtime plugin: ${err instanceof Error ? err.message : err}`,
"plugins",
);
}
try { try {
const { loaded, errors } = await pluginLoader.loadAllPlugins(); const { loaded, errors } = await pluginLoader.loadAllPlugins();
logSink.log(`Loaded ${loaded} plugins (${errors} errors)`, "plugins"); logSink.log(`Loaded ${loaded} plugins (${errors} errors)`, "plugins");

View File

@@ -78,7 +78,7 @@ import {
import { resolveSelfExtension } from "./self-extension.js"; import { resolveSelfExtension } from "./self-extension.js";
import { registerCustomProviders, reregisterCustomProviders } from "./custom-provider-registry.js"; import { registerCustomProviders, reregisterCustomProviders } from "./custom-provider-registry.js";
import { handleOpencodeGoApiKeySaved, syncStartupModels } from "./startup-model-sync.js"; import { handleOpencodeGoApiKeySaved, syncStartupModels } from "./startup-model-sync.js";
import { ensureBundledDependencyGraphPluginInstalled, ensureBundledPluginInstalled, isBundledPluginId } from "../plugins/bundled-plugin-install.js"; import { ensureBundledDependencyGraphPluginInstalled, ensureBundledGrokRuntimePluginInstalled, ensureBundledPluginInstalled, isBundledPluginId } from "../plugins/bundled-plugin-install.js";
import { ensureCwdProjectRegistered } from "./ensure-project-registered.js"; import { ensureCwdProjectRegistered } from "./ensure-project-registered.js";
const DIAGNOSTIC_INTERVAL_MS = 30 * 60 * 1000; // 30 minutes const DIAGNOSTIC_INTERVAL_MS = 30 * 60 * 1000; // 30 minutes
@@ -546,7 +546,22 @@ export async function runServe(
console.warn(`[plugins] Failed to auto-install bundled Dependency Graph plugin: ${err instanceof Error ? err.message : err}`); console.warn(`[plugins] Failed to auto-install bundled Dependency Graph plugin: ${err instanceof Error ? err.message : err}`);
} }
// Lazy-install hook for bundled runtime plugins (Hermes/OpenClaw/Paperclip). /*
* FNXC:GrokCliRouting 2026-07-09-23:05:
* FN-7761: packaged `fn serve` must make the bundled Grok CLI runtime discoverable before any message-sending lane starts. Otherwise grok-cli/no-key selections fall through to the direct xAI endpoint and incorrectly ask for GROK_API_KEY even though the `grok` CLI owns auth.
*/
try {
const installStatus = await ensureBundledGrokRuntimePluginInstalled(pluginStore, pluginLoader);
if (installStatus === "installed") {
console.log("[plugins] Installed bundled Grok CLI runtime plugin");
} else if (installStatus === "missing-bundle") {
console.warn("[plugins] Bundled Grok CLI runtime plugin was not found in this build");
}
} catch (err) {
console.warn(`[plugins] Failed to auto-install bundled Grok CLI runtime plugin: ${err instanceof Error ? err.message : err}`);
}
// Lazy-install hook for bundled runtime plugins (Hermes/OpenClaw/Paperclip/Grok).
const ensureBundledPluginInstalledCallback = async (pluginId: string): Promise<boolean> => { const ensureBundledPluginInstalledCallback = async (pluginId: string): Promise<boolean> => {
if (!isBundledPluginId(pluginId)) { if (!isBundledPluginId(pluginId)) {
console.warn(`[plugins] ensureBundledPluginInstalled: unknown bundled plugin id "${pluginId}"`); console.warn(`[plugins] ensureBundledPluginInstalled: unknown bundled plugin id "${pluginId}"`);

View File

@@ -38,6 +38,7 @@ import {
BUNDLED_PLUGIN_IDS, BUNDLED_PLUGIN_IDS,
ensureBundledDependencyGraphPluginInstalled, ensureBundledDependencyGraphPluginInstalled,
ensureBundledCursorRuntimePluginInstalled, ensureBundledCursorRuntimePluginInstalled,
ensureBundledGrokRuntimePluginInstalled,
ensureBundledPluginInstalled, ensureBundledPluginInstalled,
type BundledPluginDirResolver, type BundledPluginDirResolver,
} from "../bundled-plugin-install.js"; } from "../bundled-plugin-install.js";
@@ -47,6 +48,7 @@ import {
const BUNDLED_PLUGIN_ID = "fusion-plugin-dependency-graph"; const BUNDLED_PLUGIN_ID = "fusion-plugin-dependency-graph";
const HERMES_PLUGIN_ID = "fusion-plugin-hermes-runtime"; const HERMES_PLUGIN_ID = "fusion-plugin-hermes-runtime";
const CURSOR_PLUGIN_ID = "fusion-plugin-cursor-runtime"; const CURSOR_PLUGIN_ID = "fusion-plugin-cursor-runtime";
const GROK_PLUGIN_ID = "fusion-plugin-grok-runtime";
const ROADMAP_PLUGIN_ID = "fusion-plugin-roadmap"; const ROADMAP_PLUGIN_ID = "fusion-plugin-roadmap";
const REPORTS_PLUGIN_ID = "fusion-plugin-reports"; const REPORTS_PLUGIN_ID = "fusion-plugin-reports";
const LINEAR_IMPORT_PLUGIN_ID = "fusion-plugin-linear-import"; const LINEAR_IMPORT_PLUGIN_ID = "fusion-plugin-linear-import";
@@ -189,6 +191,7 @@ describe("ensureBundledPluginInstalled (host-agnostic shared helper)", () => {
expect(BUNDLED_PLUGIN_IDS).toContain(REPORTS_PLUGIN_ID); expect(BUNDLED_PLUGIN_IDS).toContain(REPORTS_PLUGIN_ID);
expect(BUNDLED_PLUGIN_IDS).toContain(LINEAR_IMPORT_PLUGIN_ID); expect(BUNDLED_PLUGIN_IDS).toContain(LINEAR_IMPORT_PLUGIN_ID);
expect(BUNDLED_PLUGIN_IDS).toContain(HERMES_PLUGIN_ID); expect(BUNDLED_PLUGIN_IDS).toContain(HERMES_PLUGIN_ID);
expect(BUNDLED_PLUGIN_IDS).toContain(GROK_PLUGIN_ID);
}); });
it("fresh install: registers and loads the plugin when not in DB (CLI-shaped resolver)", async () => { it("fresh install: registers and loads the plugin when not in DB (CLI-shaped resolver)", async () => {
@@ -350,6 +353,20 @@ describe("ensureBundledPluginInstalled (host-agnostic shared helper)", () => {
); );
}); });
it("registers and loads the Grok runtime through the dedicated helper", async () => {
setupBundleExists(cliShapedResolver, { id: GROK_PLUGIN_ID });
const store = makePluginStore();
const loader = makePluginLoader();
const result = await ensureBundledGrokRuntimePluginInstalled(store as never, loader as never, cliShapedResolver);
expect(result).toBe("installed");
expect(store.registerPlugin).toHaveBeenCalledWith(
expect.objectContaining({ manifest: expect.objectContaining({ id: GROK_PLUGIN_ID }) }),
);
expect(loader.loadPlugin).toHaveBeenCalledWith(GROK_PLUGIN_ID);
});
it("registers Dependency Graph through the deprecated dedicated helper", async () => { it("registers Dependency Graph through the deprecated dedicated helper", async () => {
setupBundleExists(cliShapedResolver); setupBundleExists(cliShapedResolver);
const store = makePluginStore(); const store = makePluginStore();

View File

@@ -295,27 +295,26 @@ describe("Grok CLI runtime routing (FN-7725)", () => {
})); }));
}); });
it("keeps grok-cli on pi when no key is visible but the Grok runtime is not registered", async () => { it("surfaces an actionable dual-remediation error instead of falling through to the key-requiring pi runtime when the Grok runtime is unavailable", async () => {
vi.mocked(fusionCore.isGrokApiKeyFusionVisible).mockReturnValue(false); vi.mocked(fusionCore.isGrokApiKeyFusionVisible).mockReturnValue(false);
const pluginRunner = createMockPluginRunner({ const pluginRunner = createMockPluginRunner({
getRuntimeById: vi.fn().mockReturnValue(undefined), getRuntimeById: vi.fn().mockReturnValue(undefined),
}); });
const result = await createResolvedAgentSession({ /*
* FNXC:GrokCliRouting 2026-07-09-23:05:
* FN-7761 symptom reproduction: packaged hosts previously left fusion-plugin-grok-runtime uninstalled, so getRuntimeById("grok") was undefined and grok-cli/no-key sessions fell through to pi's direct endpoint. The fixed invariant forbids that silent fallback and tells operators to either enable the Grok CLI runtime or set GROK_API_KEY.
*/
await expect(createResolvedAgentSession({
sessionPurpose: "executor", sessionPurpose: "executor",
pluginRunner, pluginRunner,
cwd: "/tmp/project", cwd: "/tmp/project",
defaultProvider: "grok-cli", defaultProvider: "grok-cli",
defaultModelId: "grok-4.5", defaultModelId: "grok-4.5",
systemPrompt: "no-runtime-registered", systemPrompt: "no-runtime-registered",
}); })).rejects.toThrow(/Install and enable the Grok CLI runtime plugin, or set GROK_API_KEY/);
expect(result.runtimeId).toBe("pi"); expect(mockCreateFnAgent).not.toHaveBeenCalled();
expect(result.wasConfigured).toBe(false);
expect(mockCreateFnAgent).toHaveBeenCalledWith(expect.objectContaining({
defaultProvider: "grok-cli",
defaultModelId: "grok-4.5",
}));
}); });
it("auto-routes heartbeat/room responder grok-cli defaults to the Grok runtime when no Fusion-visible key exists", async () => { it("auto-routes heartbeat/room responder grok-cli defaults to the Grok runtime when no Fusion-visible key exists", async () => {

View File

@@ -163,6 +163,13 @@ function stripGrokCliModelProviderPrefix(modelId: string | undefined): string |
: normalized; : normalized;
} }
function buildMissingGrokRuntimeError(): Error {
return new Error(
"Grok CLI models require the bundled Grok CLI runtime when no Fusion-visible GROK_API_KEY is set. "
+ "Install and enable the Grok CLI runtime plugin, or set GROK_API_KEY to use the direct xAI endpoint.",
);
}
function deriveGrokRuntimeHintForNoVisibleKey( function deriveGrokRuntimeHintForNoVisibleKey(
runtimeOptions: AgentRuntimeOptions, runtimeOptions: AgentRuntimeOptions,
pluginRunner: PluginRunner | undefined, pluginRunner: PluginRunner | undefined,
@@ -171,10 +178,11 @@ function deriveGrokRuntimeHintForNoVisibleKey(
&& runtimeOptions.fallbackProvider !== GROK_CLI_PROVIDER_ID) return undefined; && runtimeOptions.fallbackProvider !== GROK_CLI_PROVIDER_ID) return undefined;
if (isGrokApiKeyFusionVisible()) return undefined; if (isGrokApiKeyFusionVisible()) return undefined;
try { try {
return pluginRunner?.getRuntimeById("grok") ? "grok" : undefined; if (pluginRunner?.getRuntimeById("grok")) return "grok";
} catch { } catch {
return undefined; throw buildMissingGrokRuntimeError();
} }
throw buildMissingGrokRuntimeError();
} }
function applyGrokCliNoKeyRuntimeOptions( function applyGrokCliNoKeyRuntimeOptions(
@@ -435,6 +443,9 @@ export async function createResolvedAgentSession(
FN-7758 extends the no-visible-key invariant to configured fallback models. Pi resolves fallback models FN-7758 extends the no-visible-key invariant to configured fallback models. Pi resolves fallback models
during session creation and prompt-time swaps through the key-requiring provider registry, so a grok-cli during session creation and prompt-time swaps through the key-requiring provider registry, so a grok-cli
fallback must select the Grok CLI runtime up front and promote the fallback model into the CLI session. fallback must select the Grok CLI runtime up front and promote the fallback model into the CLI session.
FNXC:GrokCliRouting 2026-07-09-23:05:
FN-7761 closes the packaged serve/daemon/dashboard gap: if grok-cli is selected and no Fusion-visible key exists, this seam must never silently fall through to the key-requiring pi/openai-completions runtime when the Grok plugin was not pre-installed. The hosts eagerly install/load the bundled runtime; if that genuinely fails, throw an operator-actionable error naming the two supported remediations.
*/ */
const autoGrokRuntimeHint = !useMockRuntime && !runtimeHint const autoGrokRuntimeHint = !useMockRuntime && !runtimeHint
? deriveGrokRuntimeHintForNoVisibleKey(runtimeOptions, pluginRunner) ? deriveGrokRuntimeHintForNoVisibleKey(runtimeOptions, pluginRunner)