Skip to content
38 changes: 38 additions & 0 deletions apps/server/src/orchestration-v2/testkit/fixtures/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,13 @@ import { messageSteeringInput } from "./message_steering/input.ts";
import { assertPiMessageSteeringOutput } from "./message_steering/pi_output.ts";
import { MUSE_PERMISSION_DECLINED_FILE, musePermissionInput } from "./muse_permission/input.ts";
import { assertMusePermissionOutput } from "./muse_permission/output.ts";
import { museSubagentApprovalInput } from "./muse_subagent_approval/input.ts";
import { assertMuseSubagentApprovalOutput } from "./muse_subagent_approval/output.ts";
import { museSubagentInterruptInput } from "./muse_subagent_interrupt/input.ts";
import { assertMuseSubagentInterruptOutput } from "./muse_subagent_interrupt/output.ts";
import { museWorkflowInput } from "./muse_workflow/input.ts";
import { assertMuseWorkflowOutput } from "./muse_workflow/output.ts";
import { assertMuseSubagentV2Output } from "./subagent_v2/muse_output.ts";
import { assertMuseMultiTurnOutput } from "./multi_turn/muse_output.ts";
import { assertMuseTurnInterruptOutput } from "./turn_interrupt/muse_output.ts";
import { piCompactionInput } from "./pi_compaction/input.ts";
Expand Down Expand Up @@ -860,6 +865,12 @@ export const ORCHESTRATOR_REPLAY_FIXTURES: ReadonlyArray<OrchestratorReplayFixtu
modelSelection: CODEX_MODEL_SELECTION,
assertOutput: assertSubagentV2Output,
},
{
driver: ProviderDriverKind.make("muse"),
transcriptFile: new URL("./subagent_v2/muse_transcript.ndjson", import.meta.url),
modelSelection: MUSE_MODEL_SELECTION,
assertOutput: assertMuseSubagentV2Output,
},
],
},
{
Expand Down Expand Up @@ -1227,6 +1238,33 @@ export const ORCHESTRATOR_REPLAY_FIXTURES: ReadonlyArray<OrchestratorReplayFixtu
},
],
},
{
name: "muse_subagent_approval",
buildInput: museSubagentApprovalInput,
providers: [
{
driver: ProviderDriverKind.make("muse"),
transcriptFile: new URL("./muse_subagent_approval/muse_transcript.ndjson", import.meta.url),
modelSelection: MUSE_MODEL_SELECTION,
assertOutput: assertMuseSubagentApprovalOutput,
},
],
},
{
name: "muse_subagent_interrupt",
buildInput: museSubagentInterruptInput,
providers: [
{
driver: ProviderDriverKind.make("muse"),
transcriptFile: new URL(
"./muse_subagent_interrupt/muse_transcript.ndjson",
import.meta.url,
),
modelSelection: MUSE_MODEL_SELECTION,
assertOutput: assertMuseSubagentInterruptOutput,
},
],
},
{
name: "muse_workflow",
buildInput: museWorkflowInput,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { SUBAGENT_V2_APPROVAL_PROMPT, type OrchestratorFixtureInput } from "../shared.ts";

/**
* A Supervised Muse thread spawns a native subagent that runs a shell command.
* The subagent's approval is asked in its child thread and approved there.
*/
export function museSubagentApprovalInput(): OrchestratorFixtureInput {
return {
runtimeMode: "approval-required",
steps: [
{ type: "message", text: SUBAGENT_V2_APPROVAL_PROMPT },
{ type: "approve_next_runtime_request" },
],
};
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { assert } from "@effect/vitest";
import type { ProviderReplayTranscript } from "@t3tools/contracts";

import type { OrchestratorV2ScenarioResult } from "../../OrchestratorScenario.ts";
import { assertSemanticProjectionIntegrity, projectionFor } from "../shared.ts";

/**
* A native subagent's approval is asked on the parent thread, as Codex and
* OpenCode ask theirs: Muse children have no provider session of their own to
* answer from. The answer reaches Muse and the subagent finishes its work.
*/
export function assertMuseSubagentApprovalOutput(
result: OrchestratorV2ScenarioResult,
transcript: ProviderReplayTranscript,
) {
const projection = projectionFor(result, transcript.scenario);
assertSemanticProjectionIntegrity(projection);
assert.deepEqual(
projection.runs.map((run) => run.status),
["completed"],
);

const requests = projection.runtimeRequests;
assert.lengthOf(requests, 1);
assert.equal(requests[0]?.kind, "command");
assert.equal(requests[0]?.status, "resolved");
assert.equal(requests[0]?.decision, "accept");
const outbound = transcript.entries.flatMap((entry) =>
entry.type === "expect_outbound" ? [entry.frame] : [],
);
assert.isTrue(
outbound.some(
(frame) =>
typeof frame === "object" &&
frame !== null &&
Reflect.get(frame, "method") === "approval/decide",
),
"the answer reaches Muse",
);

assert.lengthOf(projection.subagents, 1);
const subagent = projection.subagents[0]!;
assert.equal(subagent.status, "completed");
assert.match(subagent.result ?? "", /^Written\.?$/u);
if (subagent.childThreadId === null) throw new Error("The subagent has no child thread");
const child = result.projections.get(subagent.childThreadId);
if (child === undefined) throw new Error("The child thread has no projection");
assert.lengthOf(child.runtimeRequests, 0);
assert.deepEqual(
child.messages.map((message) => [message.role, message.text]),
[
["user", subagent.prompt],
["assistant", subagent.result],
],
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { OrchestratorFixtureInput } from "../shared.ts";

const MUSE_SUBAGENT_INTERRUPT_PROMPT =
"Spawn one subagent whose only task is to run the shell command `sleep 30` and then reply with exactly: Done. Wait for it to finish, then reply with exactly what it said.";

/** Stop while a Muse native subagent is still running. */
export function museSubagentInterruptInput(): OrchestratorFixtureInput {
return {
steps: [
{ type: "message", text: MUSE_SUBAGENT_INTERRUPT_PROMPT },
{ type: "interrupt", targetRunIndex: 1, waitForTurnItemType: "subagent" },
],
};
}
Loading
Loading