Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions apps/server/src/mcp/PreviewAutomationBroker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1504,3 +1504,43 @@ it.effect("keeps the host connected when a background status read times out", ()
}),
),
);

it.effect("keeps each thread's current tab when threads share a provider session", () =>
Effect.scoped(
Effect.gen(function* () {
const broker = yield* makeBroker;
const routed: Array<RoutedRequest> = [];
const requests = requestsFrom(yield* broker.connect(makeHost(), { preferred: true }));
yield* Stream.runForEach(requests, (request) => {
routed.push(request);
return broker.respond({
clientId: "client-1",
connectionId: request.connectionId,
requestId: request.requestId,
ok: true,
result: { tabId: request.tabId ?? `tab-${request.threadId}` },
});
}).pipe(Effect.forkScoped);
yield* Effect.yieldNow;
// Codex runs every thread of an instance in one provider session.
const otherThread = {
...scope,
thread: { ...scope.thread, threadId: ThreadId.make("thread-2") },
};

yield* broker.invoke({ scope, operation: "open", input: {} });
yield* broker.invoke({ scope: otherThread, operation: "snapshot", input: {} });
yield* broker.invoke({ scope, operation: "snapshot", input: {} });
yield* broker.invoke({ scope: otherThread, operation: "snapshot", input: {} });

expect(routed.map(({ threadId, tabId }) => [threadId, tabId])).toEqual([
["thread-1", undefined],
["thread-2", undefined],
["thread-1", "tab-thread-1"],
["thread-2", "tab-thread-2"],
]);
// Each thread's agent owns its own tabs and tab budget.
expect(routed[0]!.agentSessionId).not.toBe(routed[1]!.agentSessionId);
}),
),
);
7 changes: 6 additions & 1 deletion apps/server/src/mcp/PreviewAutomationBroker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,13 @@ const selectorDiagnosticsFromInput = (
return {};
};

/**
* One agent: a provider session on one thread. Codex and OpenCode run every
* thread of a provider instance in one session, so the session alone would
* share one current tab, tab budget, and tab ownership across chats.
*/
const hostAssignmentKey = (scope: McpInvocationContext.McpThreadInvocationScope): string =>
`${scope.environmentId}\u0000${scope.thread.providerSessionId}`;
`${scope.environmentId}\u0000${scope.thread.providerSessionId}\u0000${scope.thread.threadId}`;

const isPreviewTabId = Schema.is(PreviewTabId);
const decodeControlReason = Schema.decodeUnknownOption(PreviewAutomationControlReason);
Expand Down
Loading