import { execFile } from "node:child_process"; import { UsageSourceRegistry } from "../../../packages/server/src/server/plugins/usage-sources/index.js"; import { Usage as MuseUsage } from "../server/usage.js"; import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises"; import { setTimeout as delay } from "node:timers/promises"; import os from "node:os"; import path from "node:path"; import { fileURLToPath } from "node:url"; import { afterEach, expect, test, vi } from "vitest"; import { PROVIDER_CAPABILITIES, ProviderEventSchema, type ProviderConnection, type ProviderEvent, type ProviderRegistration, type ProviderLaunch, type ProviderInput, } from "@getpaseo/plugin/server/provider"; import type { UsageSourceRegistration } from "@getpaseo/plugin/server"; import { execCommand } from "@getpaseo/plugin/server"; import contribute from "../index.server.js"; const connections: ProviderConnection[] = []; const roots: string[] = []; afterEach(async () => { vi.useRealTimers(); await Promise.all(connections.splice(0).map((connection) => connection.close())); await Promise.all(roots.splice(0).map((root) => rm(root, { recursive: true, force: true }))); }); function registration( registerUsageSource = (_source: UsageSourceRegistration) => {}, ): ProviderRegistration { let provider: ProviderRegistration | undefined; const server = { registerUsageSource, registerProvider(value: ProviderRegistration) { provider = value; }, }; contribute(server); if (!provider) throw new Error("Provider was not registered"); return provider; } async function harness( scenario = "text-reasoning", env: Record = {}, providerOptions?: Record, ) { const root = await mkdtemp(path.join(os.tmpdir(), "muse-provider-test-")); roots.push(root); const requests = path.join(root, "requests.ndjson"); const launch: ProviderLaunch = { command: process.execPath, args: [fileURLToPath(new URL("./fake-muse.mjs", import.meta.url))], env: { MUSE_TEST_SCENARIO: scenario, MUSE_TEST_REQUESTS: requests, ...env }, }; await writeFile(requests, ""); let usageSource!: UsageSourceRegistration; const provider = registration((source) => { usageSource = source; }); const connection = await provider.connect({ launch, versions: [1], capabilities: PROVIDER_CAPABILITIES, }); connections.push(connection); const events: ProviderEvent[] = []; connection.onEvent((event) => { events.push(ProviderEventSchema.parse(event)); }); async function wait( predicate: (event: ProviderEvent) => boolean, from = 0, ): Promise { const deadline = Date.now() + 5000; while (Date.now() < deadline) { const event = events.slice(from).find(predicate); if (event) return event; await delay(10); } throw new Error(`Missing event; received ${JSON.stringify(events)}`); } async function send(input: ProviderInput) { const from = events.length; await connection.send(input); return from; } async function open( persistence?: Extract["persistence"], mode = "onRequest", model: string | null = "meta/muse-spark-1.3", ) { await send({ type: "session.open", requestId: "open", sessionId: "paseo-session", config: { cwd: root, providerOptions, env: {}, settings: {}, mcpServers: {}, persist: true, mode, model: model ?? undefined, thinkingOption: "high", systemPrompt: "Test instructions", }, persistence, history: "replay", }); return wait((event) => event.type === "session.ready" || event.type === "request.failed"); } async function prompt(delivery: "auto" | "steer" = "auto", image = false) { return send({ type: "session.prompt", sessionId: "paseo-session", prompt: { clientMessageId: `client-${events.length}`, delivery, input: { type: "message", content: image ? [ { type: "text", text: "hello" }, { type: "image", mimeType: "image/png", data: "image-bytes" }, ] : [{ type: "text", text: "hello" }], }, }, }); } async function recorded() { return (await readFile(requests, "utf8")) .trim() .split("\n") .filter(Boolean) .map((line) => JSON.parse(line)); } return { provider, launch, connection, events, wait, send, open, prompt, recorded, root, usageSource, }; } test("registration exposes Muse with a daemon-resolved command", () => { expect(registration()).toMatchObject({ id: "muse", label: "Muse Code", command: ["muse"], icon: "icon.svg", }); }); test("catalog uses model effort variants, modes, and bounded cached discovery", async () => { const h = await harness("catalog-controls"); await h.send({ type: "catalog", requestId: "catalog-1" }); const catalogEvent = await h.wait((candidate) => candidate.type === "catalog"); expect(catalogEvent).toMatchObject({ catalog: { models: [ { id: "meta/muse-spark-1.3", label: "meta/muse-spark-1.3", defaultThinkingOptionId: "high", thinkingOptions: [ { id: "minimal" }, { id: "low" }, { id: "medium" }, { id: "high" }, { id: "xhigh" }, ], }, ], defaultMode: "onRequest", modes: [ { id: "onRequest" }, { id: "promptUnmatched" }, { id: "denyUnmatched" }, { id: "allowAll", isUnattended: true }, ], }, }); await h.send({ type: "catalog", requestId: "catalog-2" }); await h.wait((event) => event.type === "catalog" && event.requestId === "catalog-2"); expect((await h.recorded()).filter((frame) => frame.method === "model/list")).toHaveLength(1); }); test("text and reasoning stream, completion overwrites deltas, user echo retains client identity", async () => { const h = await harness(); expect(await h.open()).toMatchObject({ type: "session.ready" }); await h.prompt(); await h.wait((event) => event.type === "session.turn" && event.state === "completed"); expect(h.events).toEqual( expect.arrayContaining([ expect.objectContaining({ type: "timeline.item", item: expect.objectContaining({ type: "reasoning" }), }), expect.objectContaining({ type: "timeline.item", item: expect.objectContaining({ type: "user_message", clientMessageId: expect.stringMatching(/^client-/), }), }), ]), ); const frames = await h.recorded(); expect(frames.find((frame) => frame.method === "turn/start").params).toMatchObject({ ifBusy: "queue", reasoningEffort: "high", displayText: "hello", input: [ { type: "text", text: "\nTest instructions\n" }, { type: "text", text: "hello" }, ], }); expect(h.events.findLast((event) => event.type === "session.persistence")).toMatchObject({ persistence: { version: 1, data: { model: "meta/muse-spark-1.3", thinkingOption: "high" } }, }); }); test("tool calls classify read, shell, and edit with fetched JSON patch converted to unified diff", async () => { const h = await harness("tools-edit"); await h.open(); await h.prompt(); await h.wait((event) => event.type === "session.turn" && event.state === "completed"); expect(h.events).toEqual( expect.arrayContaining([ expect.objectContaining({ type: "timeline.item", item: expect.objectContaining({ detail: expect.objectContaining({ type: "read", filePath: "f" }), }), }), expect.objectContaining({ type: "timeline.item", item: expect.objectContaining({ detail: expect.objectContaining({ type: "shell", command: "wc -l f", output: "2 f\n" }), }), }), expect.objectContaining({ type: "timeline.item", item: expect.objectContaining({ detail: expect.objectContaining({ type: "edit", unifiedDiff: expect.stringContaining("@@ -1,2 +1,2 @@\n alpha\n-beta\n+gamma\n"), }), }), }), ]), ); }); test("multi-stage approvals surface Stage 2 then Stage 3 and send the current requirement guard", async () => { const h = await harness("multi-stage-approval"); await h.open(); await h.prompt(); const first = await h.wait((event) => event.type === "session.permission"); expect(first).toMatchObject({ request: { title: "Stage 2 of 3", detail: { type: "shell", command: "rm -rf f" }, actions: [ { id: "allow_once", behavior: "allow" }, { id: "allow_local_prefix", behavior: "allow" }, { id: "abort", behavior: "deny" }, ], }, }); if (first.type !== "session.permission") throw new Error("Expected approval"); const from = await h.send({ type: "session.permission", sessionId: "paseo-session", permissionId: first.request.id, response: { behavior: "allow", selectedActionId: first.request.actions![0]!.id }, }); const second = await h.wait((event) => event.type === "session.permission", from); expect(second).toMatchObject({ request: { title: "Stage 3 of 3", detail: { command: "rm -rf g" } }, }); await h.send({ type: "session.permission", sessionId: "paseo-session", permissionId: first.request.id, response: { behavior: "allow", selectedActionId: first.request.actions![0]!.id }, }); await h.wait((event) => event.type === "session.permission_resolved"); await h.wait((event) => event.type === "session.turn" && event.state === "completed"); expect( (await h.recorded()) .filter((frame) => frame.method === "approval/decide") .map((frame) => frame.params.requirementId.sourceIndex), ).toEqual([1, 2]); }); test("Full access automatically answers every escalated stage with the published allow-once choice", async () => { const h = await harness("multi-stage-approval"); await h.open(undefined, "allowAll"); await h.prompt(); await h.wait((event) => event.type === "session.turn" && event.state === "completed"); expect(h.events.filter((event) => event.type === "session.permission")).toEqual([]); expect( (await h.recorded()) .filter((frame) => frame.method === "approval/decide") .map((frame) => frame.params.choiceId), ).toEqual(["allow_once", "allow_once"]); }); test("interrupt awaits cancelled terminal and reconciles the tool", async () => { const h = await harness("interrupt"); await h.open(); await h.prompt(); await h.wait((event) => event.type === "session.prompt_result"); await h.send({ type: "session.interrupt", sessionId: "paseo-session", requestId: "interrupt" }); await h.wait((event) => event.type === "request.completed" && event.requestId === "interrupt"); const terminalIndex = h.events.findIndex( (event) => event.type === "session.turn" && event.state === "canceled", ); expect(terminalIndex).toBeGreaterThan(-1); expect( h.events.findIndex( (event) => event.type === "request.completed" && event.requestId === "interrupt", ), ).toBeGreaterThan(terminalIndex); }); test("a fresh connection restores earlier messages from persisted session state", async () => { const original = await harness(); const historyPath = path.join(original.root, "native-history.ndjson"); original.launch.env.MUSE_TEST_HISTORY = historyPath; await original.open(); await original.prompt(); await original.wait((event) => event.type === "session.turn" && event.state === "completed"); const messages = [ ...new Map( original.events .filter((event) => event.type === "timeline.item") .map((event) => event.item) .filter((item) => item.type === "user_message" || item.type === "assistant_message") .map((item) => [item.id, item] as const), ).values(), ]; expect(messages.map((item) => item.type)).toEqual(["user_message", "assistant_message"]); const saved = original.events.findLast((event) => event.type === "session.persistence"); if (!saved) throw new Error("Expected session persistence"); const persistencePath = path.join(original.root, "persistence.json"); // Older daemon records carry a view cursor even though their timeline is not durable. await writeFile( persistencePath, JSON.stringify({ ...saved.persistence, data: { ...saved.persistence.data, cursor: saved.persistence.data.cursor ?? "v:old:7" }, }), ); await original.connection.close(); const reopened = await harness("text-reasoning", { MUSE_TEST_HISTORY: historyPath }); expect(await reopened.open(JSON.parse(await readFile(persistencePath, "utf8")))).toMatchObject({ type: "session.ready", }); const restored = reopened.events .filter((event) => event.type === "timeline.item") .map((event) => event.item) .filter((item) => item.type === "user_message" || item.type === "assistant_message"); expect(restored.map((item) => ({ type: item.type, text: item.text }))).toEqual( messages.map((item) => ({ type: item.type, text: item.text })), ); const reopenedState = reopened.events.findLast((event) => event.type === "session.persistence"); expect(reopenedState?.persistence.data).not.toHaveProperty("cursor"); expect(reopened.events.filter((event) => event.type === "session.turn")).toEqual([]); await reopened.prompt(); await reopened.wait((event) => event.type === "session.turn" && event.state === "completed"); }); for (const cursor of [undefined, "v:old:7"]) { test(`resume ${cursor ? "with a legacy persistence cursor" : "without a persistence cursor"} replays inline history`, async () => { const h = await harness("resume-without-cursor"); await h.open({ version: 1, data: { sessionId: "saved-session", ...(cursor ? { cursor } : {}) }, }); expect(h.events.filter((event) => event.type === "session.turn")).toEqual([]); const history = h.events.filter((event) => event.type === "timeline.item"); expect(history).toEqual( expect.arrayContaining([ expect.objectContaining({ item: expect.objectContaining({ type: "assistant_message", text: "RESUME_MARKER_OK" }), }), ]), ); await h.prompt(); await h.wait((event) => event.type === "session.turn" && event.state === "completed"); expect( h.events.filter((event) => event.type === "session.turn" && event.state === "completed"), ).toHaveLength(1); expect( (await h.recorded()).find((frame) => frame.method === "session/resume").params, ).toMatchObject({ sessionId: "saved-session" }); expect( (await h.recorded()).find((frame) => frame.method === "session/resume").params, ).not.toHaveProperty("cursor"); }); } test("steer sends ifBusy steer and joins the running turn", async () => { const h = await harness("steer"); await h.open(); await h.prompt(); await h.wait((event) => event.type === "session.prompt_result"); const from = await h.prompt("steer"); expect(await h.wait((event) => event.type === "session.prompt_result", from)).toMatchObject({ result: { type: "steer" }, }); await h.wait((event) => event.type === "session.turn" && event.state === "completed"); expect( (await h.recorded()).filter((frame) => frame.method === "turn/start")[1].params.ifBusy, ).toBe("steer"); }); test("image turn sends a native image and the subsequent text turn succeeds", async () => { const h = await harness("image-followup"); await h.open(); await h.prompt("auto", true); await h.wait((event) => event.type === "session.turn" && event.state === "completed"); const from = await h.prompt(); await h.wait((event) => event.type === "session.turn" && event.state === "completed", from); const turns = (await h.recorded()).filter((frame) => frame.method === "turn/start"); expect(turns[0].params.input).toContainEqual({ type: "image", mediaType: "image/png", base64Data: "image-bytes", }); expect(turns[1].params.input).toEqual([{ type: "text", text: "hello" }]); }); for (const source of ["live", "history", "backfill"]) { test(`reminder housekeeping emits no timeline items or child sessions from ${source}`, async () => { const scenario = source === "history" ? "resume-without-cursor" : "text-reasoning"; const frames = ( await readFile(new URL(`./fixtures/${scenario}.ndjson`, import.meta.url), "utf8") ) .trim() .split("\n") .map((line) => JSON.parse(line)); const reminderIds = new Set( frames .flatMap(({ msg }) => msg.params?.item ? [msg.params.item] : (msg.result?.history?.items ?? []), ) .filter((item) => item?.kind === "reminderChild") .map((item) => item.itemId), ); expect(reminderIds.size).toBeGreaterThan(0); const h = await harness(scenario, source === "backfill" ? { MUSE_TEST_GAP: "1" } : {}); await h.open( source === "history" ? { version: 1, data: { sessionId: "saved-session" } } : undefined, ); await h.prompt(); await h.wait((event) => event.type === "session.turn" && event.state === "completed"); expect( h.events.filter((event) => event.type === "timeline.item" && reminderIds.has(event.item.id)), ).toEqual([]); expect( h.events.filter((event) => event.type === "session.opened").map((event) => event.sessionId), ).toEqual(["paseo-session"]); expect((await h.recorded()).filter((frame) => frame.method === "session/read")).toEqual([]); }); } for (const [scenario, code, guidance] of [ ["auto-review-unavailable", "defaultProfileUnavailable", "permissions.default_profile"], ["unsafe-path", "unsafePath", path.join("/test-data", "muse")], ]) { test(`${scenario} is a typed actionable construction error`, async () => { const h = await harness(scenario, { XDG_DATA_HOME: "/test-data" }); expect(await h.open()).toMatchObject({ type: "request.failed", error: { code, message: expect.stringContaining(guidance!) }, }); }); } for (const [code, kind] of [ [0, "cleanExit"], [2, "usage"], [3, "credentials"], [4, "leaseHeld"], [5, "surfaceOff"], [17, "crash"], ]) { test(`exit ${code} is classified ${kind} with stderr diagnostics`, async () => { const h = await harness("text-reasoning", { MUSE_TEST_EXIT: String(code) }); expect(await h.open()).toMatchObject({ type: "request.failed", error: { code: kind, message: expect.stringContaining("diagnostic tail, not protocol") }, }); }); } for (const [env, expected] of [ [ { MUSE_TEST_VERSION: "1.2.9" }, { available: false, diagnostic: "Update Muse Code: found 1.2.9, need ≥1.3.0" }, ], [ { MUSE_TEST_ACCOUNT: "loggedOut" }, { available: false, diagnostic: "Run `muse login` or set META_API_KEY" }, ], [{ MUSE_TEST_ACCOUNT: "envKey" }, { available: true }], [{ MUSE_TEST_ACCOUNT: "apiKey" }, { available: true }], [{ MUSE_TEST_ACCOUNT: "accountLogin" }, { available: true }], ] as const) { test(`status checks version and credential state ${JSON.stringify(env)}`, async () => { const h = await harness("catalog-controls", env); expect(await h.provider.status!({ launch: h.launch })).toEqual(expected); }); } test("status preserves the version-probe command and stderr diagnostic", async () => { const h = await harness("catalog-controls", { MUSE_TEST_VERSION_ERROR: "Muse version probe could not read its installation metadata", }); // Compare against the original execFile callback: wrapping in MuseError only changed its kind. const previousDiagnostic = await new Promise((resolve, reject) => { execFile(h.launch.command, [...h.launch.args, "--version"], { env: h.launch.env }, (error) => { if (error) resolve(error.message); else reject(new Error("Expected the version probe to fail")); }); }); const status = await h.provider.status!({ launch: h.launch }); expect(status).toEqual({ available: false, diagnostic: previousDiagnostic }); expect(status.diagnostic).toContain("--version"); expect(status.diagnostic).toContain( "Muse version probe could not read its installation metadata", ); expect(await h.recorded()).toEqual([]); }); test("status reports a missing executable", async () => { const h = await harness(); expect( await h.provider.status!({ launch: { ...h.launch, command: "/missing/muse" } }), ).toMatchObject({ available: false, diagnostic: expect.stringContaining("ENOENT") }); }); test("overloaded retry reuses UUIDv7 commandId", async () => { const h = await harness("text-reasoning", { MUSE_TEST_RETRY: "turn/start" }); await h.open(); await h.prompt(); await h.wait((event) => event.type === "session.turn" && event.state === "completed"); const turns = (await h.recorded()).filter((frame) => frame.method === "turn/start"); expect(turns).toHaveLength(2); expect(turns[0].params.commandId).toBe(turns[1].params.commandId); }); test("missing saved session refuses to start fresh", async () => { const h = await harness("resume-without-cursor", { MUSE_TEST_GONE: "1" }); expect(await h.open({ version: 1, data: { sessionId: "gone" } })).toMatchObject({ type: "request.failed", error: { code: "sessionNotFound", message: "This Muse session no longer exists. Create a new agent.", }, }); expect((await h.recorded()).filter((frame) => frame.method === "session/start")).toEqual([]); }); test("empty resume viewCursor is a typed error", async () => { const h = await harness("resume-without-cursor", { MUSE_TEST_EMPTY_CURSOR: "1" }); expect(await h.open({ version: 1, data: { sessionId: "saved" } })).toMatchObject({ type: "request.failed", error: { code: "emptyViewCursor" }, }); }); test("catalogue identity includes command and routing credentials without exposing them", async () => { const h = await harness(); const first = await h.provider.getCatalogCacheKey!({ scope: "global", launch: h.launch }); const second = await h.provider.getCatalogCacheKey!({ scope: "global", launch: { ...h.launch, env: { ...h.launch.env, META_API_KEY: "secret" } }, }); expect(first).not.toBe(second); expect(second).not.toContain("secret"); }); test("configure reports the effective model, approval mode, and per-turn effort", async () => { const h = await harness("catalog-controls"); await h.open(); const from = await h.send({ type: "session.configure", requestId: "configure", sessionId: "paseo-session", changes: { model: "meta/muse-spark-1.3", mode: "promptUnmatched", thinkingOption: "low" }, }); await h.wait((event) => event.type === "request.completed" && event.requestId === "configure"); expect(h.events.slice(from)).toContainEqual( expect.objectContaining({ type: "session.config", config: expect.objectContaining({ model: "meta/muse-spark-1.3", mode: "promptUnmatched", thinkingOption: "low", models: [expect.objectContaining({ id: "meta/muse-spark-1.3" })], }), }), ); }); test("CLI-style allow response selects a published allow action without a supplied id", async () => { const h = await harness("staged-approval"); await h.open(); await h.prompt(); const permissionEvent = await h.wait((candidate) => candidate.type === "session.permission"); if (permissionEvent.type !== "session.permission") throw new Error("Expected approval"); await h.send({ type: "session.permission", sessionId: "paseo-session", permissionId: permissionEvent.request.id, response: { behavior: "allow" }, }); await h.wait((event) => event.type === "session.turn" && event.state === "completed"); expect( (await h.recorded()).find((frame) => frame.method === "approval/decide").params.choiceId, ).toBe("allow_once"); }); test("closing cancels a catalogue host instead of waiting for a stalled request", async () => { const h = await harness("catalog-controls", { MUSE_TEST_HANG: "model/list" }); await h.send({ type: "catalog", requestId: "catalog" }); await expect .poll(async () => (await h.recorded()).some((frame) => frame.method === "model/list")) .toBe(true); await h.connection.close(); expect(h.events.filter((event) => event.type === "catalog")).toEqual([]); expect(await h.recorded()).toContainEqual({ event: "hostClosed" }); }); for (const retryKind of ["overloaded", "backpressured"]) { test(`${retryKind} session construction retries with the same UUIDv7`, async () => { const h = await harness("text-reasoning", { MUSE_TEST_RETRY: "session/start", MUSE_TEST_RETRY_KIND: retryKind, }); expect(await h.open()).toMatchObject({ type: "session.ready" }); const attempts = (await h.recorded()).filter((frame) => frame.method === "session/start"); expect(attempts).toHaveLength(2); expect(attempts[0].params.commandId).toBe(attempts[1].params.commandId); }); } test("higher-revision item completion replaces streamed text and ignores stale snapshots", async () => { const h = await harness("text-reasoning", { MUSE_TEST_STALE: "1" }); await h.open(); await h.prompt(); await h.wait((event) => event.type === "session.turn" && event.state === "completed"); const assistant = h.events.filter( (event) => event.type === "timeline.item" && event.item.type === "assistant_message", ); expect(assistant.length).toBeGreaterThan(2); expect(assistant.at(-1)).toMatchObject({ item: { text: expect.stringContaining("`147/60 = 49/20`") }, }); expect(assistant).not.toContainEqual( expect.objectContaining({ item: expect.objectContaining({ text: "STALE_REVISION" }) }), ); expect(h.events).toContainEqual( expect.objectContaining({ type: "timeline.item", item: { type: "reasoning", id: "f3c7d3fc-1644-46c3-b63c-74e0c7dee6c3", text: "Computing the rational sum by finding a common denominator and reducing the fraction to lowest terms via gcd.", }, }), ); }); test("ordinary subagent tool calls render sub_agent details without inventing child identity", async () => { const h = await harness("subagent"); await h.open(); await h.prompt(); await h.wait((event) => event.type === "session.turn" && event.state === "completed"); expect(h.events).toContainEqual( expect.objectContaining({ type: "timeline.item", item: expect.objectContaining({ name: "subagent_read_result", status: "completed", detail: expect.objectContaining({ type: "sub_agent", log: expect.stringContaining("SUBAGENT_PHASE0_OK"), }), }), }), ); }); for (const [variants, expected] of [ [ ["low", "high"], ["low", "high"], ], ["unknown", ["minimal", "low", "medium", "high", "xhigh", "max", "ultra"]], [[], ["minimal", "low", "medium", "high", "xhigh", "max", "ultra"]], ] as const) { test(`catalogue effort discovery with variants ${JSON.stringify(variants)}`, async () => { const model = { modelId: "test-model", providerId: "meta", displayLabel: "Test model", contextLimit: 123456, isDefault: true, variants, defaultReasoningEffort: "high", }; const h = await harness("catalog-controls", { MUSE_TEST_MODELS: JSON.stringify([model]) }); await h.send({ type: "catalog", requestId: "models" }); const result = await h.wait((event) => event.type === "catalog"); expect(result).toMatchObject({ catalog: { defaultModel: "test-model", models: [ { contextWindowMaxTokens: 123456, thinkingOptions: expected.map((id) => ({ id })) }, ], }, }); }); } test("catalogue loads models whose effort tiers carry descriptions", async () => { const deepest = "Use this for deepest analysis and complex fixes."; const row = (modelId: string, isDefault: boolean) => ({ modelId, providerId: "meta", displayLabel: modelId, contextLimit: 1000000, isDefault, defaultReasoningEffort: "max", variants: ["minimal", "low", "medium", "high", "xhigh", "max"], reasoningEffortVariants: isDefault ? [] : [{ tier: "xhigh", description: deepest }], }); const models = [ row("muse-spark-1.3", true), row("muse-spark-1.3-contributor", false), row("muse-spark-1.2", false), row("muse-spark-1.2-contributor", false), ]; const h = await harness("catalog-controls", { MUSE_TEST_MODELS: JSON.stringify(models) }); await h.send({ type: "catalog", requestId: "models" }); const result = await h.wait( (event) => event.type === "catalog" || event.type === "request.failed", ); const offered = [ { id: "minimal" }, { id: "low" }, { id: "medium" }, { id: "high" }, { id: "xhigh" }, { id: "max", isDefault: true }, ]; expect(result).toMatchObject({ type: "catalog", catalog: { defaultModel: "muse-spark-1.3", models: [ { id: "muse-spark-1.3", defaultThinkingOptionId: "max" }, { id: "muse-spark-1.3-contributor", thinkingOptions: offered }, { id: "muse-spark-1.2", thinkingOptions: offered }, { id: "muse-spark-1.2-contributor", thinkingOptions: offered }, ], }, }); }); for (const [configuredModel, catalogModels, expectedModel] of [ ["configured-model", [], "configured-model"], [ "", [ { modelId: "catalog-model", providerId: "meta", displayLabel: "Catalog model", contextLimit: null, isDefault: true, variants: ["high"], }, ], "catalog-model", ], ["", [], undefined], ] as const) { test(`null session model preserves ${expectedModel ?? "unset model"}`, async () => { const h = await harness("text-reasoning", { MUSE_TEST_NULL_MODEL: "1", MUSE_TEST_MODELS: JSON.stringify(catalogModels), }); expect(await h.open(undefined, "onRequest", configuredModel || null)).toEqual({ type: "session.ready", requestId: "open", sessionId: "paseo-session", }); const config = await h.wait((event) => event.type === "session.config"); if (config.type !== "session.config") throw new Error("Expected session config"); expect(config.config.model).toBe(expectedModel); const saved = h.events.findLast((event) => event.type === "session.persistence"); if (!saved) throw new Error("Expected persistence"); expect(saved.persistence.data.model).toBe(expectedModel); await h.prompt(); await h.wait((event) => event.type === "session.turn" && event.state === "completed"); }); } test("session startup accepts described catalog efforts and publishes the complete list", async () => { const model = { modelId: "meta/muse-spark-1.3", providerId: "meta", displayLabel: "Muse Spark", contextLimit: null, isDefault: true, defaultReasoningEffort: "max", variants: ["minimal", "low", "medium", "high", "xhigh", "max"], reasoningEffortVariants: [{ tier: "xhigh", description: "Deepest analysis" }], }; const h = await harness("text-reasoning", { MUSE_TEST_MODELS: JSON.stringify([model]) }); expect(await h.open()).toEqual({ type: "session.ready", requestId: "open", sessionId: "paseo-session", }); expect(await h.wait((event) => event.type === "session.config")).toMatchObject({ config: { models: [{ id: model.modelId, defaultThinkingOptionId: "max" }], thinkingOptions: model.variants.map((id) => ({ id, isDefault: id === "max" })), }, }); await h.send({ type: "session.configure", requestId: "choose-max", sessionId: "paseo-session", changes: { thinkingOption: "max" }, }); await h.wait((event) => event.type === "request.completed" && event.requestId === "choose-max"); await h.prompt(); await h.wait((event) => event.type === "session.turn" && event.state === "completed"); expect( (await h.recorded()).find((frame) => frame.method === "turn/start").params.reasoningEffort, ).toBe("max"); }); for (const metadata of [ "unknown", [{ tier: "future-tier", description: { text: "Future metadata" } }], ]) { test(`catalog ignores unused effort descriptions ${JSON.stringify(metadata)}`, async () => { const model = { modelId: "test-model", providerId: "meta", displayLabel: "Muse Spark", contextLimit: null, isDefault: true, defaultReasoningEffort: "high", variants: ["low", "high"], reasoningEffortVariants: metadata, }; const h = await harness("catalog-controls", { MUSE_TEST_MODELS: JSON.stringify([model]) }); await h.send({ type: "catalog", requestId: "models" }); expect( await h.wait((event) => event.type === "catalog" || event.type === "request.failed"), ).toMatchObject({ type: "catalog", catalog: { models: [ { id: model.modelId, thinkingOptions: [{ id: "low" }, { id: "high", isDefault: true }] }, ], }, }); }); } test("status accepts future credential states and drains its maintenance host", async () => { const h = await harness("catalog-controls", { MUSE_TEST_ACCOUNT: "futureCredential" }); expect(await h.provider.status!({ launch: h.launch })).toEqual({ available: true }); expect(await h.recorded()).toContainEqual({ event: "hostClosed" }); }); for (const [tool, detail] of [ ["grep", "search"], ["glob", "search"], ["ls", "search"], ["web_search", "search"], ["web_fetch", "fetch"], ["custom_tool", "unknown"], ]) { test(`${tool} tool calls use ${detail} detail`, async () => { const h = await harness("tools-edit", { MUSE_TEST_TOOL: tool! }); await h.open(); await h.prompt(); await h.wait((event) => event.type === "session.turn" && event.state === "completed"); expect(h.events).toContainEqual( expect.objectContaining({ type: "timeline.item", item: expect.objectContaining({ name: tool, status: "completed", detail: expect.objectContaining({ type: detail }), }), }), ); }); } test("a future item kind uses fallbackText without breaking turn completion", async () => { const h = await harness("tools-edit", { MUSE_TEST_UNKNOWN_KIND: "1" }); await h.open(); await h.prompt(); await h.wait((event) => event.type === "session.turn" && event.state === "completed"); expect(h.events).toContainEqual( expect.objectContaining({ type: "timeline.item", item: expect.objectContaining({ name: "futureItem", detail: { type: "plain_text", label: "futureItem", text: "New MSP item" }, }), }), ); }); test("fingerprint mismatch is logged but does not prevent opening", async () => { const h = await harness("text-reasoning", { MUSE_TEST_FINGERPRINT: "sha256:unverified" }); expect(await h.open()).toMatchObject({ type: "session.ready" }); }); for (const restoring of [false, true]) { test(`MCP servers map to MSP stdio and streamableHttp on ${restoring ? "resume" : "start"}`, async () => { const h = await harness(restoring ? "resume-without-cursor" : "text-reasoning"); await h.send({ type: "session.open", sessionId: "paseo-session", requestId: "mcp", persistence: restoring ? { version: 1, data: { sessionId: "saved-session", cursor: "v:old:7" } } : undefined, history: "replay", config: { cwd: h.root, env: {}, persist: true, settings: {}, mcpServers: { stdio: { type: "stdio", command: "test-server", args: ["--serve"], env: { TEST_ENV: "yes" }, }, paseo: { type: "http", url: "http://localhost/mcp/agents", headers: { Authorization: "Bearer test-token" }, }, }, }, }); await h.wait((event) => event.type === "session.ready"); expect( (await h.recorded()).find( (frame) => frame.method === (restoring ? "session/resume" : "session/start"), ).params.config.mcpServers, ).toEqual({ stdio: { transport: "stdio", command: "test-server", args: ["--serve"], env: { TEST_ENV: "yes" }, framing: "lineDelimitedJson", }, paseo: { transport: "streamableHttp", url: "http://localhost/mcp/agents", headers: { Authorization: "Bearer test-token" }, }, }); }); } test("authRequired turn errors retain their kind and explain login recovery", async () => { const h = await harness("text-reasoning", { MUSE_TEST_AUTH_REQUIRED: "1" }); await h.open(); await h.prompt(); expect( await h.wait((event) => event.type === "session.turn" && event.state === "failed"), ).toMatchObject({ error: { code: "authRequired", message: "Run `muse login` or set META_API_KEY." }, }); }); test("status bounds a stalled account read and closes the host", async () => { const h = await harness("catalog-controls", { MUSE_TEST_HANG: "account/read" }); expect(await h.provider.status!({ launch: h.launch })).toEqual({ available: false, diagnostic: "Muse account/read timed out", }); expect(await h.recorded()).toContainEqual({ event: "hostClosed" }); }); test("interrupt targets the running turn when another prompt is queued", async () => { const h = await harness("interrupt", { MUSE_TEST_QUEUE: "1" }); await h.open(); await h.prompt(); await h.wait((event) => event.type === "session.prompt_result"); const from = await h.prompt(); await h.wait((event) => event.type === "session.prompt_result", from); await h.send({ type: "session.interrupt", sessionId: "paseo-session", requestId: "interrupt-queued", }); await h.wait( (event) => event.type === "request.completed" && event.requestId === "interrupt-queued", ); const requests = await h.recorded(); const active = requests.find((frame) => frame.method === "turn/start"); expect(requests.find((frame) => frame.method === "turn/interrupt").params.turnId).toBe( active.params.commandId, ); }); for (const terminal of ["completed", "failed"]) { test(`interrupt resolves when its targeted turn independently ends ${terminal}`, async () => { const h = await harness("interrupt", { MUSE_TEST_INTERRUPT_TERMINAL: terminal }); await h.open(); await h.prompt(); await h.wait((event) => event.type === "session.prompt_result"); await h.send({ type: "session.interrupt", sessionId: "paseo-session", requestId: "racing-interrupt", }); await h.wait((event) => event.type === "session.turn" && event.state === terminal); await h.wait( (event) => event.type === "request.completed" && event.requestId === "racing-interrupt", ); expect(h.events.filter((event) => event.type === "request.failed")).toEqual([]); }); } for (const [behavior, choiceId] of [ ["allow", "allow_once"], ["deny", "abort"], ] as const) { test(`CLI ${behavior} prefers the plain once choice over a broader fixture grant`, async () => { const h = await harness("staged-approval", { MUSE_TEST_CHOICES: "reverse" }); await h.open(); await h.prompt(); const permission = await h.wait((event) => event.type === "session.permission"); if (permission.type !== "session.permission") throw new Error("Expected permission"); await h.send({ type: "session.permission", sessionId: "paseo-session", permissionId: permission.request.id, response: { behavior }, }); await expect .poll( async () => (await h.recorded()).find((frame) => frame.method === "approval/decide")?.params.choiceId, ) .toBe(choiceId); }); } test("CLI deny rejects a tool without aborting when denied/once is offered", async () => { const h = await harness("staged-approval", { MUSE_TEST_DENIED: "1" }); await h.open(); await h.prompt(); const permission = await h.wait((event) => event.type === "session.permission"); if (permission.type !== "session.permission") throw new Error("Expected permission"); await h.send({ type: "session.permission", sessionId: "paseo-session", permissionId: permission.request.id, response: { behavior: "deny" }, }); await expect .poll( async () => (await h.recorded()).find((frame) => frame.method === "approval/decide")?.params.choiceId, ) .toBe("reject_once"); }); test("skills publish commands and command prompts submit structured skill parts", async () => { const h = await harness("skill"); await h.open(); expect(await h.wait((e) => e.type === "session.commands")).toMatchObject({ commands: expect.arrayContaining([ expect.objectContaining({ name: "phase0", description: expect.any(String) }), expect.objectContaining({ name: "compact", description: expect.any(String) }), ]), }); await h.send({ type: "session.prompt", sessionId: "paseo-session", prompt: { clientMessageId: "skill-client", delivery: "auto", input: { type: "command", name: "phase0", arguments: "argument text" }, }, }); await h.wait((e) => e.type === "session.turn" && e.state === "completed"); expect((await h.recorded()).find((f) => f.method === "turn/start").params).toMatchObject({ input: expect.arrayContaining([ { type: "skill", selector: "phase0", arguments: "argument text" }, ]), displayText: "/phase0 argument text", }); }); test("skill changes refresh the session command catalog", async () => { const h = await harness("skill", { MUSE_TEST_SKILLS_CHANGED: "1" }); await h.open(); const from = await h.prompt(); expect(await h.wait((e) => e.type === "session.commands", from)).toMatchObject({ commands: [{ name: "compact", description: "Compact conversation context" }], }); expect((await h.recorded()).filter((f) => f.method === "skill/list")).toHaveLength(2); }); for (const mode of ["answer", "cancel"] as const) { test(`todo and question ${mode} replay through the provider interface`, async () => { const h = await harness(mode === "answer" ? "phase3-controls" : "phase3-cancel"); await h.open(); await h.prompt(); if (mode === "answer") expect( await h.wait((e) => e.type === "timeline.item" && e.item.type === "todo"), ).toMatchObject({ item: { items: [ { text: "inspect f", completed: false, status: "in_progress" }, { text: "report", completed: false, status: "pending" }, ], }, }); const permission = await h.wait( (e) => e.type === "session.permission" && e.request.kind === "question", ); if (permission.type !== "session.permission") throw new Error("Expected question"); expect(permission.request.input).toMatchObject({ questions: [ { header: "Color", options: [{ label: "Blue" }, { label: "Green" }], multiSelect: false }, ], }); await h.send({ type: "session.permission", sessionId: "paseo-session", permissionId: permission.request.id, response: mode === "answer" ? { behavior: "allow", updatedInput: { answers: { Color: "Blue" } } } : { behavior: "deny", message: "QA cancellation" }, }); await h.wait( (e) => e.type === "session.permission_resolved" && e.permissionId === permission.request.id, ); const frame = (await h.recorded()).find( (f) => f.method === (mode === "answer" ? "userInput/answer" : "userInput/cancel"), ); expect(frame.params.userInputId).toBe(permission.request.id); if (mode === "answer") expect(frame.params.answers).toEqual([{ questionId: "color_choice", selectedLabel: "Blue" }]); expect((await h.recorded()).some((f) => f.result && Object.keys(f.result).length === 0)).toBe( true, ); }); } for (const variant of ["tool", "dedicated", "workflow"]) { test(`${variant} subagent opens only the supplied child and pages its real transcript`, async () => { const h = await harness("subagent", { MUSE_TEST_CHILD: variant }); await h.open(); await h.prompt(); expect( await h.wait((e) => e.type === "session.opened" && e.sessionId === "fixture-child"), ).toMatchObject({ parentSessionId: "paseo-session", restoration: "parent", toolCallId: expect.any(String), }); expect( await h.wait( (e) => e.type === "timeline.item" && e.sessionId === "fixture-child" && e.item.type === "assistant_message", ), ).toMatchObject({ item: { text: "SUBAGENT_PHASE0_OK" } }); await h.wait((e) => e.type === "session.turn" && e.state === "completed"); expect( h.events.filter((e) => e.type === "session.opened" && e.sessionId === "fixture-child"), ).toHaveLength(1); expect( (await h.recorded()) .filter((f) => f.method === "session/read") .map((f) => f.params.sessionId), ).toEqual(["fixture-child"]); expect( h.events.filter( (e) => e.type === "timeline.item" && e.sessionId === "fixture-child" && e.item.type === "assistant_message", ), ).toHaveLength(1); }); } test("gap backfill repairs a missing completion without duplicating item revisions", async () => { const h = await harness("text-reasoning", { MUSE_TEST_GAP: "1" }); await h.open(); await h.prompt(); await h.wait((e) => e.type === "session.turn" && e.state === "completed"); const frames = (await h.recorded()).filter((f) => f.method === "view/page"); expect(frames.map((f) => f.params.cursor)).toEqual(["fixture-last-cursor"]); const messages = h.events.filter( (e) => e.type === "timeline.item" && e.item.type === "assistant_message", ); expect(messages.at(-1)).toMatchObject({ item: { text: expect.stringContaining("49/20") } }); expect(h.events.filter((e) => e.type === "session.turn" && e.state === "completed")).toHaveLength( 1, ); }); test("silent active turns page after two minutes and stop polling on recovered terminal", async () => { const h = await harness("text-reasoning", { MUSE_TEST_SILENT: "1" }); await h.open(); vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout"] }); await h.prompt(); await h.wait((e) => e.type === "session.prompt_result"); await vi.advanceTimersByTimeAsync(119999); expect((await h.recorded()).filter((f) => f.method === "view/page")).toEqual([]); await vi.advanceTimersByTimeAsync(1); await h.wait((e) => e.type === "session.turn" && e.state === "completed"); await vi.advanceTimersByTimeAsync(240000); expect((await h.recorded()).filter((f) => f.method === "view/page")).toHaveLength(1); }); test("session readiness accepts initialization beyond the old event polling window", async () => { const h = await harness("text-reasoning", { MUSE_TEST_INITIALIZE_DELAY_MS: "3500" }); expect(await h.open()).toMatchObject({ type: "session.ready" }); expect((await h.recorded()).find((frame) => frame.event === "hostStarted").args).toEqual([ "serve", "--sandbox-network", "proxy-only", "--disable-sandbox", "--trust-workspace", ]); }, 10000); test("default launch disables sandbox and trusts workspace", async () => { const h = await harness(); await h.open(); expect((await h.recorded()).find((f) => f.event === "hostStarted").args).toEqual([ "serve", "--sandbox-network", "proxy-only", "--disable-sandbox", "--trust-workspace", ]); }); test.each([ [{ sandbox: { enabled: true } }, ["--sandbox-network", "proxy-only", "--trust-workspace"]], [{ trustWorkspace: false }, ["--sandbox-network", "proxy-only", "--disable-sandbox"]], [ { sandbox: { network: "restricted" } }, ["--sandbox-network", "restricted", "--disable-sandbox", "--trust-workspace"], ], [ { sandbox: { network: "enabled" } }, ["--sandbox-network", "enabled", "--disable-sandbox", "--trust-workspace"], ], [ { sandbox: { enabled: true, network: "proxy-only" }, trustWorkspace: false }, ["--sandbox-network", "proxy-only"], ], ])("provider options %j control launch flags", async (params, args) => { const h = await harness("text-reasoning", {}, params); await h.open(); expect((await h.recorded()).find((f) => f.event === "hostStarted").args).toEqual([ "serve", ...args, ]); }); test.each([ [{ sandbox: { enabled: "true" } }, "sandbox.enabled"], [{ sandbox: { network: "invalid" } }, "sandbox.network"], [{ trustWorkspace: 1 }, "trustWorkspace"], [{ unexpected: true }, "unexpected"], ])("invalid provider options %j fail session creation clearly", async (options, field) => { const h = await harness("text-reasoning", {}, options); const event = await h.open(); expect(event).toMatchObject({ type: "request.failed", error: { code: "invalidProviderOptions" }, }); if (event.type !== "request.failed") throw new Error("Expected failure"); expect(event.error.message).toContain("Invalid Muse providerOptions"); expect(event.error.message).toContain(field); expect(await h.recorded()).toEqual([]); }); test("resuming a session reapplies its provider options to the new host", async () => { const h = await harness( "resume-without-cursor", {}, { sandbox: { enabled: true, network: "restricted" }, trustWorkspace: false, }, ); await h.open({ version: 1, data: { sessionId: "saved-session" } }); const saved = h.events.find((e) => e.type === "session.opened"); if (saved?.type !== "session.opened" || !saved.persistence) throw new Error("Expected persistence"); await h.send({ type: "session.close", sessionId: "paseo-session", requestId: "close" }); await h.wait((e) => e.type === "session.closed"); const from = h.events.length; await h.open(saved.persistence); await h.wait((e) => e.type === "session.ready", from); const frames = await h.recorded(); expect(frames.filter((f) => f.event === "hostStarted").map((f) => f.args)).toEqual([ ["serve", "--sandbox-network", "restricted"], ["serve", "--sandbox-network", "restricted"], ]); expect(frames.some((f) => f.method === "session/resume")).toBe(true); }); test("an uncorrelated MSP parse error keeps the provider diagnostic", async () => { const h = await harness("text-reasoning", { MUSE_TEST_NULL_ERROR_ID: "1" }); expect(await h.open()).toMatchObject({ type: "request.failed", error: { code: "parseError", message: "Cannot recover request id" }, }); }); test("text deltas with an omitted field stream as text", async () => { const h = await harness("text-reasoning", { MUSE_TEST_IMPLICIT_TEXT_DELTA: "1" }); await h.open(); await h.wait((e) => e.type === "session.ready"); await h.prompt(); await h.wait((e) => e.type === "session.turn" && e.state === "completed"); expect( h.events.some( (e) => e.type === "timeline.item" && e.item.type === "assistant_message" && e.item.text === "Sum with", ), ).toBe(true); }); test("sessions with a null model import and resume without a null persistence model", async () => { const h = await harness("resume-without-cursor", { MUSE_TEST_NULL_MODEL: "1" }); await h.send({ type: "sessions", requestId: "null-list", cwd: "/tmp/muse-phase0/repo", limit: 2, }); const listed = await h.wait((e) => e.type === "sessions"); if (listed.type !== "sessions") throw new Error("Expected sessions"); expect(listed.sessions).toHaveLength(2); expect(listed.sessions[0]!.persistence.data.model).toBeUndefined(); await h.open(listed.sessions[0]!.persistence); await h.wait((e) => e.type === "session.ready"); expect( h.events.some((e) => e.type === "session.config" && e.config.model === "meta/muse-spark-1.3"), ).toBe(true); }); test("sessions list filters workspace and imported persistence opens via resume", async () => { const h = await harness("resume-without-cursor"); await h.send({ type: "sessions", requestId: "import-list", cwd: "/tmp/muse-phase0/repo", limit: 2, }); const listed = await h.wait((e) => e.type === "sessions"); if (listed.type !== "sessions") throw new Error("Expected sessions"); expect(listed.sessions).toHaveLength(2); expect((await h.recorded()).find((f) => f.method === "session/list").params.workspaceRoot).toBe( "/tmp/muse-phase0/repo", ); await h.open(listed.sessions[0]!.persistence); expect((await h.recorded()).filter((f) => f.method === "session/start")).toEqual([]); expect((await h.recorded()).find((f) => f.method === "session/resume").params.sessionId).toBe( listed.sessions[0]!.persistence.data.sessionId, ); }); for (const fixture of ["phase3-compact", "phase3-compact-history", "phase3-compact-limit"]) { test(`${fixture} routes compact and exposes the recorded outcome`, async () => { const h = await harness(fixture); const rows = ( await readFile( fileURLToPath(new URL(`./fixtures/${fixture}.ndjson`, import.meta.url)), "utf8", ) ) .trim() .split("\n") .map((line) => JSON.parse(line)); const nativeId = rows.find((row) => row.msg.method === "session/resume").msg.params.sessionId; await h.open({ version: 1, data: { sessionId: nativeId } }); await h.send({ type: "session.prompt", sessionId: "paseo-session", prompt: { clientMessageId: "compact", delivery: "auto", input: { type: "command", name: "compact", arguments: "" }, }, }); if (fixture === "phase3-compact-limit") { expect(await h.wait((e) => e.type === "session.prompt_result")).toMatchObject({ result: { type: "completed" }, }); expect( await h.wait( (e) => e.type === "timeline.item" && e.item.type === "compaction" && e.item.status === "completed", ), ).toMatchObject({ item: { preTokens: 22192 } }); } else expect(await h.wait((e) => e.type === "session.prompt_result")).toMatchObject({ result: { type: "failed", error: { code: "commandRejected", message: expect.stringContaining("context_compaction.provider_context_limit_tokens"), }, }, }); expect((await h.recorded()).filter((f) => f.method === "turn/start")).toEqual([]); }); } for (const usage of [ {}, { usage: { observedAtMs: 1700000000000, tier: "Pro", window: { usedPercent: 75, resetsAtMs: 1700018000000, windowDurationMins: 300 }, weekly: { usedPercent: 105, resetsAtMs: 1700604800000 }, }, }, ]) { test(`usage source presents ${"usage" in usage ? "subscription windows" : "real route absence"} from discovered accounts`, async () => { const h = await harness("catalog-controls", { MUSE_TEST_USAGE: JSON.stringify(usage) }); await h.open(); const inputs = await h.usageSource.discover({ kind: "global" }); expect(inputs).toHaveLength(1); const account = inputs[0]!; expect(account).toEqual({ key: expect.stringMatching(/^[a-f0-9]{64}$/), label: "Muse Code", input: { account: account.key }, }); const report = await h.usageSource.fetch(account.input); if ("usage" in usage) expect(report).toMatchObject({ status: "available", planLabel: "Pro", windows: [ { id: "five_hour", label: "5-hour", usedPct: 75, remainingPct: 25, tone: "warning", }, { id: "weekly", usedPct: 105, remainingPct: 0, tone: "danger" }, ], }); else expect(report).toEqual({ status: "unavailable", problem: { kind: "no_quota", detail: "No usage quota reported" }, }); expect((await h.recorded()).filter((f) => f.method === "usage/read")).toHaveLength(1); }); } test("a terminal before turn admission does not start an idle watchdog", async () => { const h = await harness("text-reasoning", { MUSE_TEST_ACK_LAST: "1" }); await h.open(); vi.useFakeTimers({ toFake: ["setTimeout", "clearTimeout"] }); await h.prompt(); await h.wait((e) => e.type === "session.prompt_result"); await h.wait((e) => e.type === "session.turn" && e.state === "completed"); await vi.advanceTimersByTimeAsync(120000); await delay(30); expect((await h.recorded()).filter((f) => f.method === "view/page")).toEqual([]); }); test("admitted compact publishes compaction progress and completes the command without a turn", async () => { const h = await harness("text-reasoning", { MUSE_TEST_COMPACT: "1" }); await h.open(); await h.send({ type: "session.prompt", sessionId: "paseo-session", prompt: { clientMessageId: "compact-admitted", delivery: "auto", input: { type: "command", name: "compact", arguments: "" }, }, }); expect(await h.wait((e) => e.type === "session.prompt_result")).toMatchObject({ result: { type: "completed" }, }); await h.wait( (e) => e.type === "timeline.item" && e.item.type === "compaction" && e.item.status === "completed", ); expect( h.events .filter((e) => e.type === "timeline.item" && e.item.type === "compaction") .map((e) => e.type === "timeline.item" && e.item), ).toEqual([ { type: "compaction", id: "fixture-compaction", status: "loading" }, { type: "compaction", id: "fixture-compaction", status: "completed" }, ]); expect((await h.recorded()).filter((f) => f.method === "turn/start")).toEqual([]); }); test("default workflow children render structured tool details from the real no-flag run", async () => { const h = await harness("text-reasoning", { MUSE_TEST_WORKFLOW: "1" }); await h.open(); await h.prompt(); const event = await h.wait( (e) => e.type === "timeline.item" && e.item.type === "tool_call" && e.item.name === "workflow" && e.item.detail.type === "sub_agent" && e.item.detail.log?.includes("MUSE_PARITY_CHILD_OK") === true, ); expect(event).toMatchObject({ item: { status: "completed", detail: { type: "sub_agent" } } }); expect(h.events.filter((e) => e.type === "session.opened")).toHaveLength(1); const workflows = h.events.filter( (e) => e.type === "timeline.item" && e.item.type === "tool_call" && e.item.name === "workflow", ); expect(workflows.length).toBeGreaterThan(1); for (const workflow of workflows) expect(workflow).toMatchObject({ item: { detail: { type: "sub_agent" } } }); }); for (const source of ["user", "project"] as const) { test(`real ${source} skill catalog and command replay with explicit workspace trust`, async () => { const h = await harness(`phase3-${source}-skill`, {}, { trustWorkspace: source === "project" }); await h.open(); expect(await h.wait((e) => e.type === "session.commands")).toMatchObject({ commands: expect.arrayContaining([ expect.objectContaining({ name: `muse-parity-${source}` }), ]), }); await h.send({ type: "session.prompt", sessionId: "paseo-session", prompt: { clientMessageId: "real-skill", delivery: "auto", input: { type: "command", name: `muse-parity-${source}`, arguments: "QA" }, }, }); await h.wait((e) => e.type === "session.turn" && e.state === "completed"); expect(h.events).toEqual( expect.arrayContaining([ expect.objectContaining({ type: "timeline.item", item: expect.objectContaining({ type: "assistant_message", text: `MUSE_PARITY_${source.toUpperCase()}_OK`, }), }), ]), ); const recorded = await h.recorded(); expect(recorded.find((f) => f.method === "turn/start").params.input).toContainEqual({ type: "skill", selector: `muse-parity-${source}`, arguments: "QA", }); expect(recorded.find((f) => f.event === "hostStarted").args.includes("--trust-workspace")).toBe( source === "project", ); }); } test("usage identity follows the resolved config directory across credential and route changes", async () => { const identities: string[] = []; for (const [config, token, route] of [ ["/usage/config", "token-a", "route-a"], ["/usage/config", "token-b", "route-b"], ["/usage/other", "token-b", "route-b"], ]) { const h = await harness("catalog-controls", { XDG_CONFIG_HOME: config!, META_API_KEY: token!, META_BASE_URL: route!, }); await h.open(); const inputs = await h.usageSource.discover({ kind: "global" }); expect(inputs).toHaveLength(1); identities.push(inputs[0]!.key); } expect(identities[0]).toBe(identities[1]); expect(identities[2]).not.toBe(identities[0]); }); test.each(["no sessions", "unrelated environment"])("Muse discovery is empty with %s", async () => { const { Usage } = await import("../server/usage.js"); expect(await new Usage().registration().discover({ kind: "global" })).toEqual([]); }); test("Muse window identity follows the reported duration instead of assuming five hours", async () => { const h = await harness("catalog-controls", { MUSE_TEST_USAGE: JSON.stringify({ usage: { observedAtMs: 1700000000000, tier: "Pro", window: { usedPercent: 11, resetsAtMs: 1700018000000, windowDurationMins: 120 }, weekly: { usedPercent: 22, resetsAtMs: 1700604800000 }, }, }), }); await h.open(); const [account] = await h.usageSource.discover({ kind: "global" }); expect(await h.usageSource.fetch(account!.input)).toMatchObject({ status: "available", windows: [ { id: "7200s", label: "2-hour", shortLabel: "2h" }, { id: "weekly", label: "Weekly", shortLabel: "wk" }, ], }); }); test("status accepts initialization slower than the account deadline without isolating history", async () => { const h = await harness("catalog-controls", { MUSE_TEST_INITIALIZE_DELAY_MS: "3500" }); expect(await h.provider.status!({ launch: h.launch })).toEqual({ available: true }); expect(await h.recorded()).toContainEqual({ event: "hostClosed" }); }, 10000); for (const extension of ["cmd", "bat"]) { test.runIf(process.platform === "win32")( `launches the discovered Muse .${extension} for status, catalog and sessions`, async () => { const h = await harness("catalog-controls"); const shim = path.join(h.root, `muse launcher.${extension}`); await writeFile(shim, `@echo off\r\n"${process.execPath}" "${h.launch.args[0]}" %*\r\n`); h.launch.command = shim; h.launch.args = []; expect(await h.provider.status!({ launch: h.launch })).toEqual({ available: true }); await h.send({ type: "catalog", requestId: "shim-catalog" }); expect(await h.wait((event) => event.type === "catalog")).toMatchObject({ requestId: "shim-catalog", }); await h.open(); expect(await h.wait((event) => event.type === "session.opened")).toMatchObject({ sessionId: "paseo-session", }); }, 20000, ); } test("force-stops a Muse host that ignores stdin EOF", async () => { const h = await stubbornHostHarness(); try { const result = await Promise.race([ h.provider.status!({ launch: h.launch }), delay(10000).then(() => { throw new Error("Muse status did not finish closing its host"); }), ]); expect(result).toEqual({ available: true }); const hosts = (await h.recorded()).filter((row) => row.event === "stubbornHost"); expect(hosts).toHaveLength(1); expect(() => process.kill(hosts[0].pid, 0)).toThrow(); } finally { await cleanupStubbornHosts(h); } }, 15000); test("closes the whole Muse host after an uncorrelated MSP error", async () => { const h = await stubbornHostHarness({ MUSE_TEST_NULL_ERROR_ID: "1" }); try { const result = await h.provider.status!({ launch: h.launch }); expect(result).toEqual({ available: false, diagnostic: "Cannot recover request id" }); const recorded = await h.recorded(); expect(recorded.some((row) => row.event === "hostClosed")).toBe(true); const hosts = recorded.filter((row) => row.event === "stubbornHost"); expect(hosts).toHaveLength(1); expect(() => process.kill(hosts[0].pid, 0)).toThrow(); } finally { await cleanupStubbornHosts(h); } }, 15000); async function stubbornHostHarness(env: Record = {}) { const h = await harness("catalog-controls", { MUSE_TEST_STUBBORN: "1", ...env }); if (process.platform === "win32") { const shim = path.join(h.root, "stubborn muse.cmd"); await writeFile(shim, `@echo off\r\n"${process.execPath}" "${h.launch.args[0]}" %*\r\n`); h.launch.command = shim; h.launch.args = []; } return h; } async function cleanupStubbornHosts(h: Awaited>) { // Keep a failing Windows regression from leaving its deliberately stubborn child alive. for (const host of (await h.recorded()).filter((row) => row.event === "stubbornHost")) { if (process.platform === "win32") { await execCommand("taskkill.exe", ["/PID", String(host.pid), "/T", "/F"], { shell: false, }).catch(() => {}); } else { try { process.kill(host.pid, "SIGKILL"); } catch {} } } } test("Muse reports belong only to the session's account and remain available globally", async () => { const usage = new MuseUsage(); const homeEnv = { HOME: "/usage/home" }; const configEnv = { HOME: "/usage/home", XDG_CONFIG_HOME: "/usage/work" }; const accountIds = [homeEnv, configEnv].map((env, index) => { const launch = { command: "muse", args: [], env }; usage.attach(`session-${index}`, launch, async () => ({ usage: { observedAtMs: 1700000000000, tier: "Pro", window: { usedPercent: 11, resetsAtMs: 1700018000000, windowDurationMins: 120 }, weekly: { usedPercent: 22, resetsAtMs: 1700604800000 }, }, })); return `muse:${usage.remember(launch).account}`; }); const sessions = new Map([ ["claude", { provider: "claude", env: homeEnv, sessionKey: "claude" }], ["codex", { provider: "codex", env: configEnv, sessionKey: "codex" }], ["muse-home", { provider: "muse", env: homeEnv, sessionKey: "muse-home" }], ["muse-work", { provider: "muse", env: configEnv, sessionKey: "muse-work" }], ["muse-unknown", { provider: "muse", env: { HOME: "/usage/unknown" }, sessionKey: "unknown" }], ]); const registry = new UsageSourceRegistry(Date.now, 300_000, undefined, { hasAgent: (id) => sessions.has(id), usageSession: (id) => sessions.get(id) ?? null, }); registry.register(usage.registration()); expect((await registry.listReports()).map((report) => report.id)).toEqual(accountIds); expect(await registry.listReports({ agentId: "claude" })).toEqual([]); expect(await registry.listReports({ agentId: "codex" })).toEqual([]); for (const [index, agentId] of ["muse-home", "muse-work"].entries()) { const reports = await registry.listReports({ agentId }); expect(reports.map((report) => report.id)).toEqual([accountIds[index]]); expect(reports[0]!.report.status).toBe("available"); } expect(await registry.listReports({ agentId: "muse-unknown" })).toEqual([]); expect((await registry.listReports()).map((report) => report.id)).toEqual(accountIds); });