Files
fusion/plugins/fusion-plugin-grok-runtime/src/__tests__/cli-spawn.test.ts
gsxdsm 081dae0e0f FN-7705: Add Grok CLI runtime support as a bundled plugin
Adds a new bundled Grok CLI runtime plugin, wiring it end-to-end into settings, auth routes, model discovery, and the dashboard authentication UI.

- New `fusion-plugin-grok-runtime` package with CLI spawn, probe, provider, process-manager, and runtime-adapter modules plus tests
- Bundled-plugin install list (CLI + core) updated to auto-install the grok-cli plugin
- New `useGrokCli`/`grokCliBinaryPath` settings in `settings-schema.ts` and `types.ts`
- Dashboard: `GrokCliProviderCard` component/styles, `ProviderIcon` grok entry, `AuthenticationSection` wiring
- New `grok-model-cache.ts` for caching `grok models` discovery results, registered model/auth routes for `/auth/grok-cli` and `/providers/grok-cli/status`, merged into `/api/models`
- `runtime-provider-probes.ts` extended with Grok CLI probe/model-discovery delegation
- Docs updated (`PLUGIN_AUTHORING.md`, `settings-reference.md`) and changeset added (minor, feature)
- Workspace config (`pnpm-workspace.yaml`, `pnpm-lock.yaml`) updated to register the new plugin package

Files changed:
 .changeset/fn-7705-grok-cli-runtime.md             |   7 +
 docs/PLUGIN_AUTHORING.md                           |   2 +-
 docs/settings-reference.md                         |   4 +
 packages/cli/src/plugins/bundled-plugin-install.ts |   8 +
 .../cli/src/plugins/staged-bundled-plugin-ids.ts   |   1 +
 packages/cli/vitest.config.ts                      |  12 +
 .../core/src/__tests__/grok-cli-settings.test.ts   |  34 +++
 packages/core/src/index.ts                         |   1 +
 .../core/src/plugins/bundled-plugin-install.ts     |  10 +
 packages/core/src/settings-schema.ts               |   6 +
 packages/core/src/types.ts                         |   9 +
 packages/dashboard/app/api/legacy.ts               |  40 ++++
 .../app/components/GrokCliProviderCard.css         |  65 ++++++
 .../app/components/GrokCliProviderCard.tsx         | 204 ++++++++++++++++
 packages/dashboard/app/components/ProviderIcon.tsx |   5 +
 .../__tests__/GrokCliProviderCard.test.tsx         | 105 +++++++++
 .../app/components/__tests__/ProviderIcon.test.tsx |   8 +
 .../settings/sections/AuthenticationSection.tsx    |   8 +-
 packages/dashboard/package.json                    |   1 +
 .../src/__tests__/grok-model-cache.test.ts         | 152 ++++++++++++
 .../register-model-routes-grok-cli.test.ts         | 214 +++++++++++++++++
 .../dashboard/src/__tests__/routes-auth.test.ts    | 258 ++++++++++++++++++++-
 packages/dashboard/src/grok-model-cache.ts         | 166 +++++++++++++
 packages/dashboard/src/routes.ts                   |   1 +
 .../dashboard/src/routes/register-auth-routes.ts   | 134 ++++++++++-
 .../dashboard/src/routes/register-model-routes.ts  |  51 ++++
 packages/dashboard/src/runtime-provider-probes.ts  |  43 ++++
 packages/dashboard/vitest.config.ts                |  12 +
 packages/desktop/scripts/workspace-tools.ts        |   3 +-
 plugins/fusion-plugin-grok-runtime/CHANGELOG.md    |   7 +
 plugins/fusion-plugin-grok-runtime/README.md       |  54 +++++
 plugins/fusion-plugin-grok-runtime/manifest.json   |   6 +
 plugins/fusion-plugin-grok-runtime/package.json    |  40 ++++
 .../src/__tests__/cli-spawn.test.ts                | 103 ++++++++
 .../src/__tests__/index.test.ts                    |  12 +
 .../src/__tests__/probe.test.ts                    | 135 +++++++++++
 .../src/__tests__/process-manager.test.ts          |  96 ++++++++
 .../src/__tests__/provider.test.ts                 |  57 +++++
 .../src/__tests__/runtime-adapter.test.ts          |  21 ++
 .../fusion-plugin-grok-runtime/src/cli-spawn.ts    |  50 ++++
 plugins/fusion-plugin-grok-runtime/src/index.ts    |  74 ++++++
 plugins/fusion-plugin-grok-runtime/src/probe.ts    | 107 +++++++++
 .../src/process-manager.ts                         |  86 +++++++
 plugins/fusion-plugin-grok-runtime/src/provider.ts |  25 ++
 .../src/runtime-adapter.ts                         |  25 ++
 plugins/fusion-plugin-grok-runtime/src/types.ts    |  12 +
 plugins/fusion-plugin-grok-runtime/tsconfig.json   |  10 +
 .../fusion-plugin-grok-runtime/vitest.config.ts    |  22 ++
 pnpm-lock.yaml                                     |  25 ++
 pnpm-workspace.yaml                                |   1 +
 50 files changed, 2525 insertions(+), 7 deletions(-)

