docs(agent-mode): note the compose-lane model; align picker test with swap path

Add a one-line architecture note (AGENTS.md) on the compose lane, and update the
cross-backend picker test to assert the new
replaceSessionInPlace(inheritLane, seedSelection) path instead of the old
createSession + closeSession.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Zero Liu 2026-07-05 13:02:49 +08:00
parent 79c02f0ed5
commit ff4c1271a8
No known key found for this signature in database
2 changed files with 12 additions and 7 deletions

View file

@ -48,5 +48,6 @@ Read the matching guide when your task touches that area — they aren't loaded
- Local model support via Ollama / LM Studio.
- Rate limiting is implemented for all API calls.
- Message & chat architecture (Repository → Manager → UIState → UI; single `MessageRepository`; per-project isolation) → [`designdocs/MESSAGE_ARCHITECTURE.md`](./designdocs/MESSAGE_ARCHITECTURE.md).
- A tab's compose lane (`AgentSession.composeLaneId`) is stable across in-place session swaps; the Agent Mode composer keys by lane, while the transcript/token-meter/persistence key by session.
- Tech debt and known issues → [`designdocs/todo/TECHDEBT.md`](./designdocs/todo/TECHDEBT.md). Current session plan → [`TODO.md`](./TODO.md).
- Available Tailwind tokens/classes → [`tailwind.config.js`](./tailwind.config.js).

View file

@ -152,6 +152,7 @@ function makeManager(opts: {
persistDefaultSelection?: jest.Mock;
createSession?: jest.Mock;
closeSession?: jest.Mock;
replaceSessionInPlace?: jest.Mock;
}): AgentSessionManager {
return {
getCachedBackendState: (id: string) => opts.cachedStateById?.[id] ?? null,
@ -162,6 +163,7 @@ function makeManager(opts: {
persistDefaultSelection: opts.persistDefaultSelection ?? jest.fn().mockResolvedValue(undefined),
createSession: opts.createSession ?? jest.fn().mockResolvedValue(undefined),
closeSession: opts.closeSession ?? jest.fn().mockResolvedValue(undefined),
replaceSessionInPlace: opts.replaceSessionInPlace ?? jest.fn().mockResolvedValue(undefined),
} as unknown as AgentSessionManager;
}
@ -612,14 +614,14 @@ describe("buildModelOnChange", () => {
expect(applySelection).not.toHaveBeenCalled();
});
it("cross-backend pick seeds the new session transiently without persisting the default", async () => {
it("cross-backend pick swaps the tab in place, seeding transiently without persisting the default", async () => {
const persistDefaultSelection = jest.fn().mockResolvedValue(undefined);
const createSession = jest.fn().mockResolvedValue(undefined);
const replaceSessionInPlace = jest.fn().mockResolvedValue(undefined);
const setDefaultBackend = jest.fn();
const closeSession = jest.fn().mockResolvedValue(undefined);
const manager = makeManager({
persistDefaultSelection,
createSession,
replaceSessionInPlace,
setDefaultBackend,
closeSession,
// The legacy single-arg path seeds effort from the target's persisted
@ -632,12 +634,14 @@ describe("buildModelOnChange", () => {
// Allow the IIFE to run.
await new Promise((r) => window.setTimeout(r, 0));
expect(persistDefaultSelection).not.toHaveBeenCalled();
expect(createSession).toHaveBeenCalledWith("claude", undefined, {
baseModelId: "opus",
effort: "low",
// With an existing tab the pick swaps in place, inheriting the old session's
// composer lane and seeding the model — it does NOT mint+close a new tab.
expect(replaceSessionInPlace).toHaveBeenCalledWith("tab-1", "claude", {
inheritLane: true,
seedSelection: { baseModelId: "opus", effort: "low" },
});
expect(setDefaultBackend).toHaveBeenCalledWith("claude");
expect(closeSession).toHaveBeenCalledWith("tab-1");
expect(closeSession).not.toHaveBeenCalled();
});
it("ignores entries with no _backendId or unresolvable baseModelId", () => {