fix(muse-provider): negotiate session.subsession and gate child projection

This commit is contained in:
George
2026-10-28 11:38:21 +00:00
committed by paseo-bot[bot]
parent 41582eb5fd
commit 6a4c05ce82
4 changed files with 29 additions and 2 deletions
+5
View File
@@ -21,6 +21,7 @@ export class Children {
private readonly parentId: string,
private readonly cwd: string,
private readonly emit: (event: ProviderEvent) => void,
private readonly subsessions: boolean,
) {}
async update(item: WireItem): Promise<void> {
@@ -34,6 +35,10 @@ export class Children {
)
)
return;
// Emitting a child session.opened with parentSessionId is only valid when the
// parent session negotiated the session.subsession capability; the daemon
// fails the provider connection otherwise.
if (!this.subsessions) return;
const id = item.childSessionId;
if (!this.children.has(id)) {
const { session } = await this.host.request(
+1
View File
@@ -27,6 +27,7 @@ const capabilities = [
"session.persistence",
"permission",
"session.list",
"session.subsession",
] as const;
export function createMuseProvider(usage: Usage): ProviderRegistration {
+7 -1
View File
@@ -142,7 +142,13 @@ export class Session {
const nativeId = response.session.sessionId;
this.liveState = {
nativeId,
children: new Children(this.host, this.options.id, this.config.cwd, this.options.emit),
children: new Children(
this.host,
this.options.id,
this.config.cwd,
this.options.emit,
this.options.capabilities.includes("session.subsession"),
),
timeline: new Timeline(
this.host,
nativeId,
+16 -1
View File
@@ -45,6 +45,7 @@ async function harness(
scenario = "text-reasoning",
env: Record<string, string> = {},
providerOptions?: Record<string, unknown>,
connectCapabilities: readonly string[] = PROVIDER_CAPABILITIES,
) {
const root = await mkdtemp(path.join(os.tmpdir(), "muse-provider-test-"));
roots.push(root);
@@ -62,7 +63,7 @@ async function harness(
const connection = await provider.connect({
launch,
versions: [1],
capabilities: PROVIDER_CAPABILITIES,
capabilities: connectCapabilities,
});
connections.push(connection);
const events: ProviderEvent[] = [];
@@ -1192,6 +1193,20 @@ for (const variant of ["tool", "dedicated", "workflow"]) {
).toHaveLength(1);
});
}
test("subagent children are not projected when session.subsession is not negotiated", async () => {
const h = await harness(
"subagent",
{},
undefined,
PROVIDER_CAPABILITIES.filter((capability) => capability !== "session.subsession"),
);
await h.open();
await h.prompt();
await h.wait((e) => e.type === "session.turn" && e.state === "completed");
expect(h.events.filter((e) => e.type === "session.opened" && e.parentSessionId)).toHaveLength(0);
expect((await h.recorded()).filter((f) => f.method === "session/read")).toEqual([]);
});
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();