Defer Droid CLI validation and discovery so startup no longer spawns or hangs on droid. - Register the Droid provider without boot-time validation or model discovery side effects. - Trigger CLI validation only when a Droid stream starts and expose explicit model discovery. - Add regression coverage for non-interactive droid process spawning and startup probe behavior. - Add a patch changeset for the published Fusion CLI package. Files changed: .changeset/fn-6911-droid-cli-no-boot-spawn.md | 5 ++ .../commands/__tests__/droid-cli-extension.test.ts | 15 +++- packages/droid-cli/index.ts | 33 ++++---- packages/droid-cli/src/__tests__/index.test.ts | 90 +++++++++------------- .../src/__tests__/discover-models.test.ts | 7 +- .../src/__tests__/probe.test.ts | 8 ++ .../src/__tests__/process-manager.test.ts | 74 ++++++++++++++++++ .../src/__tests__/startup-probes.test.ts | 5 +- 8 files changed, 160 insertions(+), 77 deletions(-) Fusion-Task-Id: FN-6911 Fusion-Task-Lineage: ef4bc3df-bfe8-416a-b701-64f752faea30
75 lines
2.0 KiB
TypeScript
75 lines
2.0 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
import { EventEmitter } from "node:events";
|
|
|
|
const spawnMock = vi.hoisted(() => vi.fn());
|
|
|
|
vi.mock("node:child_process", () => ({
|
|
spawn: spawnMock,
|
|
}));
|
|
|
|
import { buildDroidSpawnArgs, spawnDroid } from "../process-manager.js";
|
|
|
|
function makeProc() {
|
|
const proc = new EventEmitter() as any;
|
|
proc.killed = false;
|
|
proc.exitCode = null;
|
|
proc.pid = 123;
|
|
proc.kill = vi.fn(() => {
|
|
proc.killed = true;
|
|
});
|
|
return proc;
|
|
}
|
|
|
|
describe("Droid agent spawn invariants", () => {
|
|
beforeEach(() => {
|
|
spawnMock.mockReset();
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
it("builds a non-interactive print-mode stream-json invocation", () => {
|
|
const args = buildDroidSpawnArgs("droid-pro", undefined, {
|
|
effort: "high",
|
|
mcpConfigPath: "/tmp/mcp.json",
|
|
newSessionId: "session-1",
|
|
});
|
|
|
|
expect(args[0]).toBe("-p");
|
|
expect(args).toEqual(expect.arrayContaining([
|
|
"--input-format",
|
|
"stream-json",
|
|
"--output-format",
|
|
"stream-json",
|
|
"--model",
|
|
"droid-pro",
|
|
"--session-id",
|
|
"session-1",
|
|
"--effort",
|
|
"high",
|
|
"--mcp-config",
|
|
"/tmp/mcp.json",
|
|
]));
|
|
expect(args).not.toContain("models");
|
|
expect(args).not.toContain("model");
|
|
});
|
|
|
|
it("spawns droid with piped stdio and never inherits a TTY", () => {
|
|
const proc = makeProc();
|
|
spawnMock.mockReturnValueOnce(proc);
|
|
|
|
expect(spawnDroid("droid-pro", undefined, { cwd: "/tmp/project" })).toBe(proc);
|
|
|
|
expect(spawnMock).toHaveBeenCalledTimes(1);
|
|
const [binary, args, options] = spawnMock.mock.calls[0] as [string, string[], { stdio: string[]; cwd: string }];
|
|
expect(binary).toBe("droid");
|
|
expect(args[0]).toBe("-p");
|
|
expect(args).toEqual(expect.arrayContaining(["--input-format", "stream-json"]));
|
|
expect(options.cwd).toBe("/tmp/project");
|
|
expect(options.stdio).toEqual(["pipe", "pipe", "pipe"]);
|
|
expect(options.stdio).not.toBe("inherit");
|
|
expect(options.stdio).not.toContain("inherit");
|
|
});
|
|
});
|