Files
paseo/plugins/muse-provider/test/provider.test.ts
Mohamed Boudra 3e2d77723e fix(usage): scope Muse and agent logins, show every failed host login (#6156)
* fix(muse): show usage only for the agent account

Usage discovery ignored session scope and exposed all remembered accounts to unrelated agents. Resolve session accounts from their own launch environment while preserving global discovery.

* test(muse): own the usage scope regression in the plugin suite

* fix(usage): scope agent reports to their own logins

Account grouping also merged fetch inputs and cache entries across agent and host queries, hiding the agent login failure behind another harness. Preserve scoped login sets, collect every host failure with source-owned harness labels, and keep refresh results within their requesting view.

* test(usage): expect harness labels in discovered accounts
2026-10-05 18:41:54 +01:00

1733 lines
66 KiB
TypeScript

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<string, string> = {},
providerOptions?: Record<string, unknown>,
) {
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<ProviderEvent> {
for (let count = 0; count < 200; count++) {
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<ProviderInput, { type: "session.open" }>["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: "<system_instructions>\nTest instructions\n</system_instructions>" },
{ 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<string>(
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<string>((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("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<string, string> = {}) {
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<ReturnType<typeof harness>>) {
// 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);
});