Fusion-Task-Id: FN-7705

Fusion-Task-Lineage: b8194ea8-c773-4199-a52a-b0e4e7347192

Co-authored-by: Fusion (runfusion.ai) <noreply@runfusion.ai>
2026-07-08 23:39:34 -07:00

104 lines
3.0 KiB
TypeScript

import { EventEmitter } from "node:events";
import { PassThrough } from "node:stream";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
vi.mock("node:child_process", () => ({ spawn: vi.fn() }));
import { spawn } from "node:child_process";
import { runGrokCommand } from "../cli-spawn.js";
function mockPlatform(platform: NodeJS.Platform) {
return vi.spyOn(process, "platform", "get").mockReturnValue(platform);
}
function createMockChild() {
const child = new EventEmitter() as EventEmitter & {
stdout: PassThrough;
stderr: PassThrough;
kill: ReturnType<typeof vi.fn>;
};
child.stdout = new PassThrough();
child.stderr = new PassThrough();
child.kill = vi.fn();
vi.mocked(spawn).mockReturnValue(child as never);
return child;
}
describe("runGrokCommand", () => {
beforeEach(() => {
vi.useRealTimers();
vi.clearAllMocks();
});
afterEach(() => {
vi.useRealTimers();
vi.restoreAllMocks();
});
it("uses the Windows shell so PATH .cmd and .bat Grok shims can run", async () => {
mockPlatform("win32");
const child = createMockChild();
const resultPromise = runGrokCommand("grok", ["--version"], 1000);
expect(spawn).toHaveBeenCalledWith("grok", ["--version"], {
stdio: ["ignore", "pipe", "pipe"],
shell: true,
});
child.stdout.write("grok 1.0.0\n");
child.stderr.write("diagnostic\n");
child.emit("close", 0);
await expect(resultPromise).resolves.toEqual({
code: 0,
stdout: "grok 1.0.0\n",
stderr: "diagnostic\n",
});
});
it("keeps non-Windows Grok invocations on direct spawn", async () => {
mockPlatform("darwin");
const child = createMockChild();
const resultPromise = runGrokCommand("grok", ["--version"], 1000);
expect(spawn).toHaveBeenCalledWith("grok", ["--version"], {
stdio: ["ignore", "pipe", "pipe"],
shell: false,
});
child.emit("close", 0);
await expect(resultPromise).resolves.toMatchObject({ code: 0 });
});
it("returns spawn errors with diagnostics instead of empty stderr", async () => {
mockPlatform("win32");
const child = createMockChild();
const resultPromise = runGrokCommand("grok", ["--version"], 1000);
child.emit("error", Object.assign(new Error("spawn grok ENOENT"), { code: "ENOENT" }));
const result = await resultPromise;
expect(result.code).toBe(127);
expect(result.stderr).toContain("spawn error: ENOENT: spawn grok ENOENT");
});
it("kills timed-out Grok commands best-effort and resolves once", async () => {
vi.useFakeTimers();
mockPlatform("linux");
const child = createMockChild();
const resultPromise = runGrokCommand("grok", ["models"], 25);
child.stdout.write("partial");
await vi.advanceTimersByTimeAsync(25);
await expect(resultPromise).resolves.toEqual({ code: 124, stdout: "partial", stderr: "" });
expect(child.kill).toHaveBeenCalledWith("SIGKILL");
child.emit("close", 0);
await expect(resultPromise).resolves.toMatchObject({ code: 124 });
});
});