fix: repair Grok CLI dashboard chat (routing + stale bundle)
Grok CLI chat failed two different ways depending on the surface: 1. Default (no-project) chat errored with "requires the bundled Grok CLI runtime". The default ChatManager was handed a bare PluginLoader, but Grok routing (deriveGrokRuntimeHintForNoVisibleKey -> resolveRuntime) needs a PluginRunner's getRuntimeById/createRuntimeContext; the unguarded call threw "getRuntimeById is not a function". New resolveChatManagerPluginRunner() prefers the engine's PluginRunner (same runner the project-scoped path uses), falling back to the loader only in UI-only mode. 2. Project-scoped chat returned empty replies. The CLI-bundled Grok plugin (packages/cli/dist/plugins/.../bundled.js, gitignored) was stale vs the FN-7796 single-JSON adapter source; the running server loads that bundle, not the plugin's own dist. `pnpm build` regenerates it. Noted in the changeset that the freshness guard only warns and the dev prebuild does not rebuild the CLI tsup bundle. Verified end-to-end on a live dashboard: both default and project-scoped grok-cli/grok-4.5 chats now stream thinking + text. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
7
.changeset/grok-cli-chat-routing-and-stale-bundle.md
Normal file
7
.changeset/grok-cli-chat-routing-and-stale-bundle.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
summary: Fix Grok CLI chat returning errors or empty replies in the dashboard.
|
||||||
|
category: fix
|
||||||
|
dev: Two independent defects. (1) The default (no-project) ChatManager received a bare PluginLoader as its runner; Grok CLI routing (deriveGrokRuntimeHintForNoVisibleKey → resolveRuntime) calls getRuntimeById/createRuntimeContext, which only exist on PluginRunner, so a grok-cli/* chat with no Fusion-visible GROK_API_KEY threw "getRuntimeById is not a function" and surfaced the misleading "requires the bundled Grok CLI runtime" error. New resolveChatManagerPluginRunner(options) prefers the engine's PluginRunner (the runner the project-scoped chat path already uses), falling back to the loader only in UI-only mode. (2) The CLI-bundled Grok plugin (packages/cli/dist/plugins/fusion-plugin-grok-runtime/bundled.js) was stale relative to the FN-7796 single-JSON adapter source, so project-scoped grok chat produced empty replies; a `pnpm build` (tsup) regenerates it. The bundled-plugin freshness guard only warns — the dev prebuild does not rebuild the CLI tsup bundle.
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import { resolveChatManagerPluginRunner } from "../server.js";
|
||||||
|
|
||||||
|
/*
|
||||||
|
FNXC:GrokCliRouting 2026-07-10-00:00:
|
||||||
|
Regression guard for the "grok chat not working" default-chat failure. The
|
||||||
|
default (no-project) ChatManager must receive a real PluginRunner (with
|
||||||
|
`getRuntimeById`/`createRuntimeContext`) for Grok CLI runtime resolution — a
|
||||||
|
bare PluginLoader lacks `getRuntimeById`, so `deriveGrokRuntimeHintForNoVisibleKey`
|
||||||
|
threw "getRuntimeById is not a function" → the misleading "requires the bundled
|
||||||
|
Grok CLI runtime" error. `resolveChatManagerPluginRunner` must prefer the
|
||||||
|
engine's PluginRunner and fall back to the loader only when no engine exists.
|
||||||
|
*/
|
||||||
|
describe("resolveChatManagerPluginRunner", () => {
|
||||||
|
const bareLoader = { getPluginRoutes: () => [] };
|
||||||
|
const engineRunner = {
|
||||||
|
getPluginRoutes: () => [],
|
||||||
|
getRuntimeById: () => ({ pluginId: "fusion-plugin-grok-runtime", runtime: {} }),
|
||||||
|
createRuntimeContext: async () => ({}),
|
||||||
|
};
|
||||||
|
|
||||||
|
it("prefers the engine's PluginRunner over the bare loader when an engine is present", () => {
|
||||||
|
const engine = { getPluginRunner: () => engineRunner } as never;
|
||||||
|
const resolved = resolveChatManagerPluginRunner({
|
||||||
|
engine,
|
||||||
|
pluginRunner: bareLoader as never,
|
||||||
|
});
|
||||||
|
expect(resolved).toBe(engineRunner);
|
||||||
|
// The chosen runner must expose runtime resolution — the exact capability
|
||||||
|
// the bare loader lacks and that Grok CLI routing depends on.
|
||||||
|
expect(typeof (resolved as { getRuntimeById?: unknown })?.getRuntimeById).toBe("function");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("falls back to options.pluginRunner in UI-only mode (no engine)", () => {
|
||||||
|
const resolved = resolveChatManagerPluginRunner({
|
||||||
|
engine: undefined,
|
||||||
|
pluginRunner: bareLoader as never,
|
||||||
|
});
|
||||||
|
expect(resolved).toBe(bareLoader);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("falls back to options.pluginRunner when the engine exposes no runner", () => {
|
||||||
|
const engine = { getPluginRunner: () => undefined } as never;
|
||||||
|
const resolved = resolveChatManagerPluginRunner({
|
||||||
|
engine,
|
||||||
|
pluginRunner: bareLoader as never,
|
||||||
|
});
|
||||||
|
expect(resolved).toBe(bareLoader);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -769,6 +769,26 @@ export function wireCliRelaunchListener(options: {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
FNXC:GrokCliRouting 2026-07-10-00:00:
|
||||||
|
Select the PluginRunner the default (no-project) ChatManager uses for runtime
|
||||||
|
resolution. Grok CLI routing (deriveGrokRuntimeHintForNoVisibleKey → resolveRuntime)
|
||||||
|
calls `getRuntimeById` and `createRuntimeContext`, which exist only on a real
|
||||||
|
PluginRunner — a bare PluginLoader (what `options.pluginRunner` is in the CLI
|
||||||
|
`dashboard` command) lacks them, so a `grok-cli/*` chat with no Fusion-visible
|
||||||
|
GROK_API_KEY threw "getRuntimeById is not a function" and surfaced the misleading
|
||||||
|
"requires the bundled Grok CLI runtime" error. Prefer the engine's PluginRunner
|
||||||
|
(the same runner the project-scoped chat path already uses via
|
||||||
|
engine.getPluginRunner()); fall back to `options.pluginRunner` only in UI-only
|
||||||
|
mode where no engine exists.
|
||||||
|
*/
|
||||||
|
export function resolveChatManagerPluginRunner(
|
||||||
|
options?: Pick<ServerOptions, "engine" | "pluginRunner">,
|
||||||
|
): ServerOptions["pluginRunner"] {
|
||||||
|
const engineRunner = options?.engine?.getPluginRunner?.();
|
||||||
|
return (engineRunner as ServerOptions["pluginRunner"] | undefined) ?? options?.pluginRunner;
|
||||||
|
}
|
||||||
|
|
||||||
export function createServer(store: TaskStore, options?: ServerOptions): ReturnType<typeof express> {
|
export function createServer(store: TaskStore, options?: ServerOptions): ReturnType<typeof express> {
|
||||||
// Register the universal post-create hook so every task-creation path
|
// Register the universal post-create hook so every task-creation path
|
||||||
// (HTTP routes, CLI, pi extension, mission triage, etc.) triggers
|
// (HTTP routes, CLI, pi extension, mission triage, etc.) triggers
|
||||||
@@ -1374,12 +1394,24 @@ export function createServer(store: TaskStore, options?: ServerOptions): ReturnT
|
|||||||
// Create AgentStore for chat prompt enrichment (initialized lazily by ChatManager)
|
// Create AgentStore for chat prompt enrichment (initialized lazily by ChatManager)
|
||||||
const chatAgentStore = new AgentStore({ rootDir: store.getFusionDir() });
|
const chatAgentStore = new AgentStore({ rootDir: store.getFusionDir() });
|
||||||
|
|
||||||
// Create ChatManager for AI chat message handling
|
// Create ChatManager for AI chat message handling.
|
||||||
|
/*
|
||||||
|
FNXC:GrokCliRouting 2026-07-10-00:00:
|
||||||
|
The default (no-project) ChatManager must receive a real PluginRunner — not the
|
||||||
|
bare PluginLoader passed as `options.pluginRunner`. Grok CLI routing
|
||||||
|
(deriveGrokRuntimeHintForNoVisibleKey → resolveRuntime) calls `getRuntimeById`
|
||||||
|
and `createRuntimeContext`, which exist only on PluginRunner; a PluginLoader
|
||||||
|
lacks them, so a `grok-cli/*` chat with no visible GROK_API_KEY threw
|
||||||
|
"getRuntimeById is not a function" → the misleading "requires the bundled Grok
|
||||||
|
CLI runtime" error. Prefer the engine's PluginRunner (the same runner the
|
||||||
|
project-scoped chat path already uses via engine.getPluginRunner()), falling
|
||||||
|
back to the loader only in UI-only mode where no engine exists.
|
||||||
|
*/
|
||||||
const chatManager = options?.chatManager ?? new ChatManager(
|
const chatManager = options?.chatManager ?? new ChatManager(
|
||||||
chatStore,
|
chatStore,
|
||||||
store.getRootDir(),
|
store.getRootDir(),
|
||||||
chatAgentStore,
|
chatAgentStore,
|
||||||
options?.pluginRunner,
|
resolveChatManagerPluginRunner(options),
|
||||||
() => store.getSettings(),
|
() => store.getSettings(),
|
||||||
options?.engine?.getMessageStore(),
|
options?.engine?.getMessageStore(),
|
||||||
store,
|
store,
|
||||||
|
|||||||
Reference in New Issue
Block a user