diff --git a/docs/design/README.md b/docs/design/README.md index 7fe450f4..8f1318fa 100644 --- a/docs/design/README.md +++ b/docs/design/README.md @@ -15,6 +15,7 @@ These records predate [`Decision 0001`](../decisions/0001-documentation-and-evid - [`OPENPI_WEB_ARCHITECTURE.md`](OPENPI_WEB_ARCHITECTURE.md) — draft architecture, protocol boundaries, delivery phases, and visual direction for the local Web workbench - [`OPENPI_WEB_REACT_MVP.md`](OPENPI_WEB_REACT_MVP.md) — draft local validation design for a behavior-compatible React, Astryx, and Tailwind browser migration - [`OPENPI_WEB_DEV_PORT_CONFLICTS.md`](OPENPI_WEB_DEV_PORT_CONFLICTS.md) — draft local design for development-port fallback, strict explicit ports, startup diagnostics, and TUI `/web` error projection +- [`WEB_SESSION_CREATION_TARGET.md`](WEB_SESSION_CREATION_TARGET.md) — stable receipt identity and fail-closed target binding for a newly created Web Session's model selection and first prompt - [`WEB_SLASH_COMMAND_DISCOVERY.md`](WEB_SLASH_COMMAND_DISCOVERY.md) — Pi-owned command discovery, bounded Web projection, availability policy, and Composer completion interaction - [`COMPLETION_INBOX.md`](COMPLETION_INBOX.md) — shared owner, epoch, consumption, retry, and receipt contract for background completions diff --git a/docs/design/WEB_SESSION_CREATION_TARGET.md b/docs/design/WEB_SESSION_CREATION_TARGET.md new file mode 100644 index 00000000..8cb946b3 --- /dev/null +++ b/docs/design/WEB_SESSION_CREATION_TARGET.md @@ -0,0 +1,86 @@ +# Web Session Creation Target Binding + +- Status: validated +- Created: 2026-09-08 +- Verified: 2026-09-08 +- Source boundary: the implementation in this record's commit, based on + `upstream/main` at `0d17f4577fe31315fe6c95370d251bdb4e2413cf` +- Related Issue: [#466](https://github.com/openpi-dev/openpi/issues/466) +- Related PR: [#490](https://github.com/openpi-dev/openpi/pull/490) +- Supersedes: none + +## Problem + +Creating a Web Session and sending its first prompt spans an HTTP creation +receipt, snapshot refreshes, optional model selection, and prompt admission. +Another browser tab can activate a different Session between those operations. +If the browser discards the creation receipt and reads the target back from the +latest snapshot, the original prompt can be sent to the other tab's active +Session. + +The Host and runtime already reject prompts whose `sessionId` is not active. +That guard cannot recover the user's intent after the browser has replaced the +intended target with a different but currently valid Session id. + +## Decision + +The creation receipt carries three distinct facts: + +- `commandId` correlates the creation request and its events; +- `sessionId` is the required stable target identity; +- `sessionPath` is optional because a new Pi Session may not have a persisted + file before its first message. + +The Web store retains those facts as the target of the creation operation. A +snapshot refresh may confirm the target but cannot replace it. Before model +selection, after model selection, and before prompt admission, the store checks +that both `currentSessionId` and the selected Session still match the receipt's +`sessionId`. When the receipt included a path, the selected path must also +match. + +If another tab changes the active Session, the operation stops without calling +model selection or prompt admission. The Composer keeps its input because +`sendPrompt` returns `false`; the store reports that the active Session changed. +The Host's existing `SESSION_CONFLICT` validation and prompt `commandId` +idempotency remain the final runtime boundaries. + +This is a browser admission context, not a second Session state machine. Pi's +runtime and `SessionManager` remain authoritative for Session activation and +persistence. + +## Evidence + +Validated tests cover: + +- a second tab becoming active between the creation receipt and snapshot; +- creation and correlated SSE events before a Session has a persisted path; +- draft model selection refusing to target the externally activated Session; +- the runtime and Host returning the same stable Session identity; +- existing Session selection, prompt retry, and model-selection races. + +Repository validation on 2026-09-08: + +- `bun run check` passed; +- `bun run test` passed with 1,465 Node tests passed, 1 platform-specific test + skipped, and 134 Web tests passed; +- `bun run test:web:e2e` passed 12/12. The new browser case starts the current + checkout's standalone Host and Pi runtime, delays the created Session's HTTP + receipt, switches the runtime through an independent request, suppresses the + SSE transition, and verifies that no prompt is retargeted. No model call was + made; +- the local shell had no separately installed `pi` executable, so `pi list` + provenance for an installed package was unavailable. The browser test runs + `bin/openpi.js` directly from the named checkout instead. + +## Ablation + +An initial implementation recorded both the expected receipt identity and +separate Session/path identities observed from creation events. Removing the +observed-identity state left the merged Web store suite at 60/60: `commandId` +already correlates the event stream, while the receipt's `sessionId` is the +only target authority needed after HTTP completion. The redundant state was +removed. + +Removing receipt-bound `sessionId` validation restores the reported failure: +the snapshot can supply another tab's active Session as the first prompt target. +That validation is therefore required. diff --git a/tests/web/artifact-evidence.e2e.ts b/tests/web/artifact-evidence.e2e.ts index 4226a370..e5617e1d 100644 --- a/tests/web/artifact-evidence.e2e.ts +++ b/tests/web/artifact-evidence.e2e.ts @@ -182,7 +182,10 @@ test("real file evidence, authenticated downloads, edits, refresh and failure st setModel: async () => { throw new Error("unused"); }, - newSession: async () => ({ cancelled: true }), + newSession: async () => ({ + cancelled: true, + sessionId: runtime.sessionManager.getSessionId(), + }), switchSession: async () => ({ cancelled: true }), cancelTurn: async (options) => ({ ...options, state: "stale-turn" }), subscribe: (listener) => { diff --git a/tests/web/artifact-host.test.ts b/tests/web/artifact-host.test.ts index 170f1ac2..3bc2d1a8 100644 --- a/tests/web/artifact-host.test.ts +++ b/tests/web/artifact-host.test.ts @@ -25,7 +25,10 @@ test("artifact HTTP access authenticates, binds a Session, serves exact revision dispose: async () => undefined, sendPrompt: async () => ({ pendingFollowUps: 0 }), cancelTurn: async (options) => ({ ...options, state: "stale-turn" }), - newSession: async () => ({ cancelled: true }), + newSession: async () => ({ + cancelled: true, + sessionId: runtime.sessionManager.getSessionId(), + }), switchSession: async () => ({ cancelled: true }), setModel: async () => { throw new Error("unused"); diff --git a/tests/web/openpi-web.e2e.ts b/tests/web/openpi-web.e2e.ts index 57995c5f..ed13b1c1 100644 --- a/tests/web/openpi-web.e2e.ts +++ b/tests/web/openpi-web.e2e.ts @@ -1457,6 +1457,93 @@ test("workspace selection survives refresh and creates the exact native Session } }); +test("a delayed creation receipt never retargets the first prompt to another tab's Session", async ({ + page, +}) => { + const workspaceA = await mkdtemp(join(tmpdir(), "openpi-issue-466-a-")); + const workspaceB = await mkdtemp(join(tmpdir(), "openpi-issue-466-b-")); + const headers = { + Authorization: `Bearer ${token}`, + Origin: "http://127.0.0.1:57109", + }; + const promptRequests: unknown[] = []; + let createdSessionId: string | undefined; + let externalSessionId: string | undefined; + try { + const importedA = await page.request.post("/api/workspaces", { + headers, + data: { path: workspaceA }, + }); + const importedB = await page.request.post("/api/workspaces", { + headers, + data: { path: workspaceB }, + }); + expect(importedA.status()).toBe(201); + expect(importedB.status()).toBe(201); + const { path: canonicalA } = await importedA.json(); + const { path: canonicalB } = await importedB.json(); + const workspaceNameA = canonicalA.split("/").at(-1); + + await page.route("**/events?**", (route) => + route.fulfill({ + contentType: "text/event-stream", + body: ": heartbeat\n\n", + }), + ); + await page.route("**/api/prompt", async (route) => { + promptRequests.push(route.request().postDataJSON()); + await route.fulfill({ + status: 202, + json: { + id: route.request().postDataJSON().commandId, + accepted: true, + }, + }); + }); + await page.route("**/api/sessions", async (route) => { + const createdResponse = await route.fetch(); + const created = await createdResponse.json(); + createdSessionId = created.sessionId; + const external = await page.request.post("/api/sessions", { + headers, + data: { + workspacePath: canonicalB, + commandId: "external-tab-switch", + }, + }); + expect(external.status()).toBe(201); + externalSessionId = (await external.json()).sessionId; + await route.fulfill({ response: createdResponse, json: created }); + }); + + await openWorkbench(page); + const picker = page.locator(".workspace-picker"); + await picker.click(); + await page + .getByRole("menuitem", { name: workspaceNameA, exact: true }) + .click(); + const composer = page.getByRole("textbox", { name: "描述任务" }); + await composer.fill("Only edit repository A"); + await page.getByRole("button", { name: "发送", exact: true }).click(); + + await expect(page.locator(".notice")).toContainText("no longer active"); + await expect(composer).toHaveValue("Only edit repository A"); + expect(promptRequests).toEqual([]); + expect(createdSessionId).toEqual(expect.any(String)); + expect(externalSessionId).toEqual(expect.any(String)); + expect(createdSessionId).not.toBe(externalSessionId); + const snapshot = await page.request.get("/api/snapshot", { headers }); + expect((await snapshot.json()).currentSessionId).toBe(externalSessionId); + } finally { + await page.close(); + await Promise.all( + [workspaceA, workspaceB].map((path) => + rm(path, { recursive: true, force: true }), + ), + ); + } +}); + test.describe("thinking picker", () => { test("disables thinking when the runtime reports it unsupported", async ({ page, diff --git a/tests/web/pi-adapter.test.ts b/tests/web/pi-adapter.test.ts index a8c30e3c..83ed87d4 100644 --- a/tests/web/pi-adapter.test.ts +++ b/tests/web/pi-adapter.test.ts @@ -35,7 +35,10 @@ function runtimeFor( getActiveTurn: () => undefined, cancelTurn: async (options) => ({ ...options, state: "stale-turn" }), sendPrompt: async () => ({ pendingFollowUps: 0 }), - newSession: async () => ({ cancelled: false }), + newSession: async () => ({ + cancelled: false, + sessionId: sessionManager.getSessionId(), + }), switchSession: async () => ({ cancelled: false }), listModels: () => [], searchModels: (query, limit) => projectWebModelSearch([], query, limit), diff --git a/tests/web/pi-runtime.test.ts b/tests/web/pi-runtime.test.ts index 27258c80..32f33898 100644 --- a/tests/web/pi-runtime.test.ts +++ b/tests/web/pi-runtime.test.ts @@ -1215,7 +1215,7 @@ test("dispose waits for pending candidate creation and cleans it before releasin } }); -test("new session projects its command id and activated session path", async () => { +test("new session projects its command id and stable activated identity", async () => { const active = lifecycleRuntime(lifecycleSession("session-a", false)); const candidateSession = lifecycleSession("session-b", false, 0); Object.assign(candidateSession.sessionManager, { @@ -1250,12 +1250,31 @@ test("new session projects its command id and activated session path", async () assert.deepEqual(result, { cancelled: false, commandId: "create-command", + sessionId: "session-b", sessionPath: "/tmp/session-b.jsonl", }); + const eventCount = events.length; + const replay = await harness.newSession(process.cwd(), { + commandId: "create-command", + }); + assert.deepEqual(replay, { ...result, replayed: true }); + assert.equal( + events.length, + eventCount, + "receipt replay must not activate another Session", + ); + await assert.rejects( + harness.newSession("/different-workspace", { + commandId: "create-command", + }), + /another workspace/, + ); + assert.deepEqual(events.at(-1), { type: "session_switched", detail: { commandId: "create-command", + sessionId: "session-b", sessionPath: "/tmp/session-b.jsonl", }, }); diff --git a/tests/web/web-host.test.ts b/tests/web/web-host.test.ts index ef5566e4..6cccdb40 100644 --- a/tests/web/web-host.test.ts +++ b/tests/web/web-host.test.ts @@ -127,6 +127,7 @@ test("serves workspaces through a runtime isolated from terminal sessions", asyn for (const listener of listeners) listener({ type: "session_start" }); return { cancelled: false, + sessionId: sessionManager.getSessionId(), ...(options?.commandId ? { commandId: options.commandId } : {}), }; }, @@ -859,7 +860,11 @@ test("serves workspaces through a runtime isolated from terminal sessions", asyn }), }); assert.equal(importedSession.status, 201); - assert.equal((await importedSession.json()).commandId, "create-imported"); + assert.deepEqual(await importedSession.json(), { + cancelled: false, + commandId: "create-imported", + sessionId: sessionManager.getSessionId(), + }); assert.equal(runtimeCwd, importedWorkspace.path); const newSession = await fetch(`${launched.origin}/api/sessions`, { @@ -868,10 +873,46 @@ test("serves workspaces through a runtime isolated from terminal sessions", asyn body: JSON.stringify({ workspacePath: cwd, commandId: "create-current" }), }); assert.equal(newSession.status, 201); - assert.equal((await newSession.json()).commandId, "create-current"); + assert.deepEqual(await newSession.json(), { + cancelled: false, + commandId: "create-current", + sessionId: sessionManager.getSessionId(), + }); assert.equal(runtimeCwd, cwd); assert.equal(newSessions, 2); assert.deepEqual(creationCommandIds, ["create-imported", "create-current"]); + const beforeReplay = (await ( + await fetch(`${launched.origin}/api/snapshot`, { headers: authorized }) + ).json()) as { cursor: number }; + const originalNewSession = runtime.newSession; + runtime.newSession = async (_workspacePath, options) => ({ + cancelled: false, + replayed: true, + commandId: options?.commandId, + sessionId: "older-session", + }); + try { + const replay = await fetch(`${launched.origin}/api/sessions`, { + method: "POST", + headers: authorized, + body: JSON.stringify({ + workspacePath: importedWorkspace.path, + commandId: "create-imported", + }), + }); + assert.equal(replay.status, 201); + const afterReplay = (await ( + await fetch(`${launched.origin}/api/snapshot`, { headers: authorized }) + ).json()) as { cursor: number }; + assert.equal( + afterReplay.cursor, + beforeReplay.cursor, + "a replay is not a new Session transition", + ); + assert.equal(runtimeCwd, cwd); + } finally { + runtime.newSession = originalNewSession; + } const removeActive = await fetch( `${launched.origin}/api/workspaces?path=${encodeURIComponent(cwd)}`, @@ -992,7 +1033,10 @@ test("serves terminal Sessions through a read-only bounded endpoint", async () = getActiveTurn: () => undefined, cancelTurn: async (options) => ({ ...options, state: "stale-turn" }), sendPrompt: async () => ({ pendingFollowUps: 0 }), - newSession: async () => ({ cancelled: false }), + newSession: async () => ({ + cancelled: false, + sessionId: runtime.sessionManager.getSessionId(), + }), switchSession: async () => ({ cancelled: false }), listModels: () => [], searchModels: (query, limit) => projectWebModelSearch([], query, limit), @@ -1126,7 +1170,10 @@ test("an unbound Host exposes no bootstrap Session and rejects prompt bypasses", prompts++; return { pendingFollowUps: 0 }; }, - newSession: async () => ({ cancelled: false }), + newSession: async () => ({ + cancelled: false, + sessionId: sessionManager.getSessionId(), + }), switchSession: async () => ({ cancelled: false }), listModels: () => [], searchModels: (query, limit) => projectWebModelSearch([], query, limit), @@ -1227,7 +1274,10 @@ test("returns accepted only after Pi admits the prompt", async () => { await promptAdmitted; return { pendingFollowUps: 0 }; }, - newSession: async () => ({ cancelled: false }), + newSession: async () => ({ + cancelled: false, + sessionId: sessionManager.getSessionId(), + }), switchSession: async () => ({ cancelled: false }), listModels: () => [], searchModels: (query, limit) => projectWebModelSearch([], query, limit), @@ -1715,7 +1765,10 @@ function testRuntime( getActiveTurn: () => undefined, cancelTurn: async (options) => ({ ...options, state: "stale-turn" }), sendPrompt, - newSession: async () => ({ cancelled: false }), + newSession: async () => ({ + cancelled: false, + sessionId: sessionManager.getSessionId(), + }), switchSession: async () => ({ cancelled: false }), listModels: () => [], searchModels: (query, limit) => projectWebModelSearch([], query, limit), diff --git a/tests/web/web-store.spec.ts b/tests/web/web-store.spec.ts index ec3f912b..e4ee7e3a 100644 --- a/tests/web/web-store.spec.ts +++ b/tests/web/web-store.spec.ts @@ -11,6 +11,7 @@ import type { } from "../../web/protocol/types.ts"; import { type CommandReceipt, + type SessionCreationResult, type SessionMutationResult, WebApiError, WebClient, @@ -148,9 +149,9 @@ class FakeClient extends WebClient { workspaceResult: Promise = Promise.resolve({ cancelled: true, }); - creationResult: Promise = Promise.resolve({ - sessionPath: "/tmp/ws/session.jsonl", - }); + creationResult: + | ((commandId: string) => Promise) + | null = null; selectionResults: Array> = []; modelResult: Promise = Promise.resolve({ provider: "test", @@ -204,7 +205,14 @@ class FakeClient extends WebClient { override createSession(workspacePath: string, commandId: string) { this.creations.push({ commandId, workspacePath }); - return this.creationResult; + return ( + this.creationResult?.(commandId) ?? + Promise.resolve({ + cancelled: false, + commandId, + sessionId: "session-1", + }) + ); } override selectSession(path: string) { @@ -771,6 +779,29 @@ describe("OpenPI Web store", () => { store.getState().actions.stop(); }); + it("preserves a business notice when the event stream reconnects", async () => { + const client = new FakeClient(); + client.snapshots.push(Promise.resolve(snapshot())); + let connected: (() => void) | undefined; + const consumeEvents = vi.fn((options: EventStreamOptions) => { + connected = options.onConnected; + return new Promise((resolve) => { + options.signal.addEventListener("abort", () => resolve(), { + once: true, + }); + }); + }); + const store = createWebStore(client, { consumeEvents }); + await store.getState().actions.refreshSnapshot(); + store.getState().actions.start(); + await vi.waitFor(() => expect(connected).toBeDefined()); + store.setState({ notice: "The created Session is no longer active." }); + connected?.(); + expect(store.getState().connection).toBe("connected"); + expect(store.getState().notice).toContain("no longer active"); + store.getState().actions.stop(); + }); + it("projects prompt admission optimistically and settles on the receipt", async () => { const client = new FakeClient(); client.snapshots.push(Promise.resolve(snapshot())); @@ -1123,8 +1154,8 @@ describe("OpenPI Web store", () => { Promise.resolve(snapshot()), Promise.resolve(activeSnapshot("session-2", "/tmp/ws/b.jsonl")), ); - const creation = deferred(); - client.creationResult = creation.promise; + const creation = deferred(); + client.creationResult = () => creation.promise; client.selectionResults.push(Promise.resolve({})); const store = createWebStore(client); await store.getState().actions.refreshSnapshot(); @@ -1132,7 +1163,11 @@ describe("OpenPI Web store", () => { const creating = store.getState().actions.createSession("/tmp/ws"); await vi.waitFor(() => expect(client.creations).toHaveLength(1)); const selecting = store.getState().actions.selectSession("/tmp/ws/b.jsonl"); - creation.resolve({}); + creation.resolve({ + cancelled: false, + commandId: client.creations[0]!.commandId, + sessionId: "session-1", + }); await Promise.all([creating, selecting]); expect(client.selections).toEqual(["/tmp/ws/b.jsonl"]); @@ -1149,8 +1184,8 @@ describe("OpenPI Web store", () => { activeSnapshot("session-2", "/tmp/ws/created.jsonl", { cursor: 5 }), ), ); - const creation = deferred(); - client.creationResult = creation.promise; + const creation = deferred(); + client.creationResult = () => creation.promise; const stream = eventStreamHarness(); const store = createWebStore(client, { consumeEvents: stream.consumeEvents, @@ -1168,7 +1203,12 @@ describe("OpenPI Web store", () => { sessionPath: "/tmp/ws/created.jsonl", }), ); - creation.resolve({ sessionPath: "/tmp/ws/created.jsonl" }); + creation.resolve({ + cancelled: false, + commandId, + sessionId: "session-2", + sessionPath: "/tmp/ws/created.jsonl", + }); await creating; const prompt = deferred(); @@ -1190,6 +1230,48 @@ describe("OpenPI Web store", () => { store.getState().actions.stop(); }); + it("correlates creation events by Session identity before persistence", async () => { + const client = new FakeClient(); + client.snapshots.push( + Promise.resolve(unboundSnapshot()), + Promise.resolve(activeSnapshot("session-new", "current:session-new")), + ); + const creation = deferred(); + client.creationResult = () => creation.promise; + const stream = eventStreamHarness(); + const store = createWebStore(client, { + consumeEvents: stream.consumeEvents, + }); + await store.getState().actions.refreshSnapshot(); + store.getState().actions.start(); + + const creating = store.getState().actions.createSession("/tmp/ws"); + await vi.waitFor(() => expect(client.creations).toHaveLength(1)); + const commandId = client.creations[0]!.commandId; + stream.emit( + runtimeEvent(5, "session_switched", { + commandId, + sessionId: "session-new", + }), + ); + stream.emit( + runtimeEvent(6, "session_created", { + commandId, + sessionId: "session-new", + }), + ); + creation.resolve({ + cancelled: false, + commandId, + sessionId: "session-new", + }); + + expect(await creating).toMatchObject({ sessionId: "session-new" }); + expect(store.getState().selectedPath).toBe("current:session-new"); + expect(store.getState().notice).toBeNull(); + store.getState().actions.stop(); + }); + it("lets an external activation supersede queued Session creation", async () => { const client = new FakeClient(); client.snapshots.push( @@ -1268,7 +1350,10 @@ describe("OpenPI Web store", () => { ), ); client.workspaceResult = Promise.resolve({ path: workspace }); - client.creationResult = Promise.resolve({ + client.creationResult = async (commandId) => ({ + cancelled: false, + commandId, + sessionId: "session-1", sessionPath: `${workspace}/session.jsonl`, }); const store = createWebStore(client); @@ -1311,7 +1396,10 @@ describe("OpenPI Web store", () => { it("creates a Session in the chosen workspace before sending from an old empty Session", async () => { const client = new FakeClient(); - client.creationResult = Promise.resolve({ + client.creationResult = async (commandId) => ({ + cancelled: false, + commandId, + sessionId: "session-b", sessionPath: "/tmp/repo-b/session.jsonl", }); client.snapshots.push( @@ -1336,6 +1424,81 @@ describe("OpenPI Web store", () => { store.getState().actions.stop(); }); + it("never retargets the first prompt to a Session activated by another tab", async () => { + const client = new FakeClient(); + const workspaceA = "/tmp/repo-a"; + const workspaceB = "/tmp/repo-b"; + client.snapshots.push( + Promise.resolve( + unboundSnapshot([ + { path: workspaceA, name: "A", current: false }, + { path: workspaceB, name: "B", current: false }, + ]), + ), + Promise.resolve( + activeSnapshot("session-b", `${workspaceB}/session.jsonl`, { + workspace: workspaceB, + }), + ), + ); + const store = createWebStore(client); + await store.getState().actions.refreshSnapshot(); + store.getState().actions.setWorkspace(workspaceA); + client.creationResult = async (commandId) => ({ + cancelled: false, + commandId, + sessionId: "session-a", + }); + + expect( + await store + .getState() + .actions.sendPrompt("Edit repository A configuration"), + ).toBe(false); + + expect(client.prompts).toEqual([]); + expect(store.getState().snapshot?.currentSessionId).toBe("session-b"); + expect(store.getState().notice).toContain("no longer active"); + expect(store.getState().workspaceDraft).toBe(true); + const previousCommandId = client.creations[0].commandId; + client.snapshots.push( + Promise.resolve( + activeSnapshot("session-a", `${workspaceA}/session.jsonl`, { + workspace: workspaceA, + }), + ), + ); + expect(await store.getState().actions.sendPrompt("retry in A")).toBe(true); + expect(client.creations[1].commandId).not.toBe(previousCommandId); + expect(client.prompts).toEqual([ + { sessionId: "session-a", content: "retry in A" }, + ]); + }); + + it("binds a first prompt to a new Session before it has a persisted path", async () => { + const client = new FakeClient(); + const workspace = "/tmp/ws"; + client.snapshots.push( + Promise.resolve( + unboundSnapshot([{ path: workspace, name: "WS", current: false }]), + ), + Promise.resolve(activeSnapshot("session-new", "current:session-new")), + ); + client.creationResult = async (commandId) => ({ + cancelled: false, + commandId, + sessionId: "session-new", + }); + const store = createWebStore(client); + await store.getState().actions.refreshSnapshot(); + store.getState().actions.setWorkspace(workspace); + + expect(await store.getState().actions.sendPrompt("first task")).toBe(true); + expect(client.prompts).toEqual([ + { sessionId: "session-new", content: "first task" }, + ]); + }); + it("keeps a running agent active when a handled follow-up settles", async () => { const client = new FakeClient(); client.snapshots.push(Promise.resolve(snapshot())); @@ -1867,6 +2030,30 @@ describe("draft model selection", () => { store.getState().actions.stop(); }); + it("does not apply a draft model to a Session activated by another tab", async () => { + const { client, store } = draftHarness(); + await store.getState().actions.selectModel("test/model"); + store.getState().actions.setWorkspace("/tmp/repo-a"); + client.snapshots.push( + Promise.resolve( + activeSnapshot("session-b", "/tmp/repo-b/session.jsonl", { + workspace: "/tmp/repo-b", + }), + ), + ); + client.creationResult = async (commandId) => ({ + cancelled: false, + commandId, + sessionId: "session-a", + }); + + expect(await store.getState().actions.sendPrompt("hello A")).toBe(false); + expect(client.modelSelections).toEqual([]); + expect(client.prompts).toEqual([]); + expect(store.getState().draftModel?.id).toBe("model"); + expect(store.getState().notice).toContain("no longer active"); + }); + it("blocks fallback on unavailable model and retries using the already-created Session", async () => { const { client, store } = draftHarness(); await store.getState().actions.selectModel("test/model"); @@ -1921,8 +2108,8 @@ describe("draft model selection", () => { const { client, store } = draftHarness(); await store.getState().actions.selectModel("test/model"); store.setState({ selectedWorkspace: "/tmp/ws" }); - const creation = deferred(); - client.creationResult = creation.promise; + const creation = deferred(); + client.creationResult = () => creation.promise; const sending = store.getState().actions.sendPrompt("hello"); await vi.waitFor(() => expect(client.creations).toHaveLength(1)); client.snapshots.push( @@ -1931,7 +2118,11 @@ describe("draft model selection", () => { const selecting = store .getState() .actions.selectSession("/tmp/ws/other.jsonl"); - creation.resolve({}); + creation.resolve({ + cancelled: false, + commandId: client.creations[0]!.commandId, + sessionId: "session-1", + }); expect(await sending).toBe(false); await selecting; expect(client.prompts).toHaveLength(0); @@ -1964,8 +2155,8 @@ describe("workspace selection authority", () => { expect(await refreshing).toBe(false); expect(store.getState().selectedWorkspace).toBe(workspace); - const creation = deferred(); - client.creationResult = creation.promise; + const creation = deferred(); + client.creationResult = () => creation.promise; client.snapshots.push( Promise.resolve(activeSnapshot("b", sessionPath, { workspace })), ); @@ -1974,7 +2165,12 @@ describe("workspace selection authority", () => { expect(store.getState().sessionSwitching).toBe(true); expect(await store.getState().actions.sendPrompt("duplicate")).toBe(false); expect(client.prompts).toEqual([]); - creation.resolve({ sessionPath }); + creation.resolve({ + cancelled: false, + commandId: client.creations[0]!.commandId, + sessionId: "b", + sessionPath, + }); expect(await sending).toBe(true); expect(client.prompts).toEqual([{ sessionId: "b", content: "B only" }]); store.getState().actions.stop(); @@ -1992,10 +2188,38 @@ describe("workspace selection authority", () => { expect(client.prompts).toEqual([]); }); + it("reuses the creation command after activation succeeded but its response was lost", async () => { + const { client, store } = await harness(); + store.getState().actions.setWorkspace(workspace); + const activated = activeSnapshot("b", sessionPath, { workspace }); + client.creationResult = async () => { + throw new Error("response lost"); + }; + client.snapshots.push(Promise.resolve(activated)); + expect(await store.getState().actions.sendPrompt("B only")).toBe(false); + const originalCommand = client.creations[0]!.commandId; + client.creationResult = async (commandId) => ({ + cancelled: false, + commandId, + sessionId: "b", + sessionPath, + }); + client.snapshots.push(Promise.resolve(activated)); + expect(await store.getState().actions.sendPrompt("B only")).toBe(true); + expect(client.creations.map((call) => call.commandId)).toEqual([ + originalCommand, + originalCommand, + ]); + expect(client.prompts).toEqual([{ sessionId: "b", content: "B only" }]); + store.getState().actions.stop(); + }); + it("retains B on creation failure and retries without falling back to A", async () => { const { client, initial, store } = await harness(); store.getState().actions.setWorkspace(workspace); - client.creationResult = Promise.reject(new Error("creation failed")); + client.creationResult = async () => { + throw new Error("creation failed"); + }; client.snapshots.push(Promise.resolve(initial)); expect(await store.getState().actions.sendPrompt("B only")).toBe(false); expect(store.getState().notice).toBe("creation failed"); @@ -2003,7 +2227,12 @@ describe("workspace selection authority", () => { expect(store.getState().workspaceDraft).toBe(true); expect(client.prompts).toEqual([]); - client.creationResult = Promise.resolve({ sessionPath }); + client.creationResult = async (commandId) => ({ + cancelled: false, + commandId, + sessionId: "b", + sessionPath, + }); client.snapshots.push( Promise.resolve(activeSnapshot("b", sessionPath, { workspace })), ); @@ -2011,16 +2240,19 @@ describe("workspace selection authority", () => { expect( client.creations.every((call) => call.workspacePath === workspace), ).toBe(true); + expect(new Set(client.creations.map((call) => call.commandId)).size).toBe( + 1, + ); expect(client.prompts).toEqual([{ sessionId: "b", content: "B only" }]); store.getState().actions.stop(); }); it("rechecks creation when a newer snapshot supersedes its confirmation", async () => { const { client, store } = await harness(); - const creation = deferred(); + const creation = deferred(); const slowConfirmation = deferred(); const active = activeSnapshot("b", sessionPath, { workspace }); - client.creationResult = creation.promise; + client.creationResult = () => creation.promise; client.snapshots.push( slowConfirmation.promise, Promise.resolve(active), @@ -2030,7 +2262,12 @@ describe("workspace selection authority", () => { const sending = store.getState().actions.sendPrompt("B only"); await vi.waitFor(() => expect(client.creations).toHaveLength(1)); - creation.resolve({ sessionPath }); + creation.resolve({ + cancelled: false, + commandId: client.creations[0]!.commandId, + sessionId: "b", + sessionPath, + }); await vi.waitFor(() => expect(client.snapshotPaths).toHaveLength(2)); expect(await store.getState().actions.refreshSnapshot()).toBe(true); @@ -2046,14 +2283,18 @@ describe("workspace selection authority", () => { it.each([ { name: "cancelled creation", - receipt: { cancelled: true }, + receipt: { cancelled: true, sessionId: "b" }, next: snapshot(), }, { name: "missing creation identity", receipt: {}, next: snapshot() }, - { name: "different workspace", receipt: { sessionPath }, next: snapshot() }, + { + name: "different workspace", + receipt: { cancelled: false, sessionId: "b", sessionPath }, + next: snapshot(), + }, { name: "different Session in B", - receipt: { sessionPath }, + receipt: { cancelled: false, sessionId: "b", sessionPath }, next: activeSnapshot("other", `${workspace}/other.jsonl`, { workspace }), }, ])( @@ -2061,7 +2302,8 @@ describe("workspace selection authority", () => { async ({ receipt, next }) => { const { client, store } = await harness(); store.getState().actions.setWorkspace(workspace); - client.creationResult = Promise.resolve(receipt); + client.creationResult = async (commandId) => + ({ commandId, ...receipt }) as SessionCreationResult; client.snapshots.push(Promise.resolve(next), Promise.resolve(next)); expect(await store.getState().actions.sendPrompt("B only")).toBe(false); expect(client.prompts).toEqual([]); @@ -2085,13 +2327,18 @@ describe("workspace selection authority", () => { it("never sends an in-flight B draft after the user chooses C", async () => { const { client, store } = await harness(); - const creation = deferred(); - client.creationResult = creation.promise; + const creation = deferred(); + client.creationResult = () => creation.promise; store.getState().actions.setWorkspace(workspace); const sending = store.getState().actions.sendPrompt("B only"); await vi.waitFor(() => expect(client.creations).toHaveLength(1)); store.getState().actions.setWorkspace("/tmp/repo-c"); - creation.resolve({ sessionPath }); + creation.resolve({ + cancelled: false, + commandId: client.creations[0]!.commandId, + sessionId: "b", + sessionPath, + }); expect(await sending).toBe(false); expect(store.getState().selectedWorkspace).toBe("/tmp/repo-c"); expect(store.getState().workspaceDraft).toBe(true); @@ -2117,7 +2364,12 @@ describe("workspace selection authority", () => { expect(store.getState().selectedWorkspace).toBe("/tmp/ws"); expect(store.getState().workspaceDraft).toBe(true); const createdPath = "/tmp/ws/fresh.jsonl"; - client.creationResult = Promise.resolve({ sessionPath: createdPath }); + client.creationResult = async (commandId) => ({ + cancelled: false, + commandId, + sessionId: "fresh", + sessionPath: createdPath, + }); client.snapshots.push( Promise.resolve(activeSnapshot("fresh", createdPath)), ); diff --git a/web/dist/app.js b/web/dist/app.js index 99cbf714..105c1944 100644 --- a/web/dist/app.js +++ b/web/dist/app.js @@ -94,4 +94,4 @@ Try polyfilling it using "@formatjs/intl-pluralrules" `,t);continue}let r=e.charCodeAt(t);if(Jx(e,t,r)){let r=e.charCodeAt(t+5)===Gx?t+6:t+5,o=e.slice(r,n);if(f===0&&e.charCodeAt(n+1)===Ux){u!==void 0&&a?.(u),i?.({id:u,event:p,data:o}),u=void 0,d=``,p=void 0,t=n+2,n=e.indexOf(` `,t);continue}d=f===0?o:`${d}\n${o}`,f++}else Yx(e,t,r)?p=e.slice(e.charCodeAt(t+6)===Gx?t+7:t+6,n)||void 0:C(e,t,n);t=n+1,n=e.indexOf(` `,t)}return e.slice(t)}for(;t20?`${e.slice(0,20)}…`:e}"`,{type:`unknown-field`,field:e,value:t,line:n}))}}function T(){u!==void 0&&a?.(u),f>0&&i?.({id:u,event:p,data:d}),u=void 0,d=``,f=0,p=void 0}function E(e={}){if(e.consume&&s.length>0){let e=s.join(``);C(e,0,e.length)}l=!0,u=void 0,d=``,f=0,p=void 0,s.length=0,c=0,m=!1,h=!1,g=!1}return{feed:_,reset:E}}function Jx(e,t,n){return n===100&&e.charCodeAt(t+1)===97&&e.charCodeAt(t+2)===116&&e.charCodeAt(t+3)===97&&e.charCodeAt(t+4)===58}function Yx(e,t,n){return n===101&&e.charCodeAt(t+1)===118&&e.charCodeAt(t+2)===101&&e.charCodeAt(t+3)===110&&e.charCodeAt(t+4)===116&&e.charCodeAt(t+5)===58}function Xx(e,t){let n=1;for(;nt.abort();e.signal.addEventListener(`abort`,n,{once:!0}),e.signal.aborted&&n();let r=window.setTimeout(n,45e3),i=await fetch(`/events?cursor=${e.cursor}`,{headers:e.client.headers(),signal:t.signal}).finally(()=>{window.clearTimeout(r),e.signal.removeEventListener(`abort`,n)});if(i.status===409)throw new Zx(`event replay expired`);if(!i.ok||!i.body)throw Error(`event connection failed`);e.onConnected();let a=e.cursor,o=0,s=qx({onComment(t){t.trim()===`heartbeat`&&++o>=4&&(o=0,e.onHeartbeat?.())},onEvent(t){let n=JSON.parse(t.data);if(!Number.isSafeInteger(n.sequence))throw new Zx(`invalid event cursor`);if(!(n.sequence<=a)){if(n.sequence!==a+1)throw new Zx(`event cursor gap`);if(a=n.sequence,n.type===`state_invalidated`)throw new Zx(`state invalidated`);e.onEvent(n)}}}),c=i.body.getReader(),l=new TextDecoder;try{for(;!e.signal.aborted;){let t,n,r=c.read(),i=new Promise((r,i)=>{n=()=>i(Error(`event stream aborted`)),e.signal.addEventListener(`abort`,n,{once:!0}),e.signal.aborted&&n(),t=window.setTimeout(()=>i(Error(`event stream stalled`)),45e3)}),{done:a,value:o}=await Promise.race([r,i]).finally(()=>{window.clearTimeout(t),n&&e.signal.removeEventListener(`abort`,n)});if(a)throw Error(`event connection closed`);s.feed(l.decode(o,{stream:!0}))}}finally{await c.cancel().catch(()=>void 0)}}function $x(e,t,n){if([`session_start`,`session_switched`,`session_created`].includes(t))return[];if([`agent_settled`,`turn_settled`].includes(t))return e.map(e=>e.state===`running`?{...e,state:`unknown`}:e);if(!t.startsWith(`tool_execution_`))return e;let r=n.toolCallId;if(typeof r!=`string`||r.length>500)return e;let i=e.find(e=>e.call.id===r);if(i&&i.state!==`running`&&(i.state!==`unknown`||t!==`tool_execution_end`))return e;let a=Uu(n.call),o=a.type===`toolCall`&&typeof a.name==`string`&&typeof a.arguments==`string`?a:i?.call;if(!o||o.evidenceTruncated&&o.id?.includes(`[truncated]`))return e;let s=Uu(n.result),c={call:o,result:typeof s.content==`string`?s:i?.result,state:t===`tool_execution_end`?n.isError===!0?`failed`:`returned`:`running`},l=[...e.filter(e=>e.call.id!==r),c].slice(-32);for(;l.length>0&&new TextEncoder().encode(JSON.stringify(l)).byteLength>524288;)l.shift();return l}var eS=`openpi.collapsed-workspaces`,tS=`openpi.sidebar-collapsed`,nS=new Set([`agent_start`,`turn_started`,`turn_settled`,`agent_settled`,`prompt_settled`,`message_end`,`tool_execution_end`,`session_start`,`session_switched`,`session_progress`,`prompt_failed`,`model_select`,`workspace_imported`,`workspace_removed`,`workspace_renamed`,`session_renamed`,`session_archived`,`session_unarchived`,`session_created`,`prompt_accepted`,`runtime_changed`]);function rS(e){try{let t=JSON.parse(window.sessionStorage.getItem(e)||`[]`);return new Set(Array.isArray(t)?t.filter(e=>typeof e==`string`):[])}catch{return new Set}}function iS(e){try{return window.sessionStorage.getItem(e)===`true`}catch{return!1}}function aS(e,t){try{window.sessionStorage.setItem(e,JSON.stringify(t))}catch{}}function oS(e,t){return t.aborted?Promise.resolve():new Promise(n=>{let r=()=>{window.clearTimeout(i),t.removeEventListener(`abort`,r),n()},i=window.setTimeout(r,e);t.addEventListener(`abort`,r,{once:!0})})}function sS(e=new ed,t={}){let n=t.consumeEvents??Qx,r=0,i=0,a=0,o=null,s=null,c=null,l=Promise.resolve(),u=null,d=!1,f=!1,p=null,m=null,h=0,g=null,_=0,v=!1,y=0,b=0,x=null,S=-1,C=null,w=0,T=new Set,E=new Set,D=(e,t)=>{if(typeof t==`string`)for(e.add(t);e.size>32;){let t=e.values().next().value;t&&e.delete(t)}},O=()=>({activeTurn:null,turnCancellationPending:!1,turnTerminalStatus:null,pendingFollowUpsReceipt:null,liveMessages:[],liveRunning:!1,livePhase:`idle`,liveRetry:null,thinkingStarts:{},thinkingDurations:{}}),k=()=>(h++,m?.abort(),m=null,{modelSearch:{query:``,status:`idle`,models:[],totalMatches:0,matchesOmitted:0,error:null}}),A=()=>({sessionId:null,status:`idle`,commands:[],totalAvailable:0,commandsOmitted:0,error:null}),j=(e,t)=>({liveRunning:t===`running`||!e,livePhase:t===`running`?`running`:e?`idle`:`preparing`});return un((t,M)=>{let N=e=>{t({notice:e instanceof Error?e.message:String(e)})},P=()=>{w++,C?.abort(),C=null,t({commandDiscovery:A()})},ee=async(n,i,a)=>{if(M().modelSelectionPending)return!1;t({modelSelectionPending:!0});try{let o=await e.selectModel(n.provider,n.id,a);if(i!==r||a!==M().snapshot?.selectedSession?.id)return!1;if(o.provider!==n.provider||o.id!==n.id||!o.current)throw Error(`Model selection was not confirmed. Please select a model again.`);if(!await M().actions.refreshSnapshot({epoch:i})||i!==r||a!==M().snapshot?.selectedSession?.id)return!1;let s=M().snapshot?.models.find(e=>e.current);if(s?.provider!==n.provider||s.id!==n.id)throw Error(`The Session model changed. Please select a model again.`);return t({draftModel:null,notice:null}),!0}catch(e){return i===r&&a===M().snapshot?.selectedSession?.id&&N(e),!1}finally{i===r&&t({modelSelectionPending:!1})}},F=(e=160)=>{if(d){f=!0;return}u===null&&(u=window.setTimeout(async()=>{u=null,d=!0;try{await M().actions.refreshSnapshot()}finally{d=!1,f&&(f=!1,F())}},e))},I=()=>{x=null,b=0,S=-1},L=()=>{let e=M().snapshot;if(!e)return;x&&S!==r&&I();let n=e.thinking;if(n){if(!x||n.revision>=b){x=n,b=n.revision,S=r;return}n!==x&&t({snapshot:{...e,thinking:x}})}},te=e=>{x&&S!==r&&I(),e&&e.revision>=b&&(x=e,b=e.revision,S=r),L()},ne=()=>{g=null,_++,v=!1,t({thinkingPendingLevel:null})},re=async()=>{let t=r,n=M().snapshot?.selectedSession?.id;if(n)try{let i=await e.thinking(n,new AbortController().signal);if(t!==r||n!==M().snapshot?.selectedSession?.id)return;te(i)}catch{}},R=async()=>{if(v)return;v=!0;let n=++y;try{for(;g!==null;){let n=g,i=_,a=r,o=M().snapshot?.selectedSession?.id;if(!o||M().modelSelectionPending){ne();return}if(M().snapshot?.thinking?.level===n){i===_&&(g=null,t({thinkingPendingLevel:null}));continue}let s;try{s=await e.setThinkingLevel(o,n)}catch(e){if(a!==r)return;if(i!==_)continue;ne(),N(e),re();return}if(a!==r||o!==M().snapshot?.selectedSession?.id)return;if(i===_){if(s.level!==n){ne(),N(Error(Pt.t(`thinkingNotConfirmed`)));return}te(s),g=null,t({thinkingPendingLevel:null}),F();return}}}finally{n===y&&(v=!1)}},z=e=>{let n=M(),i=e.detail??{},a=i.sessionId,l=[`session_start`,`session_switched`,`session_created`].includes(e.type);if(n.cursor!==null&&e.sequence<=n.cursor)return;t({cursor:e.sequence});let u=n.promptAdmissionRecovery,d=u?.commandId;if(typeof i.commandId==`string`&&i.commandId===d&&[`prompt_accepted`,`prompt_failed`,`prompt_settled`,`turn_started`,`turn_settled`].includes(e.type)){let r=e.type!==`prompt_failed`;t({promptAdmissionRecovery:null,promptAdmissionResolution:r&&u?{commandId:u.commandId,content:u.content}:n.promptAdmissionResolution,liveMessages:r&&u&&!n.liveMessages.some(e=>e.key===u.optimisticKey)?[...n.liveMessages,{key:u.optimisticKey,message:{role:`user`,content:u.content}}].slice(-8):n.liveMessages})}if(e.type===`runtime_changed`&&t(k()),e.type===`runtime_changed`&&P(),n.sessionSwitching&&!l){F();return}if(typeof a==`string`&&a!==n.snapshot?.currentSessionId&&!l){F();return}if(n.snapshot){let r=$x(n.snapshot.runtime.liveTools??[],e.type,i);t({snapshot:{...n.snapshot,runtime:{...n.snapshot.runtime,liveTools:r}}})}if(l){let a=i.commandId;if(typeof a==`string`&&E.has(a)){F();return}let l=i.sessionPath,d=n.snapshot?.sessions.some(e=>e.path===l),f=!1;if(c?.kind===`select`?f=c.expectedPath===l:c?.kind===`create`&&c.commandId===a&&e.type===`session_switched`&&typeof l==`string`&&!d?(c.observedPath=l,f=!0):c?.kind===`create`&&c.commandId===a&&e.type===`session_created`&&typeof c.observedPath==`string`&&(f=!0),f&&c?.epoch!==r)return;if(f)t(O());else{P();let e=++r;ne(),o=null,s=null,t({...O(),promptAdmissionPending:!1,promptAdmissionRecovery:null,promptAdmissionResolution:u?{commandId:u.commandId,content:u.content}:n.promptAdmissionResolution,selectedPath:typeof l==`string`?l:null,draftModel:n.workspaceDraft?n.draftModel:null,modelSelectionPending:!1,sessionSwitching:!0}),M().actions.refreshSnapshot({epoch:e}).then(n=>{e===r&&t({selectedPath:n?M().selectedPath:null,sessionSwitching:!1})})}}else if(e.type===`prompt_accepted`){let e=T.has(String(i.commandId??``));t({...j(e,n.livePhase),liveRetry:null,pendingFollowUpsReceipt:Number.isInteger(i.pendingFollowUps)?Number(i.pendingFollowUps):n.pendingFollowUpsReceipt})}else if(e.type===`turn_started`)t({activeTurn:{sessionId:String(i.sessionId),commandId:String(i.commandId),epoch:Number(i.epoch)},liveRunning:!0,livePhase:`running`,liveRetry:null,turnTerminalStatus:null});else if(e.type===`agent_start`)t({...i.activeTurn?{activeTurn:i.activeTurn}:{},liveRunning:!0,livePhase:`running`,liveRetry:null});else if(e.type===`turn_settled`){D(T,i.commandId);let e=n.activeTurn;e?.sessionId===i.sessionId&&e?.commandId===i.commandId&&e?.epoch===i.epoch&&t({activeTurn:null,liveRunning:!1,livePhase:`idle`,liveRetry:null,turnTerminalStatus:typeof i.outcome==`string`?i.outcome:null})}else if(e.type===`agent_settled`)t({pendingFollowUpsReceipt:null,...n.activeTurn?{}:{liveRunning:!1,livePhase:`idle`,liveRetry:null}});else if(e.type===`prompt_settled`)D(T,i.commandId),n.livePhase!==`running`&&t({liveRunning:!1,livePhase:`idle`,liveRetry:null});else if(i.message&&typeof i.message==`object`){let r=i.message,a=n.liveMessages;r.role===`user`&&(a=a.filter(e=>!e.key.startsWith(`optimistic-`)||e.message.content!==r.content));let o=typeof i.messageKey==`string`?i.messageKey:`${r.role||`message`}-${e.sequence}`,s={key:o,message:r},c=a.findIndex(e=>e.key===o);a=c>=0?a.map((e,t)=>t===c?s:e):[...a,s].slice(-8);let l={...n.thinkingStarts},u={...n.thinkingDurations};r.parts?.some(e=>e.type===`thinking`)&&(l[o]??=Date.now(),e.type===`message_end`&&(u[o]=Date.now()-l[o])),t({liveMessages:a,thinkingDurations:u,thinkingStarts:l})}if(e.type===`prompt_failed`&&(D(T,i.commandId),t({liveMessages:M().liveMessages.filter(e=>!e.key.startsWith(`optimistic-`)),liveRunning:!1,livePhase:`idle`,liveRetry:null,notice:typeof i.error==`string`?i.error:`Prompt failed`})),e.type===`auto_retry_start`&&t({liveRunning:!0,livePhase:`running`,liveRetry:{attempt:Number(i.attempt)||0,maxAttempts:Number(i.maxAttempts)||0}}),e.type===`thinking_level_changed`){let t=M().snapshot?.thinking;t&&typeof i.level==`string`&&te({...t,level:i.level,revision:e.sequence})}nS.has(e.type)&&F()},ie=async r=>{let i=500;for(;!r.aborted;){let a=!1;try{if(M().cursor===null){if(a=!0,!await M().actions.refreshSnapshot({resetCursor:!0}))throw Error(`snapshot unavailable`);a=!1}if(r.aborted)return;await n({client:e,cursor:M().cursor??0,onConnected:()=>{i=500,t({connection:`connected`,notice:null})},onEvent:z,onHeartbeat:()=>F(0),signal:r})}catch(e){if(r.aborted)return;t({connection:`reconnecting`}),!a&&await M().actions.refreshSnapshot({resetCursor:!0})||t(O()),await oS(i,r),i=Math.min(i*2,5e3),e instanceof SyntaxError&&t({notice:`Invalid event data`})}}},ae={start(){p||(p=new AbortController,ie(p.signal))},stop(){p?.abort(),p=null,u!==null&&window.clearTimeout(u),u=null,ne(),P()},async refreshSnapshot(n={}){let a=n.epoch??r,o=++i,c=M().selectedPath;try{let l=await e.snapshot(c);if(a!==r||o!==i)return!1;let u=typeof l.currentSessionId==`string`,d=u?l.sessions.find(e=>e.id===l.currentSessionId):void 0,f=u?l.selectedSession?.id===l.currentSessionId:l.selectedSession===void 0,p=!c||l.sessions.some(e=>e.path===c),m=!c||l.selectedSession?.path===c;if(!p||!m||!f)return t({selectedPath:null}),!n.canonicalRetry&&ae.refreshSnapshot({...n,canonicalRetry:!0,epoch:a});let h=l.selectedSession?.cwd,g=l.workspaces.find(e=>e.current)?.path,_=l.workspaces.some(e=>e.path===M().selectedWorkspace)?M().selectedWorkspace:void 0,v=M().workspaceDraft?M().selectedWorkspace:l.workspaces.some(e=>e.path===h)?h:g??_??null,y=n.resetCursor;return y&&I(),M().snapshot?.currentSessionId!==l.currentSessionId&&P(),t({...y?O():{},connection:M().connection===`connecting`?`connecting`:M().connection,cursor:y||M().cursor===null?l.cursor:Math.max(M().cursor??0,l.cursor),activeTurn:l.runtime.activeTurn??null,livePhase:l.runtime.status===`running`?`running`:!M().promptAdmissionPending&&!s?`idle`:M().livePhase,liveRetry:l.runtime.status!==`running`&&!M().promptAdmissionPending&&!s?null:M().liveRetry,liveRunning:l.runtime.status===`running`?!0:!M().promptAdmissionPending&&!s?!1:M().liveRunning,selectedPath:d?.path??l.selectedSession?.path??null,selectedWorkspace:v,snapshot:l,...k()}),te(),!0}catch(e){return a!==r||o!==i?!1:(t({connection:`unavailable`}),N(e),!1)}},async chooseWorkspace(){let t=r;try{let n=await e.chooseWorkspace();if(t!==r||n.cancelled||!n.path)return;ae.setWorkspace(n.path),await ae.refreshSnapshot()}catch(e){t===r&&N(e)}},setWorkspace(e){let n=M();e===n.selectedWorkspace&&!n.sessionSwitching||(++r,ne(),P(),o=null,s=null,t({...O(),...k(),selectedWorkspace:e,workspaceDraft:!0,sessionSwitching:!1,modelSelectionPending:!1,promptAdmissionPending:!1,promptAdmissionRecovery:null,promptAdmissionResolution:n.promptAdmissionRecovery?{commandId:n.promptAdmissionRecovery.commandId,content:n.promptAdmissionRecovery.content}:n.promptAdmissionResolution,notice:null}))},async renameWorkspace(t,n){try{await e.renameWorkspace(t,n),await ae.refreshSnapshot()}catch(e){throw N(e),e}},async removeWorkspace(n){try{await e.removeWorkspace(n),M().selectedWorkspace===n&&P(),t({selectedPath:null,selectedWorkspace:M().selectedWorkspace===n?null:M().selectedWorkspace}),await ae.refreshSnapshot()}catch(e){N(e)}},async createSession(n){if(!n||M().modelSelectionPending)return!1;let i=M(),a=++r;ne(),P();let u=globalThis.crypto?.randomUUID?.()??`web-create-${Date.now()}-${a}`;o=null,s=null,t({...O(),...k(),mobileSidebarOpen:!1,promptAdmissionPending:!1,promptAdmissionRecovery:null,promptAdmissionResolution:i.promptAdmissionRecovery?{commandId:i.promptAdmissionRecovery.commandId,content:i.promptAdmissionRecovery.content}:i.promptAdmissionResolution,selectedPath:null,selectedWorkspace:n,workspaceDraft:!0,sessionSwitching:!0});let d=!1,f=l.then(async()=>{if(a===r){c={commandId:u,epoch:a,expectedPath:null,kind:`create`,observedPath:null};try{let i=await e.createSession(n,u);if(a!==r)return;if(i.cancelled||!i.sessionPath)throw Error(`Session creation was not confirmed. Please try again.`);t({selectedPath:null});let o=await ae.refreshSnapshot({epoch:a});if(!o&&a===r&&(o=await ae.refreshSnapshot({epoch:a})),a!==r)return;if(!o)throw Error(`The created Session could not be confirmed. Please try again.`);let s=M().snapshot?.selectedSession;if(!s||s.path!==i.sessionPath||s.cwd!==n||s.id!==M().snapshot?.currentSessionId)throw Error(`The created Session is no longer active in the selected workspace. Please try again.`);t({workspaceDraft:!1,notice:null});let c=M().draftModel,l=s.id;d=!c||await ee(c,a,l)}catch(e){if(a!==r)return;t({selectedPath:null}),N(e),await ae.refreshSnapshot({epoch:a})}finally{D(E,u),c?.epoch===a&&(c=null),a===r&&t({sessionSwitching:!1})}}});return l=f.catch(()=>void 0),await f,d&&a===r},async selectSession(n){if(!n)return;let i=M();P(),t({...k(),workspaceDraft:!1,draftModel:null,modelSelectionPending:!1});let a=++r;ne(),o=null,s=null,t({...O(),mobileSidebarOpen:!1,promptAdmissionPending:!1,promptAdmissionRecovery:null,promptAdmissionResolution:i.promptAdmissionRecovery?{commandId:i.promptAdmissionRecovery.commandId,content:i.promptAdmissionRecovery.content}:i.promptAdmissionResolution,selectedPath:n,sessionSwitching:!0});let u=l.then(async()=>{if(a===r){c={epoch:a,expectedPath:n,kind:`select`};try{if(await e.selectSession(n),a!==r)return;await ae.refreshSnapshot({epoch:a})||t({selectedPath:null})}catch(e){if(a!==r)return;t({selectedPath:null}),N(e),await ae.refreshSnapshot({epoch:a})}finally{c?.epoch===a&&(c=null),a===r&&t({sessionSwitching:!1})}}});l=u.catch(()=>void 0),await u},async renameSession(t,n){try{await e.renameSession(t,n),await ae.refreshSnapshot()}catch(e){throw N(e),e}},async archiveSession(t){try{await e.archiveSession(t),await ae.refreshSnapshot()}catch(e){N(e)}},async unarchiveSession(t){let n=r;try{return await e.unarchiveSession(t),n!==r||await ae.refreshSnapshot({epoch:n})}catch(e){return n===r&&N(e),!1}},async selectModel(e){let[n,...i]=e.split(`/`),a=i.join(`/`),o=M();if(!n||!a||o.sessionSwitching||o.modelSelectionPending||o.promptAdmissionPending||!o.workspaceDraft&&(o.liveRunning||o.snapshot?.runtime.status===`running`))return;let s=o.snapshot?.selectedSession?.id;if(o.workspaceDraft||!s&&!o.snapshot?.currentSessionId){let e=[...o.snapshot?.models??[],...o.modelSearch.models].find(e=>e.provider===n&&e.id===a);e&&t({draftModel:e,notice:null});return}!s||s!==o.snapshot?.currentSessionId||(ne(),await ee({provider:n,id:a},r,s))},async searchModels(n){let a=n.trim();if(!a){t(k());return}m?.abort();let o=new AbortController;m=o;let s=++h,c=r,l=i,u=M().snapshot?.currentSessionId;t({modelSearch:{query:a,status:`loading`,models:[],totalMatches:0,matchesOmitted:0,error:null}});try{let n=await e.searchModels(a,u,o.signal);if(o.signal.aborted||c!==r||l!==i||s!==h)return;t({modelSearch:{query:a,status:`ready`,models:n.models,totalMatches:n.totalMatches,matchesOmitted:n.truncation.matchesOmitted,error:null}})}catch(e){if(o.signal.aborted||c!==r||l!==i||s!==h)return;t({modelSearch:{query:a,status:`error`,models:[],totalMatches:0,matchesOmitted:0,error:e instanceof Error?e.message:String(e)}})}finally{m===o&&(m=null)}},clearModelSearch(){t(k())},selectThinking(e){let n=M(),r=n.snapshot?.thinking,i=n.snapshot?.selectedSession?.id;!e||!r?.supported||n.sessionSwitching||n.modelSelectionPending||n.workspaceDraft||!i||i!==n.snapshot?.currentSessionId||n.liveRunning||n.snapshot?.runtime.status===`running`||e!==(n.thinkingPendingLevel??r.level)&&(g=e,_++,t({thinkingPendingLevel:e}),R())},async cancelActiveTurn(){let n=M().activeTurn??M().snapshot?.runtime.activeTurn;if(!n||M().turnCancellationPending||M().sessionSwitching)return;let i=r;t({turnCancellationPending:!0});try{await e.cancelActiveTurn(n)}catch(e){if(i!==r)return;await ae.refreshSnapshot({epoch:i}),i===r&&N(e)}finally{i===r&&t({turnCancellationPending:!1})}},async sendPrompt(n){let i=n.trim(),c=M(),l=c.promptAdmissionRecovery,u=l?.phase===`submitting`?l:null,d=c.selectedWorkspace;if(!d||!i||c.sessionSwitching||c.promptAdmissionPending||l&&!u||c.promptAdmissionResolution||c.modelSelectionPending||c.thinkingPendingLevel!==null)return!1;let f=M().workspaceDraft||!M().snapshot?.selectedSession?.id;if(f&&!await ae.createSession(d)||f&&M().draftModel)return!1;let p=M().snapshot?.selectedSession?.id;if(!p||M().workspaceDraft||M().selectedWorkspace!==d||M().snapshot?.selectedSession?.cwd!==d||p!==M().snapshot?.currentSessionId||M().sessionSwitching||M().promptAdmissionPending||u&&(M().promptAdmissionRecovery?.commandId!==u.commandId||M().promptAdmissionRecovery?.phase!==`submitting`))return!1;let m=r,h=M().draftModel;if(h&&!await ee(h,m,p)||m!==r||p!==M().snapshot?.selectedSession?.id||p!==M().snapshot?.currentSessionId||M().workspaceDraft||M().selectedWorkspace!==d||M().snapshot?.selectedSession?.cwd!==d||u&&(M().promptAdmissionRecovery?.commandId!==u.commandId||M().promptAdmissionRecovery?.phase!==`submitting`))return!1;let g=++a,_=s?.sessionId===p&&s.content===i,v=_?s.commandId:globalThis.crypto?.randomUUID?.()??`web-prompt-${Date.now()}-${g}`,y=_?s.optimisticKey:`optimistic-${v}`;s={sessionId:p,content:i,commandId:v,optimisticKey:y},o=g,t({liveMessages:_?M().liveMessages:[...M().liveMessages,{key:y,message:{role:`user`,content:i}}].slice(-8),notice:null,pendingFollowUpsReceipt:null,turnTerminalStatus:null,promptAdmissionPending:!0,scrollToBottom:M().scrollToBottom+1});try{let n=await e.prompt(p,i,v,_);if(m!==r||o!==g)return!1;let a=T.has(n.id);return s?.commandId===v&&(s=null),t({...j(a,M().livePhase),pendingFollowUpsReceipt:n.pendingFollowUps??null,...u&&M().promptAdmissionRecovery?.commandId===u.commandId?{promptAdmissionRecovery:null}:{}}),F(120),!0}catch(e){if(m!==r||o!==g)return!1;if(e instanceof Qu&&e.code===`COMMAND_ADMISSION_UNKNOWN`&&s?.commandId===v){let e=s;s=null,t({liveRunning:!1,livePhase:`idle`,liveRetry:null,notice:null,promptAdmissionPending:!1,promptAdmissionRecovery:{...e,phase:`checking`}});let n=await ae.refreshSnapshot({resetCursor:!0,epoch:m}),r=M().promptAdmissionRecovery;return r?.commandId===v&&t({promptAdmissionRecovery:{...r,phase:n?`ready`:`verification-failed`}}),!1}return e instanceof Qu&&[`WORKSPACE_REQUIRED`,`SESSION_CONFLICT`,`PROMPT_REJECTED`,`COMMAND_CONFLICT`,`PROMPT_ADMISSION_CAPACITY`].includes(e.code??``)?(s?.commandId===v&&(s=null),t({liveMessages:M().liveMessages.filter(e=>e.key!==y),livePhase:`idle`,liveRetry:null,liveRunning:!1}),N(e),!1):(t({liveRunning:!0,livePhase:M().livePhase===`running`?`running`:`preparing`,liveRetry:null}),N(e),!1)}finally{m===r&&o===g&&(o=null,t({promptAdmissionPending:!1}))}},async checkPromptAdmissionRecovery(){let e=M().promptAdmissionRecovery;if(!e||e.phase!==`verification-failed`)return;let n=r;t({notice:null,promptAdmissionRecovery:{...e,phase:`checking`}});let i=await ae.refreshSnapshot({resetCursor:!0,epoch:n}),a=M().promptAdmissionRecovery;a?.commandId===e.commandId&&a.phase===`checking`&&t({promptAdmissionRecovery:{...a,phase:i?`ready`:`verification-failed`}})},async sendPromptAsNew(e){let n=M().promptAdmissionRecovery;if(!n||n.phase!==`ready`||n.sessionId!==M().snapshot?.selectedSession?.id)return!1;let r=e.trim();if(!r)return!1;t({promptAdmissionRecovery:{...n,phase:`submitting`},notice:null});let i=!1;try{return i=await ae.sendPrompt(r),i}finally{let e=M().promptAdmissionRecovery;e?.commandId===n.commandId&&e.phase===`submitting`&&t({promptAdmissionRecovery:i?null:{...e,phase:`ready`}})}},abandonPromptAdmission(){let e=M().promptAdmissionRecovery;!e||e.phase===`submitting`||t({liveMessages:M().liveMessages.filter(t=>t.key!==e.optimisticKey),notice:null,promptAdmissionRecovery:null})},acknowledgePromptAdmissionResolution(e){M().promptAdmissionResolution?.commandId===e&&t({promptAdmissionResolution:null})},async discoverCommands(){let n=M(),i=n.snapshot?.selectedSession?.id;if(n.workspaceDraft||!i||i!==n.snapshot?.currentSessionId||n.sessionSwitching){P();return}if(n.commandDiscovery.sessionId===i&&(n.commandDiscovery.status===`loading`||n.commandDiscovery.status===`ready`))return;let a=r,o=++w;C?.abort();let s=new AbortController;C=s,t({commandDiscovery:{sessionId:i,status:`loading`,commands:[],totalAvailable:0,commandsOmitted:0,error:null}});try{let n=await e.commands(i,s.signal);if(s.signal.aborted||a!==r||o!==w||i!==M().snapshot?.currentSessionId)return;t({commandDiscovery:{sessionId:i,status:`ready`,commands:n.commands,totalAvailable:n.totalAvailable,commandsOmitted:n.truncation.commandsOmitted,error:null}})}catch(e){if(s.signal.aborted||a!==r||o!==w)return;t({commandDiscovery:{sessionId:i,status:`error`,commands:[],totalAvailable:0,commandsOmitted:0,error:e instanceof Error?e.message:String(e)}})}finally{C===s&&(C=null)}},clearCommandDiscovery:P,setQuery(e){t({query:e})},setSearchOpen(e){t({searchOpen:e,...e?{}:{query:``}})},toggleWorkspace(e){let n=new Set(M().collapsed);n.has(e)?n.delete(e):n.add(e),aS(eS,[...n]),t({collapsed:n})},toggleSidebar(e){if(e){t({mobileSidebarOpen:!M().mobileSidebarOpen});return}let n=!M().sidebarCollapsed;try{window.sessionStorage.setItem(tS,String(n))}catch{}t({sidebarCollapsed:n})},closeMobileSidebar(){t({mobileSidebarOpen:!1})},clearNotice(){t({notice:null})}};return{activeTurn:null,turnCancellationPending:!1,turnTerminalStatus:null,pendingFollowUpsReceipt:null,snapshot:null,cursor:null,selectedPath:null,selectedWorkspace:null,workspaceDraft:!1,draftModel:null,modelSelectionPending:!1,modelSearch:{query:``,status:`idle`,models:[],totalMatches:0,matchesOmitted:0,error:null},collapsed:rS(eS),sidebarCollapsed:iS(tS),mobileSidebarOpen:!1,query:``,searchOpen:!1,connection:`connecting`,notice:null,liveMessages:[],liveRunning:!1,livePhase:`idle`,liveRetry:null,thinkingStarts:{},thinkingDurations:{},promptAdmissionPending:!1,promptAdmissionRecovery:null,promptAdmissionResolution:null,sessionSwitching:!1,scrollToBottom:0,thinkingPendingLevel:null,commandDiscovery:A(),actions:ae}})}var cS=sS();function lS(){let e=fn(cS),{t}=sn(),{actions:n}=e,r=(0,w.useRef)(null),[i,a]=(0,w.useState)(()=>window.matchMedia?.(`(max-width: 760px)`).matches??!1),o=i&&e.mobileSidebarOpen;(0,w.useEffect)(()=>{let e=window.matchMedia?.(`(max-width: 760px)`);if(!e)return;let t=()=>{a(e.matches),e.matches||n.closeMobileSidebar()};return t(),e.addEventListener(`change`,t),()=>e.removeEventListener(`change`,t)},[n]);let[s,c]=(0,w.useState)(`chat`),[l,u]=(0,w.useState)(null),d=e.snapshot?.models.find(e=>e.current),f=JSON.stringify([d?.provider,d?.id]),p=t=>{let n=e.snapshot,r=n?.selectedSession;e.workspaceDraft||!n||!r||e.sessionSwitching||r.id!==n.currentSessionId||u({sessionId:r.id,sessionPath:r.path,cwd:r.cwd,model:d?.label??``,modelKey:f,terminalId:t})},m=l&&!e.workspaceDraft&&!e.sessionSwitching&&l.sessionId===e.snapshot?.currentSessionId&&l.sessionPath===e.snapshot?.selectedSession?.path&&l.modelKey===f;(0,w.useEffect)(()=>{l&&!m&&u(null)},[l,m]),(0,w.useEffect)(()=>(n.start(),n.stop),[n]);let h=e.workspaceDraft?void 0:e.snapshot?.selectedSession,g=h?.entries.some(e=>e.type===`message`&&e.message)||e.liveMessages.length>0,_=!h||!g,v=(0,w.useCallback)(e=>n.sendPrompt(e),[n]);return(0,V.jsxs)(`div`,{className:`app-shell ${e.sidebarCollapsed?`sidebar-collapsed`:``} ${e.mobileSidebarOpen?`sidebar-open`:``}`,children:[(0,V.jsx)(rd,{snapshot:e.snapshot,selectedPath:e.workspaceDraft?null:e.selectedPath,selectedWorkspace:e.selectedWorkspace,collapsed:e.collapsed,query:e.query,searchOpen:e.searchOpen,mobileOpen:o,returnFocusRef:r,actions:n}),e.sidebarCollapsed&&(0,V.jsx)(`button`,{className:`sidebar-expand`,type:`button`,"aria-label":t(`expandSidebar`),title:t(`expandSidebar`),onClick:()=>n.toggleSidebar(!1),children:(0,V.jsx)(Se,{})}),(0,V.jsxs)(`main`,{inert:o,className:`conversation-shell ${h?`has-view`:``} ${_&&(e.workspaceDraft||s===`chat`)?`landing`:``}`,children:[(0,V.jsx)(`h1`,{className:`sr-only`,children:`OpenPI`}),(0,V.jsxs)(`header`,{className:`mobile-header`,children:[(0,V.jsx)(`button`,{ref:r,type:`button`,"aria-label":t(`openSidebar`),"aria-controls":`session-sidebar`,"aria-expanded":o,onClick:()=>n.toggleSidebar(!0),children:(0,V.jsx)(be,{})}),(0,V.jsx)(`span`,{className:`connection-state ${e.connection}`,children:t(e.connection)})]}),h&&(0,V.jsxs)(`fieldset`,{className:`conversation-view-switch`,"aria-label":t(`conversationView`),children:[(0,V.jsx)(`button`,{type:`button`,"aria-pressed":s===`chat`,onClick:()=>c(`chat`),children:t(`chatView`)}),(0,V.jsx)(`button`,{type:`button`,"aria-pressed":s===`trajectory`,onClick:()=>c(`trajectory`),children:t(`trajectory`)})]}),e.sessionSwitching?(0,V.jsx)(`div`,{className:`conversation switching`,role:`status`,children:(0,V.jsxs)(`div`,{className:`conversation-running`,children:[(0,V.jsx)(`span`,{className:`conversation-running-dot`}),(0,V.jsx)(`span`,{children:t(`switchingSession`)})]})}):s===`trajectory`&&h&&e.snapshot?(0,V.jsx)(od,{snapshot:e.snapshot,running:e.liveRunning},h.path):_?(0,V.jsx)(`section`,{className:`conversation landing-conversation`,"aria-label":`Conversation`,children:(0,V.jsx)(`div`,{className:`landing-welcome`,children:(0,V.jsx)(hn,{animated:!0})})}):e.snapshot?(0,V.jsx)(zx,{snapshot:e.snapshot,liveMessages:e.liveMessages,liveRunning:e.liveRunning,livePhase:e.livePhase,liveRetry:e.liveRetry,thinkingStarts:e.thinkingStarts,thinkingDurations:e.thinkingDurations,scrollToBottom:e.scrollToBottom,onResend:v}):null,(0,V.jsx)(lu,{workspaceDraft:e.workspaceDraft,draftModel:e.draftModel,modelSelectionPending:e.modelSelectionPending,modelSearch:e.modelSearch,thinkingPendingLevel:e.thinkingPendingLevel,onInspect:p,activeTurn:e.activeTurn,turnCancellationPending:e.turnCancellationPending,turnTerminalStatus:e.turnTerminalStatus,pendingFollowUpsReceipt:e.pendingFollowUpsReceipt,commandDiscovery:e.commandDiscovery,snapshot:e.snapshot,selectedWorkspace:e.selectedWorkspace,sessionSwitching:e.sessionSwitching,promptAdmissionPending:e.promptAdmissionPending,promptAdmissionRecovery:e.promptAdmissionRecovery,promptAdmissionResolution:e.promptAdmissionResolution,liveRunning:e.liveRunning,landing:_,actions:n}),e.notice&&(0,V.jsxs)(`div`,{className:`notice`,role:`alert`,children:[(0,V.jsx)(`span`,{children:e.notice}),(0,V.jsx)(`button`,{type:`button`,"aria-label":t(`close`),onClick:n.clearNotice,children:(0,V.jsx)(Re,{})})]})]}),m&&(0,V.jsx)(td,{target:l,onClose:()=>u(null)},`${l.sessionId}:${l.sessionPath}:${l.terminalId??`status`}`),(0,V.jsx)(`button`,{className:`sidebar-scrim`,type:`button`,tabIndex:-1,"aria-hidden":`true`,"aria-label":t(`close`),onClick:n.closeMobileSidebar})]})}var uS={base:{k1xSpc:`xjp7ctv`,kMwMTN:`x1tgivj0`,kMv6JI:`x9ynric`,$$css:!0},light:{kQNsl9:`x19aimcq`,$$css:!0},dark:{kQNsl9:`xntwwlm`,$$css:!0},system:{kQNsl9:`x108lcm5`,$$css:!0}},dS=w.createContext(!1);dS.displayName=`ThemeNestingContext`;var fS=new Set,pS=0;function mS(e){let t=(0,w.useId)();(0,w.useInsertionEffect)(()=>{if(e.__built)return;let n=`astryx-theme-${e.name}`;if(fS.has(n))return;`${e.name}`,`${e.name}${e.name}${e.name}${e.name}`;let{prose:r,component:i}=Fs(e),a=Ps();fS.add(n);let o=[()=>fS.delete(n)];if(a){if(pS++===0){let e=document.createElement(`style`);e.setAttribute(Zr(`theme-base`),``),e.textContent=`@layer astryx-base {\n${a}\n}`,document.head.appendChild(e)}o.push(()=>{--pS===0&&document.querySelector(`style[${Zr(`theme-base`)}]`)?.remove()})}if(r){let n=document.createElement(`style`);n.setAttribute(Zr(`theme-prose`),e.name),n.setAttribute(Zr(`id`),t),n.textContent=`@layer reset {\n${r}\n}`,document.head.appendChild(n)}if(i){let n=document.createElement(`style`);n.setAttribute(Zr(`theme`),e.name),n.setAttribute(Zr(`id`),t),n.textContent=`@layer astryx-theme {\n${i}\n}`,document.head.appendChild(n)}return(r||i)&&o.push(()=>{let n=document.querySelector(`style[${Zr(`theme-prose`)}="${e.name}"][${Zr(`id`)}="${t}"]`),r=document.querySelector(`style[${Zr(`theme`)}="${e.name}"][${Zr(`id`)}="${t}"]`);n?.remove(),r?.remove()}),()=>{for(let e of o)e()}},[e,t])}function hS(e,t,n){Fc(()=>{if(!e&&!(typeof document>`u`))return t===`light`||t===`dark`?document.documentElement.setAttribute(`data-theme`,t):document.documentElement.removeAttribute(`data-theme`),document.documentElement.setAttribute(Zr(`theme`),n),()=>{document.documentElement.removeAttribute(`data-theme`),document.documentElement.removeAttribute(Zr(`theme`))}},[e,t,n])}function gS({theme:e,mode:t=`system`,children:n}){let r=(0,w.use)(dS);cs(e),mS(e),hS(r,t,e.name);let i=t===`dark`?uS.dark:t===`light`?uS.light:uS.system,a=(0,w.useMemo)(()=>({theme:e,mode:t}),[e,t]);return(0,V.jsx)(Ls,{value:a,children:(0,V.jsx)(dS,{value:!0,children:(0,V.jsx)(`div`,{...bn(uS.base,i),"data-astryx-theme":e.name,"data-theme":t===`system`?void 0:t,children:n})})})}gS.displayName=`Theme`;var _S={size:`1em`,"aria-hidden":!0},vS={name:`neutral`,__built:!0,tokens:{"--font-size-4xs":`0.375rem`,"--font-size-3xs":`0.4375rem`,"--font-size-2xs":`0.5rem`,"--font-size-xs":`0.625rem`,"--font-size-sm":`0.75rem`,"--font-size-base":`0.875rem`,"--font-size-lg":`1.0625rem`,"--font-size-xl":`1.25rem`,"--font-size-2xl":`1.5rem`,"--font-size-3xl":`1.8125rem`,"--font-size-4xl":`2.1875rem`,"--font-size-5xl":`2.625rem`,"--text-heading-1-size":`var(--font-size-2xl)`,"--text-heading-1-weight":`var(--font-weight-semibold)`,"--text-heading-1-leading":`1.3333`,"--text-heading-2-size":`var(--font-size-xl)`,"--text-heading-2-weight":`var(--font-weight-semibold)`,"--text-heading-2-leading":`1.4`,"--text-heading-3-size":`var(--font-size-lg)`,"--text-heading-3-weight":`var(--font-weight-bold)`,"--text-heading-3-leading":`1.4118`,"--text-heading-4-size":`var(--font-size-base)`,"--text-heading-4-weight":`var(--font-weight-bold)`,"--text-heading-4-leading":`1.4286`,"--text-heading-5-size":`var(--font-size-sm)`,"--text-heading-5-weight":`var(--font-weight-semibold)`,"--text-heading-5-leading":`1.6667`,"--text-heading-6-size":`var(--font-size-xs)`,"--text-heading-6-weight":`var(--font-weight-semibold)`,"--text-heading-6-leading":`1.6`,"--text-body-size":`var(--font-size-base)`,"--text-body-weight":`var(--font-weight-normal)`,"--text-body-leading":`1.4286`,"--text-large-size":`var(--font-size-lg)`,"--text-large-weight":`var(--font-weight-semibold)`,"--text-large-leading":`1.4118`,"--text-label-size":`var(--font-size-base)`,"--text-label-weight":`var(--font-weight-medium)`,"--text-label-leading":`1.4286`,"--text-code-size":`var(--font-size-base)`,"--text-code-weight":`var(--font-weight-normal)`,"--text-code-leading":`1.4286`,"--text-supporting-size":`var(--font-size-sm)`,"--text-supporting-weight":`var(--font-weight-normal)`,"--text-supporting-leading":`1.6667`,"--text-display-1-size":`var(--font-size-5xl)`,"--text-display-1-weight":`var(--font-weight-normal)`,"--text-display-1-leading":`1.2381`,"--text-display-2-size":`var(--font-size-4xl)`,"--text-display-2-weight":`var(--font-weight-normal)`,"--text-display-2-leading":`1.2571`,"--text-display-3-size":`var(--font-size-3xl)`,"--text-display-3-weight":`var(--font-weight-normal)`,"--text-display-3-leading":`1.3793`,"--duration-fast-min":`95ms`,"--duration-fast":`125ms`,"--duration-fast-max":`165ms`,"--duration-medium-min":`225ms`,"--duration-medium":`300ms`,"--duration-medium-max":`400ms`,"--duration-slow-min":`525ms`,"--duration-slow":`700ms`,"--duration-slow-max":`935ms`,"--font-family-body":`Figtree, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif`,"--font-family-heading":`Figtree, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif`,"--font-family-code":`ui-monospace, "SF Mono", Monaco, Consolas, "Liberation Mono", "Courier New", monospace`,"--color-syntax-keyword":`light-dark(#700084, #efa8ff)`,"--color-syntax-string":`light-dark(#005600, #a6d2a2)`,"--color-syntax-comment":`light-dark(#737373, #a3a3a3)`,"--color-syntax-number":`light-dark(#6e3500, #ffb37f)`,"--color-syntax-function":`light-dark(#00458c, #a0caff)`,"--color-syntax-type":`light-dark(#700084, #efa8ff)`,"--color-syntax-variable":`light-dark(#171717, #e5e5e5)`,"--color-syntax-operator":`light-dark(#737373, #a3a3a3)`,"--color-syntax-constant":`light-dark(#6e3500, #ffb37f)`,"--color-syntax-tag":`light-dark(#89001a, #ffaeaa)`,"--color-syntax-attribute":`light-dark(#584400, #eec12f)`,"--color-syntax-property":`light-dark(#005348, #83dac9)`,"--color-syntax-punctuation":`light-dark(#6e6e6e, #a0a0a0)`,"--color-syntax-background":`light-dark(#fafafa, #0a0a0a)`,"--color-background-surface":`light-dark(#ffffff, #262626)`,"--color-background-body":`light-dark(#f1f1f1, #1b1b1b)`,"--color-background-card":`light-dark(#ffffff, #1b1b1b)`,"--color-background-popover":`light-dark(#ffffff, #1b1b1b)`,"--color-background-muted":`light-dark(#f1f1f1, #1b1b1b)`,"--color-accent":`light-dark(#262626, #ebebeb)`,"--color-accent-muted":`light-dark(#f1f1f1, #262626)`,"--color-neutral":`light-dark(#0000000F, #FFFFFF1A)`,"--color-overlay":`light-dark(#00000080, #000000CC)`,"--color-overlay-hover":`light-dark(#0000000D, #FFFFFF0D)`,"--color-overlay-pressed":`light-dark(#0000001A, #FFFFFF1A)`,"--color-text-primary":`light-dark(#171717, #fafafa)`,"--color-text-secondary":`light-dark(#525252, #a3a3a3)`,"--color-text-disabled":`light-dark(#a3a3a3, #525252)`,"--color-text-accent":`light-dark(#262626, #ebebeb)`,"--color-on-dark":`#ffffff`,"--color-on-light":`#171717`,"--color-on-accent":`light-dark(#ffffff, #171717)`,"--color-on-success":`light-dark(#ffffff, #171717)`,"--color-on-error":`light-dark(#ffffff, #171717)`,"--color-on-warning":`#171717`,"--color-icon-accent":`light-dark(#262626, #ebebeb)`,"--color-icon-primary":`light-dark(#171717, #fafafa)`,"--color-icon-secondary":`light-dark(#737373, #a3a3a3)`,"--color-icon-disabled":`light-dark(#a3a3a3, #525252)`,"--color-success":`light-dark(#007004, #9fe59b)`,"--color-error":`light-dark(#a50c25, #ffc6c1)`,"--color-warning":`light-dark(#745b00, #fdcf4f)`,"--color-success-muted":`light-dark(#c5e5c0, #84c9803D)`,"--color-error-muted":`light-dark(#facecb, #ff9e973D)`,"--color-warning-muted":`light-dark(#f8da9d, #deb4333D)`,"--color-border":`light-dark(#00000014, #FFFFFF1A)`,"--color-border-emphasized":`light-dark(#d4d4d4, #525252)`,"--color-skeleton":`light-dark(#ebebeb, #525252)`,"--color-shadow":`light-dark(#0000001A, #0000004D)`,"--color-tint-hover":`light-dark(black, white)`,"--color-background-red":`light-dark(#facecb, #ff9e973D)`,"--color-border-red":`light-dark(#e6bab8, #ff6f6c)`,"--color-icon-red":`light-dark(#89001a, #ff9e97)`,"--color-text-red":`light-dark(#89001a, #ffc6c1)`,"--color-background-orange":`light-dark(#fad0b5, #ffa2583D)`,"--color-border-orange":`light-dark(#e6bda2, #e2883e)`,"--color-icon-orange":`light-dark(#6e3500, #ffa258)`,"--color-text-orange":`light-dark(#6e3500, #ffc9a2)`,"--color-background-yellow":`light-dark(#f8da9d, #deb4333D)`,"--color-border-yellow":`light-dark(#e4c279, #c0990e)`,"--color-icon-yellow":`light-dark(#584400, #deb433)`,"--color-text-yellow":`light-dark(#584400, #fdcf4f)`,"--color-background-green":`light-dark(#c5e5c0, #84c9803D)`,"--color-border-green":`light-dark(#b2d1ac, #69ad67)`,"--color-icon-green":`light-dark(#0c5700, #84c980)`,"--color-text-green":`light-dark(#0c5700, #9fe59b)`,"--color-background-teal":`light-dark(#a5e3d6, #7ec6b83D)`,"--color-border-teal":`light-dark(#94d6c8, #63ab9d)`,"--color-icon-teal":`light-dark(#005348, #7ec6b8)`,"--color-text-teal":`light-dark(#005348, #99e2d3)`,"--color-background-cyan":`light-dark(#a3e0ef, #83c2d43D)`,"--color-border-cyan":`light-dark(#91d3e3, #67a7b8)`,"--color-icon-cyan":`light-dark(#00505f, #83c2d4)`,"--color-text-cyan":`light-dark(#00505f, #9edef0)`,"--color-background-blue":`light-dark(#c4ddfb, #9eb7ff3D)`,"--color-border-blue":`light-dark(#b1c9e7, #6d9cfe)`,"--color-icon-blue":`light-dark(#00458c, #9eb7ff)`,"--color-text-blue":`light-dark(#00458c, #c7d3ff)`,"--color-background-purple":`light-dark(#eccef3, #f297ff3D)`,"--color-border-purple":`light-dark(#d8bbdf, #dd74f0)`,"--color-icon-purple":`light-dark(#700084, #f297ff)`,"--color-text-purple":`light-dark(#700084, #fac1ff)`,"--color-background-pink":`light-dark(#fccadc, #ff99c33D)`,"--color-border-pink":`light-dark(#e7b7c8, #f273aa)`,"--color-icon-pink":`light-dark(#83004b, #ff99c3)`,"--color-text-pink":`light-dark(#83004b, #ffc3da)`,"--color-background-gray":`light-dark(#e5e5e5, var(--color-neutral))`,"--color-border-gray":`light-dark(#d4d4d4, #262626)`,"--color-icon-gray":`light-dark(#525252, #a3a3a3)`,"--color-text-gray":`light-dark(#262626, #e5e5e5)`,"--radius-none":`0px`,"--radius-inner":`0.375rem`,"--radius-element":`0.625rem`,"--radius-container":`0.75rem`,"--radius-page":`1.75rem`,"--radius-full":`9999px`,"--shadow-low":`0 2px 4px light-dark(oklch(0 0 0 / 5%), oklch(0 0 0 / 25%)), 0 4px 8px light-dark(oklch(0 0 0 / 10%), oklch(0 0 0 / 40%)), inset 0 0 0 1px light-dark(transparent, oklch(1 0 0 / 8%))`,"--shadow-med":`0 2px 4px light-dark(oklch(0 0 0 / 5%), oklch(0 0 0 / 35%)), 0 4px 12px light-dark(oklch(0 0 0 / 10%), oklch(0 0 0 / 50%)), inset 0 0 0 1px light-dark(transparent, oklch(1 0 0 / 12%))`,"--shadow-high":`0 4px 6px light-dark(oklch(0 0 0 / 10%), oklch(0 0 0 / 50%)), 0 12px 24px light-dark(oklch(0 0 0 / 15%), oklch(0 0 0 / 70%)), inset 0 0 0 1px light-dark(transparent, oklch(1 0 0 / 15%))`,"--shadow-inset-hover":`inset 0px 0px 0px 2px #0074e24D`,"--shadow-inset-selected":`inset 0px 0px 0px 2px #0074e280`,"--shadow-inset-success":`inset 0px 0px 0px 2px #1981004D`,"--shadow-inset-warning":`inset 0px 0px 0px 2px #ffce2f4D`,"--shadow-inset-error":`inset 0px 0px 0px 2px #e33f4a4D`},components:{heading:{"level:1":{fontFamily:`var(--font-family-heading)`,fontSize:`var(--text-heading-1-size)`,fontWeight:`var(--text-heading-1-weight)`,lineHeight:`var(--text-heading-1-leading)`},"level:2":{fontFamily:`var(--font-family-heading)`,fontSize:`var(--text-heading-2-size)`,fontWeight:`var(--text-heading-2-weight)`,lineHeight:`var(--text-heading-2-leading)`},"level:3":{fontFamily:`var(--font-family-heading)`,fontSize:`var(--text-heading-3-size)`,fontWeight:`var(--text-heading-3-weight)`,lineHeight:`var(--text-heading-3-leading)`},"level:4":{fontFamily:`var(--font-family-heading)`,fontSize:`var(--text-heading-4-size)`,fontWeight:`var(--text-heading-4-weight)`,lineHeight:`var(--text-heading-4-leading)`},"level:5":{fontFamily:`var(--font-family-heading)`,fontSize:`var(--text-heading-5-size)`,fontWeight:`var(--text-heading-5-weight)`,lineHeight:`var(--text-heading-5-leading)`},"level:6":{fontFamily:`var(--font-family-heading)`,fontSize:`var(--text-heading-6-size)`,fontWeight:`var(--text-heading-6-weight)`,lineHeight:`var(--text-heading-6-leading)`},"type:display-1":{fontFamily:`var(--font-family-heading)`,fontSize:`var(--text-display-1-size)`,lineHeight:`var(--text-display-1-leading)`},"type:display-2":{fontFamily:`var(--font-family-heading)`,fontSize:`var(--text-display-2-size)`,lineHeight:`var(--text-display-2-leading)`},"type:display-3":{fontFamily:`var(--font-family-heading)`,fontSize:`var(--text-display-3-size)`,lineHeight:`var(--text-display-3-leading)`}},text:{"type:body":{fontFamily:`var(--font-family-body)`,fontSize:`var(--text-body-size)`,lineHeight:`var(--text-body-leading)`},"type:large":{fontFamily:`var(--font-family-body)`,fontSize:`var(--text-large-size)`,lineHeight:`var(--text-large-leading)`},"type:label":{fontFamily:`var(--font-family-body)`,fontSize:`var(--text-label-size)`,lineHeight:`var(--text-label-leading)`},"type:code":{fontFamily:`var(--font-family-code)`,fontSize:`var(--text-code-size)`,lineHeight:`var(--text-code-leading)`},"type:supporting":{fontFamily:`var(--font-family-body)`,fontSize:`var(--text-supporting-size)`,lineHeight:`var(--text-supporting-leading)`},"type:display-1":{fontFamily:`var(--font-family-heading)`,fontSize:`var(--text-display-1-size)`,lineHeight:`var(--text-display-1-leading)`},"type:display-2":{fontFamily:`var(--font-family-heading)`,fontSize:`var(--text-display-2-size)`,lineHeight:`var(--text-display-2-leading)`},"type:display-3":{fontFamily:`var(--font-family-heading)`,fontSize:`var(--text-display-3-size)`,lineHeight:`var(--text-display-3-leading)`}},button:{"variant:destructive":{backgroundColor:`var(--color-error-muted)`,color:`var(--color-error)`}},badge:{"variant:info":{backgroundColor:`light-dark(#0074e2, #6d9cfe)`,color:`light-dark(#ffffff, #171717)`},"variant:neutral":{backgroundColor:`var(--color-background-gray)`,color:`var(--color-text-gray)`},"variant:success":{backgroundColor:`light-dark(#198100, #64af4c)`,color:`light-dark(#ffffff, #171717)`},"variant:warning":{backgroundColor:`#ffce2f`,color:`#171717`},"variant:error":{backgroundColor:`light-dark(#c9303a, #ff705d)`,color:`light-dark(#ffffff, #171717)`},"variant:red":{backgroundColor:`var(--color-background-red)`,color:`var(--color-text-red)`},"variant:orange":{backgroundColor:`var(--color-background-orange)`,color:`var(--color-text-orange)`},"variant:yellow":{backgroundColor:`var(--color-background-yellow)`,color:`var(--color-text-yellow)`},"variant:green":{backgroundColor:`var(--color-background-green)`,color:`var(--color-text-green)`},"variant:teal":{backgroundColor:`var(--color-background-teal)`,color:`var(--color-text-teal)`},"variant:cyan":{backgroundColor:`var(--color-background-cyan)`,color:`var(--color-text-cyan)`},"variant:blue":{backgroundColor:`var(--color-background-blue)`,color:`var(--color-text-blue)`},"variant:purple":{backgroundColor:`var(--color-background-purple)`,color:`var(--color-text-purple)`},"variant:pink":{backgroundColor:`var(--color-background-pink)`,color:`var(--color-text-pink)`},"variant:gray":{backgroundColor:`var(--color-background-gray)`,color:`var(--color-text-gray)`}},statusdot:{"variant:success":{backgroundColor:`light-dark(#198100, #64af4c)`},"variant:warning":{backgroundColor:`#ffce2f`},"variant:error":{backgroundColor:`light-dark(#c9303a, #ff705d)`},"variant:accent":{backgroundColor:`light-dark(#0074e2, #6d9cfe)`}},banner:{"status:info":{"--color-accent-muted":`var(--color-background-blue)`,"--color-text-primary":`var(--color-text-blue)`,"--color-text-secondary":`var(--color-text-blue)`,"--color-accent":`var(--color-text-blue)`},"status:success":{"--color-text-primary":`var(--color-text-green)`,"--color-text-secondary":`var(--color-text-green)`,"--color-success":`var(--color-text-green)`},"status:warning":{"--color-text-primary":`var(--color-text-yellow)`,"--color-text-secondary":`var(--color-text-yellow)`,"--color-warning":`var(--color-text-yellow)`},"status:error":{"--color-text-primary":`var(--color-text-red)`,"--color-text-secondary":`var(--color-text-red)`,"--color-error":`var(--color-text-red)`}},switch:{base:{"--color-background-gray":`var(--color-border-emphasized)`}},progressbar:{base:{"--color-background-muted":`var(--color-border-emphasized)`},"variant:accent":{"--color-accent":`#0074e2`},"variant:success":{"--color-success":`#198100`},"variant:warning":{"--color-warning":`#ffce2f`},"variant:error":{"--color-error":`#c9303a`}},card:{base:{padding:`var(--spacing-3)`}},section:{base:{padding:`var(--spacing-3)`}}},__onDark:{tokens:{"color-scheme":`dark`,"--color-text-primary":`var(--color-on-dark)`,"--color-icon-primary":`var(--color-on-dark)`,"--color-accent":`var(--color-on-dark)`}},__onLight:{tokens:{"color-scheme":`light`,"--color-text-primary":`var(--color-on-light)`,"--color-icon-primary":`var(--color-on-light)`,"--color-accent":`var(--color-on-light)`}},icons:{close:(0,V.jsx)(Re,{..._S}),chevronDown:(0,V.jsx)(te,{..._S}),chevronLeft:(0,V.jsx)(ne,{..._S}),chevronRight:(0,V.jsx)(re,{..._S}),chevronsLeft:(0,V.jsx)(R,{..._S}),chevronsRight:(0,V.jsx)(z,{..._S}),check:(0,V.jsx)(L,{..._S}),success:(0,V.jsx)(ie,{..._S}),error:(0,V.jsx)(ae,{..._S}),warning:(0,V.jsx)(Fe,{..._S}),info:(0,V.jsx)(ve,{..._S}),calendar:(0,V.jsx)(F,{..._S}),clock:(0,V.jsx)(se,{..._S}),externalLink:(0,V.jsx)(de,{..._S}),menu:(0,V.jsx)(be,{..._S}),moreHorizontal:(0,V.jsx)(ue,{..._S}),search:(0,V.jsx)(De,{..._S}),arrowUp:(0,V.jsx)(N,{..._S}),arrowDown:(0,V.jsx)(j,{..._S}),arrowsUpDown:(0,V.jsx)(M,{..._S}),funnel:(0,V.jsx)(ge,{..._S}),eyeSlash:(0,V.jsx)(fe,{..._S}),viewColumns:(0,V.jsx)(ce,{..._S}),copy:(0,V.jsx)(le,{..._S}),checkDouble:(0,V.jsx)(I,{..._S}),wrench:(0,V.jsx)(Le,{..._S}),stop:(0,V.jsx)(Me,{..._S}),microphone:(0,V.jsx)(xe,{..._S})}};function yS({children:e}){let t=fn(cS,e=>e.snapshot?.preferences?.theme)??`system`,[n,r]=(0,w.useState)(()=>window.matchMedia?.(`(prefers-color-scheme: dark)`).matches??!1);(0,w.useEffect)(()=>{let e=window.matchMedia?.(`(prefers-color-scheme: dark)`);if(!e)return;let t=()=>r(e.matches);return t(),e.addEventListener(`change`,t),()=>e.removeEventListener(`change`,t)},[]);let i=t===`dark`||t===`system`&&n?`dark`:`light`;return(0,w.useEffect)(()=>{document.documentElement.dataset.theme=i},[i]),(0,V.jsx)(cn,{i18n:Pt,children:(0,V.jsx)(gS,{theme:vS,mode:i,children:e})})}var bS=document.getElementById(`root`);if(!bS)throw Error(`OpenPI Web root is missing`);(0,ze.createRoot)(bS).render((0,V.jsx)(w.StrictMode,{children:(0,V.jsx)(yS,{children:(0,V.jsx)(lS,{})})})); \ No newline at end of file +`,t),i=-1;if(n!==-1&&r!==-1?i=n20?`${e.slice(0,20)}…`:e}"`,{type:`unknown-field`,field:e,value:t,line:n}))}}function T(){u!==void 0&&a?.(u),f>0&&i?.({id:u,event:p,data:d}),u=void 0,d=``,f=0,p=void 0}function E(e={}){if(e.consume&&s.length>0){let e=s.join(``);C(e,0,e.length)}l=!0,u=void 0,d=``,f=0,p=void 0,s.length=0,c=0,m=!1,h=!1,g=!1}return{feed:_,reset:E}}function Jx(e,t,n){return n===100&&e.charCodeAt(t+1)===97&&e.charCodeAt(t+2)===116&&e.charCodeAt(t+3)===97&&e.charCodeAt(t+4)===58}function Yx(e,t,n){return n===101&&e.charCodeAt(t+1)===118&&e.charCodeAt(t+2)===101&&e.charCodeAt(t+3)===110&&e.charCodeAt(t+4)===116&&e.charCodeAt(t+5)===58}function Xx(e,t){let n=1;for(;nt.abort();e.signal.addEventListener(`abort`,n,{once:!0}),e.signal.aborted&&n();let r=window.setTimeout(n,45e3),i=await fetch(`/events?cursor=${e.cursor}`,{headers:e.client.headers(),signal:t.signal}).finally(()=>{window.clearTimeout(r),e.signal.removeEventListener(`abort`,n)});if(i.status===409)throw new Zx(`event replay expired`);if(!i.ok||!i.body)throw Error(`event connection failed`);e.onConnected();let a=e.cursor,o=0,s=qx({onComment(t){t.trim()===`heartbeat`&&++o>=4&&(o=0,e.onHeartbeat?.())},onEvent(t){let n=JSON.parse(t.data);if(!Number.isSafeInteger(n.sequence))throw new Zx(`invalid event cursor`);if(!(n.sequence<=a)){if(n.sequence!==a+1)throw new Zx(`event cursor gap`);if(a=n.sequence,n.type===`state_invalidated`)throw new Zx(`state invalidated`);e.onEvent(n)}}}),c=i.body.getReader(),l=new TextDecoder;try{for(;!e.signal.aborted;){let t,n,r=c.read(),i=new Promise((r,i)=>{n=()=>i(Error(`event stream aborted`)),e.signal.addEventListener(`abort`,n,{once:!0}),e.signal.aborted&&n(),t=window.setTimeout(()=>i(Error(`event stream stalled`)),45e3)}),{done:a,value:o}=await Promise.race([r,i]).finally(()=>{window.clearTimeout(t),n&&e.signal.removeEventListener(`abort`,n)});if(a)throw Error(`event connection closed`);s.feed(l.decode(o,{stream:!0}))}}finally{await c.cancel().catch(()=>void 0)}}function $x(e,t,n){if([`session_start`,`session_switched`,`session_created`].includes(t))return[];if([`agent_settled`,`turn_settled`].includes(t))return e.map(e=>e.state===`running`?{...e,state:`unknown`}:e);if(!t.startsWith(`tool_execution_`))return e;let r=n.toolCallId;if(typeof r!=`string`||r.length>500)return e;let i=e.find(e=>e.call.id===r);if(i&&i.state!==`running`&&(i.state!==`unknown`||t!==`tool_execution_end`))return e;let a=Uu(n.call),o=a.type===`toolCall`&&typeof a.name==`string`&&typeof a.arguments==`string`?a:i?.call;if(!o||o.evidenceTruncated&&o.id?.includes(`[truncated]`))return e;let s=Uu(n.result),c={call:o,result:typeof s.content==`string`?s:i?.result,state:t===`tool_execution_end`?n.isError===!0?`failed`:`returned`:`running`},l=[...e.filter(e=>e.call.id!==r),c].slice(-32);for(;l.length>0&&new TextEncoder().encode(JSON.stringify(l)).byteLength>524288;)l.shift();return l}var eS=`openpi.collapsed-workspaces`,tS=`openpi.sidebar-collapsed`,nS=new Set([`agent_start`,`turn_started`,`turn_settled`,`agent_settled`,`prompt_settled`,`message_end`,`tool_execution_end`,`session_start`,`session_switched`,`session_progress`,`prompt_failed`,`model_select`,`workspace_imported`,`workspace_removed`,`workspace_renamed`,`session_renamed`,`session_archived`,`session_unarchived`,`session_created`,`prompt_accepted`,`runtime_changed`]);function rS(e){try{let t=JSON.parse(window.sessionStorage.getItem(e)||`[]`);return new Set(Array.isArray(t)?t.filter(e=>typeof e==`string`):[])}catch{return new Set}}function iS(e){try{return window.sessionStorage.getItem(e)===`true`}catch{return!1}}function aS(e,t){try{window.sessionStorage.setItem(e,JSON.stringify(t))}catch{}}function oS(e,t){return t.aborted?Promise.resolve():new Promise(n=>{let r=()=>{window.clearTimeout(i),t.removeEventListener(`abort`,r),n()},i=window.setTimeout(r,e);t.addEventListener(`abort`,r,{once:!0})})}function sS(e=new ed,t={}){let n=t.consumeEvents??Qx,r=0,i=0,a=0,o=null,s=null,c=null,l=null,u=Promise.resolve(),d=null,f=!1,p=!1,m=null,h=null,g=0,_=null,v=0,y=!1,b=0,x=0,S=null,C=-1,w=null,T=0,E=new Set,D=new Set,O=(e,t)=>{if(typeof t==`string`)for(e.add(t);e.size>32;){let t=e.values().next().value;t&&e.delete(t)}},k=()=>({activeTurn:null,turnCancellationPending:!1,turnTerminalStatus:null,pendingFollowUpsReceipt:null,liveMessages:[],liveRunning:!1,livePhase:`idle`,liveRetry:null,thinkingStarts:{},thinkingDurations:{}}),A=()=>(g++,h?.abort(),h=null,{modelSearch:{query:``,status:`idle`,models:[],totalMatches:0,matchesOmitted:0,error:null}}),j=()=>({sessionId:null,status:`idle`,commands:[],totalAvailable:0,commandsOmitted:0,error:null}),M=(e,t)=>({liveRunning:t===`running`||!e,livePhase:t===`running`?`running`:e?`idle`:`preparing`});return un((t,N)=>{let P=e=>{t({notice:e instanceof Error?e.message:String(e)})},ee=e=>{let t=N().snapshot;return e.epoch===r&&N().selectedWorkspace===e.workspacePath&&t?.currentSessionId===e.sessionId&&t.selectedSession?.id===e.sessionId&&t.selectedSession.cwd===e.workspacePath&&(!e.sessionPath||t.selectedSession.path===e.sessionPath)},F=()=>{T++,w?.abort(),w=null,t({commandDiscovery:j()})},I=async(n,i,a)=>{if(N().modelSelectionPending)return!1;t({modelSelectionPending:!0});try{let o=await e.selectModel(n.provider,n.id,a);if(i!==r||a!==N().snapshot?.selectedSession?.id)return!1;if(o.provider!==n.provider||o.id!==n.id||!o.current)throw Error(`Model selection was not confirmed. Please select a model again.`);if(!await N().actions.refreshSnapshot({epoch:i})||i!==r||a!==N().snapshot?.selectedSession?.id)return!1;let s=N().snapshot?.models.find(e=>e.current);if(s?.provider!==n.provider||s.id!==n.id)throw Error(`The Session model changed. Please select a model again.`);return t({draftModel:null,notice:null}),!0}catch(e){return i===r&&a===N().snapshot?.selectedSession?.id&&P(e),!1}finally{i===r&&t({modelSelectionPending:!1})}},L=(e=160)=>{if(f){p=!0;return}d===null&&(d=window.setTimeout(async()=>{d=null,f=!0;try{await N().actions.refreshSnapshot()}finally{f=!1,p&&(p=!1,L())}},e))},te=()=>{S=null,x=0,C=-1},ne=()=>{let e=N().snapshot;if(!e)return;S&&C!==r&&te();let n=e.thinking;if(n){if(!S||n.revision>=x){S=n,x=n.revision,C=r;return}n!==S&&t({snapshot:{...e,thinking:S}})}},re=e=>{S&&C!==r&&te(),e&&e.revision>=x&&(S=e,x=e.revision,C=r),ne()},R=()=>{_=null,v++,y=!1,t({thinkingPendingLevel:null})},z=async()=>{let t=r,n=N().snapshot?.selectedSession?.id;if(n)try{let i=await e.thinking(n,new AbortController().signal);if(t!==r||n!==N().snapshot?.selectedSession?.id)return;re(i)}catch{}},ie=async()=>{if(y)return;y=!0;let n=++b;try{for(;_!==null;){let n=_,i=v,a=r,o=N().snapshot?.selectedSession?.id;if(!o||N().modelSelectionPending){R();return}if(N().snapshot?.thinking?.level===n){i===v&&(_=null,t({thinkingPendingLevel:null}));continue}let s;try{s=await e.setThinkingLevel(o,n)}catch(e){if(a!==r)return;if(i!==v)continue;R(),P(e),z();return}if(a!==r||o!==N().snapshot?.selectedSession?.id)return;if(i===v){if(s.level!==n){R(),P(Error(Pt.t(`thinkingNotConfirmed`)));return}re(s),_=null,t({thinkingPendingLevel:null}),L();return}}}finally{n===b&&(y=!1)}},ae=e=>{let n=N(),i=e.detail??{},a=i.sessionId,l=[`session_start`,`session_switched`,`session_created`].includes(e.type);if(n.cursor!==null&&e.sequence<=n.cursor)return;t({cursor:e.sequence});let u=n.promptAdmissionRecovery,d=u?.commandId;if(typeof i.commandId==`string`&&i.commandId===d&&[`prompt_accepted`,`prompt_failed`,`prompt_settled`,`turn_started`,`turn_settled`].includes(e.type)){let r=e.type!==`prompt_failed`;t({promptAdmissionRecovery:null,promptAdmissionResolution:r&&u?{commandId:u.commandId,content:u.content}:n.promptAdmissionResolution,liveMessages:r&&u&&!n.liveMessages.some(e=>e.key===u.optimisticKey)?[...n.liveMessages,{key:u.optimisticKey,message:{role:`user`,content:u.content}}].slice(-8):n.liveMessages})}if(e.type===`runtime_changed`&&t(A()),e.type===`runtime_changed`&&F(),n.sessionSwitching&&!l){L();return}if(typeof a==`string`&&a!==n.snapshot?.currentSessionId&&!l){L();return}if(n.snapshot){let r=$x(n.snapshot.runtime.liveTools??[],e.type,i);t({snapshot:{...n.snapshot,runtime:{...n.snapshot.runtime,liveTools:r}}})}if(l){let l=i.commandId;if(typeof l==`string`&&D.has(l)){L();return}let d=i.sessionPath,f=n.snapshot?.sessions.some(e=>e.path===d),p=!1;if(c?.kind===`select`?p=c.expectedPath===d:(c?.kind===`create`&&c.commandId===l&&e.type===`session_switched`&&typeof a==`string`&&(!c.expectedSessionId||c.expectedSessionId===a)&&(typeof d!=`string`||!f)||c?.kind===`create`&&c.commandId===l&&e.type===`session_created`&&typeof a==`string`&&(!c.expectedSessionId||a===c.expectedSessionId))&&(p=!0),p&&c?.epoch!==r)return;if(p)t(k());else{F();let e=++r;R(),o=null,s=null,t({...k(),promptAdmissionPending:!1,promptAdmissionRecovery:null,promptAdmissionResolution:u?{commandId:u.commandId,content:u.content}:n.promptAdmissionResolution,selectedPath:typeof d==`string`?d:null,draftModel:n.workspaceDraft?n.draftModel:null,modelSelectionPending:!1,sessionSwitching:!0}),N().actions.refreshSnapshot({epoch:e}).then(n=>{e===r&&t({selectedPath:n?N().selectedPath:null,sessionSwitching:!1})})}}else if(e.type===`prompt_accepted`){let e=E.has(String(i.commandId??``));t({...M(e,n.livePhase),liveRetry:null,pendingFollowUpsReceipt:Number.isInteger(i.pendingFollowUps)?Number(i.pendingFollowUps):n.pendingFollowUpsReceipt})}else if(e.type===`turn_started`)t({activeTurn:{sessionId:String(i.sessionId),commandId:String(i.commandId),epoch:Number(i.epoch)},liveRunning:!0,livePhase:`running`,liveRetry:null,turnTerminalStatus:null});else if(e.type===`agent_start`)t({...i.activeTurn?{activeTurn:i.activeTurn}:{},liveRunning:!0,livePhase:`running`,liveRetry:null});else if(e.type===`turn_settled`){O(E,i.commandId);let e=n.activeTurn;e?.sessionId===i.sessionId&&e?.commandId===i.commandId&&e?.epoch===i.epoch&&t({activeTurn:null,liveRunning:!1,livePhase:`idle`,liveRetry:null,turnTerminalStatus:typeof i.outcome==`string`?i.outcome:null})}else if(e.type===`agent_settled`)t({pendingFollowUpsReceipt:null,...n.activeTurn?{}:{liveRunning:!1,livePhase:`idle`,liveRetry:null}});else if(e.type===`prompt_settled`)O(E,i.commandId),n.livePhase!==`running`&&t({liveRunning:!1,livePhase:`idle`,liveRetry:null});else if(i.message&&typeof i.message==`object`){let r=i.message,a=n.liveMessages;r.role===`user`&&(a=a.filter(e=>!e.key.startsWith(`optimistic-`)||e.message.content!==r.content));let o=typeof i.messageKey==`string`?i.messageKey:`${r.role||`message`}-${e.sequence}`,s={key:o,message:r},c=a.findIndex(e=>e.key===o);a=c>=0?a.map((e,t)=>t===c?s:e):[...a,s].slice(-8);let l={...n.thinkingStarts},u={...n.thinkingDurations};r.parts?.some(e=>e.type===`thinking`)&&(l[o]??=Date.now(),e.type===`message_end`&&(u[o]=Date.now()-l[o])),t({liveMessages:a,thinkingDurations:u,thinkingStarts:l})}if(e.type===`prompt_failed`&&(O(E,i.commandId),t({liveMessages:N().liveMessages.filter(e=>!e.key.startsWith(`optimistic-`)),liveRunning:!1,livePhase:`idle`,liveRetry:null,notice:typeof i.error==`string`?i.error:`Prompt failed`})),e.type===`auto_retry_start`&&t({liveRunning:!0,livePhase:`running`,liveRetry:{attempt:Number(i.attempt)||0,maxAttempts:Number(i.maxAttempts)||0}}),e.type===`thinking_level_changed`){let t=N().snapshot?.thinking;t&&typeof i.level==`string`&&re({...t,level:i.level,revision:e.sequence})}nS.has(e.type)&&L()},oe=async r=>{let i=500;for(;!r.aborted;){let a=!1;try{if(N().cursor===null){if(a=!0,!await N().actions.refreshSnapshot({resetCursor:!0}))throw Error(`snapshot unavailable`);a=!1}if(r.aborted)return;await n({client:e,cursor:N().cursor??0,onConnected:()=>{i=500,t({connection:`connected`})},onEvent:ae,onHeartbeat:()=>L(0),signal:r})}catch(e){if(r.aborted)return;t({connection:`reconnecting`}),!a&&await N().actions.refreshSnapshot({resetCursor:!0})||t(k()),await oS(i,r),i=Math.min(i*2,5e3),e instanceof SyntaxError&&t({notice:`Invalid event data`})}}},se={start(){m||(m=new AbortController,oe(m.signal))},stop(){m?.abort(),m=null,d!==null&&window.clearTimeout(d),d=null,R(),F()},async refreshSnapshot(n={}){let a=n.epoch??r,o=++i,c=N().selectedPath;try{let l=await e.snapshot(c);if(a!==r||o!==i)return!1;let u=typeof l.currentSessionId==`string`,d=u?l.sessions.find(e=>e.id===l.currentSessionId):void 0,f=u?l.selectedSession?.id===l.currentSessionId:l.selectedSession===void 0,p=!c||l.sessions.some(e=>e.path===c),m=!c||l.selectedSession?.path===c;if(!p||!m||!f)return t({selectedPath:null}),!n.canonicalRetry&&se.refreshSnapshot({...n,canonicalRetry:!0,epoch:a});let h=l.selectedSession?.cwd,g=l.workspaces.find(e=>e.current)?.path,_=l.workspaces.some(e=>e.path===N().selectedWorkspace)?N().selectedWorkspace:void 0,v=N().workspaceDraft?N().selectedWorkspace:l.workspaces.some(e=>e.path===h)?h:g??_??null,y=n.resetCursor;return y&&te(),N().snapshot?.currentSessionId!==l.currentSessionId&&F(),t({...y?k():{},connection:N().connection===`connecting`?`connecting`:N().connection,cursor:y||N().cursor===null?l.cursor:Math.max(N().cursor??0,l.cursor),activeTurn:l.runtime.activeTurn??null,livePhase:l.runtime.status===`running`?`running`:!N().promptAdmissionPending&&!s?`idle`:N().livePhase,liveRetry:l.runtime.status!==`running`&&!N().promptAdmissionPending&&!s?null:N().liveRetry,liveRunning:l.runtime.status===`running`?!0:!N().promptAdmissionPending&&!s?!1:N().liveRunning,selectedPath:d?.path??l.selectedSession?.path??null,selectedWorkspace:v,snapshot:l,...A()}),re(),!0}catch(e){return a!==r||o!==i?!1:(t({connection:`unavailable`}),P(e),!1)}},async chooseWorkspace(){let t=r;try{let n=await e.chooseWorkspace();if(t!==r||n.cancelled||!n.path)return;se.setWorkspace(n.path),await se.refreshSnapshot()}catch(e){t===r&&P(e)}},setWorkspace(e){let n=N();e===n.selectedWorkspace&&!n.sessionSwitching||(++r,l=null,R(),F(),o=null,s=null,t({...k(),...A(),selectedWorkspace:e,workspaceDraft:!0,sessionSwitching:!1,modelSelectionPending:!1,promptAdmissionPending:!1,promptAdmissionRecovery:null,promptAdmissionResolution:n.promptAdmissionRecovery?{commandId:n.promptAdmissionRecovery.commandId,content:n.promptAdmissionRecovery.content}:n.promptAdmissionResolution,notice:null}))},async renameWorkspace(t,n){try{await e.renameWorkspace(t,n),await se.refreshSnapshot()}catch(e){throw P(e),e}},async removeWorkspace(n){try{await e.removeWorkspace(n),N().selectedWorkspace===n&&F(),t({selectedPath:null,selectedWorkspace:N().selectedWorkspace===n?null:N().selectedWorkspace}),await se.refreshSnapshot()}catch(e){P(e)}},async createSession(n){if(!n||N().modelSelectionPending)return null;let i=N(),a=++r;R(),F();let d=(l?.workspacePath===n?l.commandId:void 0)??globalThis.crypto?.randomUUID?.()??`web-create-${Date.now()}-${a}`;l={workspacePath:n,commandId:d},o=null,s=null,t({...k(),...A(),mobileSidebarOpen:!1,promptAdmissionPending:!1,promptAdmissionRecovery:null,promptAdmissionResolution:i.promptAdmissionRecovery?{commandId:i.promptAdmissionRecovery.commandId,content:i.promptAdmissionRecovery.content}:i.promptAdmissionResolution,selectedPath:null,selectedWorkspace:n,workspaceDraft:!0,sessionSwitching:!0});let f=null,p=u.then(async()=>{if(a===r){c={commandId:d,epoch:a,expectedPath:null,kind:`create`};try{let i=await e.createSession(n,d);if(a!==r)return;if(i.cancelled||i.commandId!==d||typeof i.sessionId!=`string`||!i.sessionId||i.sessionId.length>128||i.sessionPath!==void 0&&!i.sessionPath)throw Error(`Session creation did not return a valid target identity.`);let o={epoch:a,sessionId:i.sessionId,sessionPath:i.sessionPath??null,workspacePath:n};c?.epoch===a&&(c.expectedSessionId=o.sessionId,c.expectedPath=o.sessionPath),t({selectedPath:o.sessionPath});let s=await se.refreshSnapshot({epoch:a});if(!s&&a===r&&(s=await se.refreshSnapshot({epoch:a})),a!==r)return;if(!s)throw Error(`The created Session could not be confirmed. Please try again.`);if(!ee(o)){l=null,P(Error(`The created Session is no longer active in the selected workspace. Please try again.`));return}t({workspaceDraft:!1,notice:null});let u=N().draftModel;if(u&&!await I(u,a,o.sessionId))return;if(N().workspaceDraft||!ee(o)){l=null,P(Error(`The created Session is no longer active in the selected workspace. Please try again.`));return}f=o,l=null}catch(e){if(a!==r)return;t({selectedPath:null}),P(e),await se.refreshSnapshot({epoch:a})}finally{O(D,d),c?.epoch===a&&(c=null),a===r&&t({sessionSwitching:!1})}}});return u=p.catch(()=>void 0),await p,a===r?f:null},async selectSession(n){if(!n)return;l=null;let i=N();F(),t({...A(),workspaceDraft:!1,draftModel:null,modelSelectionPending:!1});let a=++r;R(),o=null,s=null,t({...k(),mobileSidebarOpen:!1,promptAdmissionPending:!1,promptAdmissionRecovery:null,promptAdmissionResolution:i.promptAdmissionRecovery?{commandId:i.promptAdmissionRecovery.commandId,content:i.promptAdmissionRecovery.content}:i.promptAdmissionResolution,selectedPath:n,sessionSwitching:!0});let d=u.then(async()=>{if(a===r){c={epoch:a,expectedPath:n,kind:`select`};try{if(await e.selectSession(n),a!==r)return;await se.refreshSnapshot({epoch:a})||t({selectedPath:null})}catch(e){if(a!==r)return;t({selectedPath:null}),P(e),await se.refreshSnapshot({epoch:a})}finally{c?.epoch===a&&(c=null),a===r&&t({sessionSwitching:!1})}}});u=d.catch(()=>void 0),await d},async renameSession(t,n){try{await e.renameSession(t,n),await se.refreshSnapshot()}catch(e){throw P(e),e}},async archiveSession(t){try{await e.archiveSession(t),await se.refreshSnapshot()}catch(e){P(e)}},async unarchiveSession(t){let n=r;try{return await e.unarchiveSession(t),n!==r||await se.refreshSnapshot({epoch:n})}catch(e){return n===r&&P(e),!1}},async selectModel(e){let[n,...i]=e.split(`/`),a=i.join(`/`),o=N();if(!n||!a||o.sessionSwitching||o.modelSelectionPending||o.promptAdmissionPending||!o.workspaceDraft&&(o.liveRunning||o.snapshot?.runtime.status===`running`))return;let s=o.snapshot?.selectedSession?.id;if(o.workspaceDraft||!s&&!o.snapshot?.currentSessionId){let e=[...o.snapshot?.models??[],...o.modelSearch.models].find(e=>e.provider===n&&e.id===a);e&&t({draftModel:e,notice:null});return}!s||s!==o.snapshot?.currentSessionId||(R(),await I({provider:n,id:a},r,s))},async searchModels(n){let a=n.trim();if(!a){t(A());return}h?.abort();let o=new AbortController;h=o;let s=++g,c=r,l=i,u=N().snapshot?.currentSessionId;t({modelSearch:{query:a,status:`loading`,models:[],totalMatches:0,matchesOmitted:0,error:null}});try{let n=await e.searchModels(a,u,o.signal);if(o.signal.aborted||c!==r||l!==i||s!==g)return;t({modelSearch:{query:a,status:`ready`,models:n.models,totalMatches:n.totalMatches,matchesOmitted:n.truncation.matchesOmitted,error:null}})}catch(e){if(o.signal.aborted||c!==r||l!==i||s!==g)return;t({modelSearch:{query:a,status:`error`,models:[],totalMatches:0,matchesOmitted:0,error:e instanceof Error?e.message:String(e)}})}finally{h===o&&(h=null)}},clearModelSearch(){t(A())},selectThinking(e){let n=N(),r=n.snapshot?.thinking,i=n.snapshot?.selectedSession?.id;!e||!r?.supported||n.sessionSwitching||n.modelSelectionPending||n.workspaceDraft||!i||i!==n.snapshot?.currentSessionId||n.liveRunning||n.snapshot?.runtime.status===`running`||e!==(n.thinkingPendingLevel??r.level)&&(_=e,v++,t({thinkingPendingLevel:e}),ie())},async cancelActiveTurn(){let n=N().activeTurn??N().snapshot?.runtime.activeTurn;if(!n||N().turnCancellationPending||N().sessionSwitching)return;let i=r;t({turnCancellationPending:!0});try{await e.cancelActiveTurn(n)}catch(e){if(i!==r)return;await se.refreshSnapshot({epoch:i}),i===r&&P(e)}finally{i===r&&t({turnCancellationPending:!1})}},async sendPrompt(n){let i=n.trim(),c=N(),l=c.promptAdmissionRecovery,u=l?.phase===`submitting`?l:null,d=c.selectedWorkspace;if(!d||!i||c.sessionSwitching||c.promptAdmissionPending||l&&!u||c.promptAdmissionResolution||c.modelSelectionPending||c.thinkingPendingLevel!==null)return!1;let f=N().workspaceDraft||!N().snapshot?.selectedSession?.id,p=f?await se.createSession(d):null;if(f&&!p)return N().notice||t({notice:`The active Session changed before the first message was sent. Your message was not sent.`}),!1;if(f&&N().draftModel)return!1;let m=N().snapshot?.selectedSession,h=p??(m?{epoch:r,sessionId:m.id,sessionPath:m.path,workspacePath:d}:null);if(!h||N().workspaceDraft||!ee(h)||N().sessionSwitching||N().promptAdmissionPending||u&&(N().promptAdmissionRecovery?.commandId!==u.commandId||N().promptAdmissionRecovery?.phase!==`submitting`))return!1;let{epoch:g,sessionId:_}=h,v=N().draftModel;if(v&&!await I(v,g,_)||N().workspaceDraft||!ee(h)||u&&(N().promptAdmissionRecovery?.commandId!==u.commandId||N().promptAdmissionRecovery?.phase!==`submitting`))return!1;let y=++a,b=s?.sessionId===_&&s.content===i,x=b?s.commandId:globalThis.crypto?.randomUUID?.()??`web-prompt-${Date.now()}-${y}`,S=b?s.optimisticKey:`optimistic-${x}`;s={sessionId:_,content:i,commandId:x,optimisticKey:S},o=y,t({liveMessages:b?N().liveMessages:[...N().liveMessages,{key:S,message:{role:`user`,content:i}}].slice(-8),notice:null,pendingFollowUpsReceipt:null,turnTerminalStatus:null,promptAdmissionPending:!0,scrollToBottom:N().scrollToBottom+1});try{let n=await e.prompt(_,i,x,b);if(g!==r||o!==y)return!1;let a=E.has(n.id);return s?.commandId===x&&(s=null),t({...M(a,N().livePhase),pendingFollowUpsReceipt:n.pendingFollowUps??null,...u&&N().promptAdmissionRecovery?.commandId===u.commandId?{promptAdmissionRecovery:null}:{}}),L(120),!0}catch(e){if(g!==r||o!==y)return!1;if(e instanceof Qu&&e.code===`COMMAND_ADMISSION_UNKNOWN`&&s?.commandId===x){let e=s;s=null,t({liveRunning:!1,livePhase:`idle`,liveRetry:null,notice:null,promptAdmissionPending:!1,promptAdmissionRecovery:{...e,phase:`checking`}});let n=await se.refreshSnapshot({resetCursor:!0,epoch:g}),r=N().promptAdmissionRecovery;return r?.commandId===x&&t({promptAdmissionRecovery:{...r,phase:n?`ready`:`verification-failed`}}),!1}return e instanceof Qu&&[`WORKSPACE_REQUIRED`,`SESSION_CONFLICT`,`PROMPT_REJECTED`,`COMMAND_CONFLICT`,`PROMPT_ADMISSION_CAPACITY`].includes(e.code??``)?(s?.commandId===x&&(s=null),t({liveMessages:N().liveMessages.filter(e=>e.key!==S),livePhase:`idle`,liveRetry:null,liveRunning:!1}),P(e),!1):(t({liveRunning:!0,livePhase:N().livePhase===`running`?`running`:`preparing`,liveRetry:null}),P(e),!1)}finally{g===r&&o===y&&(o=null,t({promptAdmissionPending:!1}))}},async checkPromptAdmissionRecovery(){let e=N().promptAdmissionRecovery;if(!e||e.phase!==`verification-failed`)return;let n=r;t({notice:null,promptAdmissionRecovery:{...e,phase:`checking`}});let i=await se.refreshSnapshot({resetCursor:!0,epoch:n}),a=N().promptAdmissionRecovery;a?.commandId===e.commandId&&a.phase===`checking`&&t({promptAdmissionRecovery:{...a,phase:i?`ready`:`verification-failed`}})},async sendPromptAsNew(e){let n=N().promptAdmissionRecovery;if(!n||n.phase!==`ready`||n.sessionId!==N().snapshot?.selectedSession?.id)return!1;let r=e.trim();if(!r)return!1;t({promptAdmissionRecovery:{...n,phase:`submitting`},notice:null});let i=!1;try{return i=await se.sendPrompt(r),i}finally{let e=N().promptAdmissionRecovery;e?.commandId===n.commandId&&e.phase===`submitting`&&t({promptAdmissionRecovery:i?null:{...e,phase:`ready`}})}},abandonPromptAdmission(){let e=N().promptAdmissionRecovery;!e||e.phase===`submitting`||t({liveMessages:N().liveMessages.filter(t=>t.key!==e.optimisticKey),notice:null,promptAdmissionRecovery:null})},acknowledgePromptAdmissionResolution(e){N().promptAdmissionResolution?.commandId===e&&t({promptAdmissionResolution:null})},async discoverCommands(){let n=N(),i=n.snapshot?.selectedSession?.id;if(n.workspaceDraft||!i||i!==n.snapshot?.currentSessionId||n.sessionSwitching){F();return}if(n.commandDiscovery.sessionId===i&&(n.commandDiscovery.status===`loading`||n.commandDiscovery.status===`ready`))return;let a=r,o=++T;w?.abort();let s=new AbortController;w=s,t({commandDiscovery:{sessionId:i,status:`loading`,commands:[],totalAvailable:0,commandsOmitted:0,error:null}});try{let n=await e.commands(i,s.signal);if(s.signal.aborted||a!==r||o!==T||i!==N().snapshot?.currentSessionId)return;t({commandDiscovery:{sessionId:i,status:`ready`,commands:n.commands,totalAvailable:n.totalAvailable,commandsOmitted:n.truncation.commandsOmitted,error:null}})}catch(e){if(s.signal.aborted||a!==r||o!==T)return;t({commandDiscovery:{sessionId:i,status:`error`,commands:[],totalAvailable:0,commandsOmitted:0,error:e instanceof Error?e.message:String(e)}})}finally{w===s&&(w=null)}},clearCommandDiscovery:F,setQuery(e){t({query:e})},setSearchOpen(e){t({searchOpen:e,...e?{}:{query:``}})},toggleWorkspace(e){let n=new Set(N().collapsed);n.has(e)?n.delete(e):n.add(e),aS(eS,[...n]),t({collapsed:n})},toggleSidebar(e){if(e){t({mobileSidebarOpen:!N().mobileSidebarOpen});return}let n=!N().sidebarCollapsed;try{window.sessionStorage.setItem(tS,String(n))}catch{}t({sidebarCollapsed:n})},closeMobileSidebar(){t({mobileSidebarOpen:!1})},clearNotice(){t({notice:null})}};return{activeTurn:null,turnCancellationPending:!1,turnTerminalStatus:null,pendingFollowUpsReceipt:null,snapshot:null,cursor:null,selectedPath:null,selectedWorkspace:null,workspaceDraft:!1,draftModel:null,modelSelectionPending:!1,modelSearch:{query:``,status:`idle`,models:[],totalMatches:0,matchesOmitted:0,error:null},collapsed:rS(eS),sidebarCollapsed:iS(tS),mobileSidebarOpen:!1,query:``,searchOpen:!1,connection:`connecting`,notice:null,liveMessages:[],liveRunning:!1,livePhase:`idle`,liveRetry:null,thinkingStarts:{},thinkingDurations:{},promptAdmissionPending:!1,promptAdmissionRecovery:null,promptAdmissionResolution:null,sessionSwitching:!1,scrollToBottom:0,thinkingPendingLevel:null,commandDiscovery:j(),actions:se}})}var cS=sS();function lS(){let e=fn(cS),{t}=sn(),{actions:n}=e,r=(0,w.useRef)(null),[i,a]=(0,w.useState)(()=>window.matchMedia?.(`(max-width: 760px)`).matches??!1),o=i&&e.mobileSidebarOpen;(0,w.useEffect)(()=>{let e=window.matchMedia?.(`(max-width: 760px)`);if(!e)return;let t=()=>{a(e.matches),e.matches||n.closeMobileSidebar()};return t(),e.addEventListener(`change`,t),()=>e.removeEventListener(`change`,t)},[n]);let[s,c]=(0,w.useState)(`chat`),[l,u]=(0,w.useState)(null),d=e.snapshot?.models.find(e=>e.current),f=JSON.stringify([d?.provider,d?.id]),p=t=>{let n=e.snapshot,r=n?.selectedSession;e.workspaceDraft||!n||!r||e.sessionSwitching||r.id!==n.currentSessionId||u({sessionId:r.id,sessionPath:r.path,cwd:r.cwd,model:d?.label??``,modelKey:f,terminalId:t})},m=l&&!e.workspaceDraft&&!e.sessionSwitching&&l.sessionId===e.snapshot?.currentSessionId&&l.sessionPath===e.snapshot?.selectedSession?.path&&l.modelKey===f;(0,w.useEffect)(()=>{l&&!m&&u(null)},[l,m]),(0,w.useEffect)(()=>(n.start(),n.stop),[n]);let h=e.workspaceDraft?void 0:e.snapshot?.selectedSession,g=h?.entries.some(e=>e.type===`message`&&e.message)||e.liveMessages.length>0,_=!h||!g,v=(0,w.useCallback)(e=>n.sendPrompt(e),[n]);return(0,V.jsxs)(`div`,{className:`app-shell ${e.sidebarCollapsed?`sidebar-collapsed`:``} ${e.mobileSidebarOpen?`sidebar-open`:``}`,children:[(0,V.jsx)(rd,{snapshot:e.snapshot,selectedPath:e.workspaceDraft?null:e.selectedPath,selectedWorkspace:e.selectedWorkspace,collapsed:e.collapsed,query:e.query,searchOpen:e.searchOpen,mobileOpen:o,returnFocusRef:r,actions:n}),e.sidebarCollapsed&&(0,V.jsx)(`button`,{className:`sidebar-expand`,type:`button`,"aria-label":t(`expandSidebar`),title:t(`expandSidebar`),onClick:()=>n.toggleSidebar(!1),children:(0,V.jsx)(Se,{})}),(0,V.jsxs)(`main`,{inert:o,className:`conversation-shell ${h?`has-view`:``} ${_&&(e.workspaceDraft||s===`chat`)?`landing`:``}`,children:[(0,V.jsx)(`h1`,{className:`sr-only`,children:`OpenPI`}),(0,V.jsxs)(`header`,{className:`mobile-header`,children:[(0,V.jsx)(`button`,{ref:r,type:`button`,"aria-label":t(`openSidebar`),"aria-controls":`session-sidebar`,"aria-expanded":o,onClick:()=>n.toggleSidebar(!0),children:(0,V.jsx)(be,{})}),(0,V.jsx)(`span`,{className:`connection-state ${e.connection}`,children:t(e.connection)})]}),h&&(0,V.jsxs)(`fieldset`,{className:`conversation-view-switch`,"aria-label":t(`conversationView`),children:[(0,V.jsx)(`button`,{type:`button`,"aria-pressed":s===`chat`,onClick:()=>c(`chat`),children:t(`chatView`)}),(0,V.jsx)(`button`,{type:`button`,"aria-pressed":s===`trajectory`,onClick:()=>c(`trajectory`),children:t(`trajectory`)})]}),e.sessionSwitching?(0,V.jsx)(`div`,{className:`conversation switching`,role:`status`,children:(0,V.jsxs)(`div`,{className:`conversation-running`,children:[(0,V.jsx)(`span`,{className:`conversation-running-dot`}),(0,V.jsx)(`span`,{children:t(`switchingSession`)})]})}):s===`trajectory`&&h&&e.snapshot?(0,V.jsx)(od,{snapshot:e.snapshot,running:e.liveRunning},h.path):_?(0,V.jsx)(`section`,{className:`conversation landing-conversation`,"aria-label":`Conversation`,children:(0,V.jsx)(`div`,{className:`landing-welcome`,children:(0,V.jsx)(hn,{animated:!0})})}):e.snapshot?(0,V.jsx)(zx,{snapshot:e.snapshot,liveMessages:e.liveMessages,liveRunning:e.liveRunning,livePhase:e.livePhase,liveRetry:e.liveRetry,thinkingStarts:e.thinkingStarts,thinkingDurations:e.thinkingDurations,scrollToBottom:e.scrollToBottom,onResend:v}):null,(0,V.jsx)(lu,{workspaceDraft:e.workspaceDraft,draftModel:e.draftModel,modelSelectionPending:e.modelSelectionPending,modelSearch:e.modelSearch,thinkingPendingLevel:e.thinkingPendingLevel,onInspect:p,activeTurn:e.activeTurn,turnCancellationPending:e.turnCancellationPending,turnTerminalStatus:e.turnTerminalStatus,pendingFollowUpsReceipt:e.pendingFollowUpsReceipt,commandDiscovery:e.commandDiscovery,snapshot:e.snapshot,selectedWorkspace:e.selectedWorkspace,sessionSwitching:e.sessionSwitching,promptAdmissionPending:e.promptAdmissionPending,promptAdmissionRecovery:e.promptAdmissionRecovery,promptAdmissionResolution:e.promptAdmissionResolution,liveRunning:e.liveRunning,landing:_,actions:n}),e.notice&&(0,V.jsxs)(`div`,{className:`notice`,role:`alert`,children:[(0,V.jsx)(`span`,{children:e.notice}),(0,V.jsx)(`button`,{type:`button`,"aria-label":t(`close`),onClick:n.clearNotice,children:(0,V.jsx)(Re,{})})]})]}),m&&(0,V.jsx)(td,{target:l,onClose:()=>u(null)},`${l.sessionId}:${l.sessionPath}:${l.terminalId??`status`}`),(0,V.jsx)(`button`,{className:`sidebar-scrim`,type:`button`,tabIndex:-1,"aria-hidden":`true`,"aria-label":t(`close`),onClick:n.closeMobileSidebar})]})}var uS={base:{k1xSpc:`xjp7ctv`,kMwMTN:`x1tgivj0`,kMv6JI:`x9ynric`,$$css:!0},light:{kQNsl9:`x19aimcq`,$$css:!0},dark:{kQNsl9:`xntwwlm`,$$css:!0},system:{kQNsl9:`x108lcm5`,$$css:!0}},dS=w.createContext(!1);dS.displayName=`ThemeNestingContext`;var fS=new Set,pS=0;function mS(e){let t=(0,w.useId)();(0,w.useInsertionEffect)(()=>{if(e.__built)return;let n=`astryx-theme-${e.name}`;if(fS.has(n))return;`${e.name}`,`${e.name}${e.name}${e.name}${e.name}`;let{prose:r,component:i}=Fs(e),a=Ps();fS.add(n);let o=[()=>fS.delete(n)];if(a){if(pS++===0){let e=document.createElement(`style`);e.setAttribute(Zr(`theme-base`),``),e.textContent=`@layer astryx-base {\n${a}\n}`,document.head.appendChild(e)}o.push(()=>{--pS===0&&document.querySelector(`style[${Zr(`theme-base`)}]`)?.remove()})}if(r){let n=document.createElement(`style`);n.setAttribute(Zr(`theme-prose`),e.name),n.setAttribute(Zr(`id`),t),n.textContent=`@layer reset {\n${r}\n}`,document.head.appendChild(n)}if(i){let n=document.createElement(`style`);n.setAttribute(Zr(`theme`),e.name),n.setAttribute(Zr(`id`),t),n.textContent=`@layer astryx-theme {\n${i}\n}`,document.head.appendChild(n)}return(r||i)&&o.push(()=>{let n=document.querySelector(`style[${Zr(`theme-prose`)}="${e.name}"][${Zr(`id`)}="${t}"]`),r=document.querySelector(`style[${Zr(`theme`)}="${e.name}"][${Zr(`id`)}="${t}"]`);n?.remove(),r?.remove()}),()=>{for(let e of o)e()}},[e,t])}function hS(e,t,n){Fc(()=>{if(!e&&!(typeof document>`u`))return t===`light`||t===`dark`?document.documentElement.setAttribute(`data-theme`,t):document.documentElement.removeAttribute(`data-theme`),document.documentElement.setAttribute(Zr(`theme`),n),()=>{document.documentElement.removeAttribute(`data-theme`),document.documentElement.removeAttribute(Zr(`theme`))}},[e,t,n])}function gS({theme:e,mode:t=`system`,children:n}){let r=(0,w.use)(dS);cs(e),mS(e),hS(r,t,e.name);let i=t===`dark`?uS.dark:t===`light`?uS.light:uS.system,a=(0,w.useMemo)(()=>({theme:e,mode:t}),[e,t]);return(0,V.jsx)(Ls,{value:a,children:(0,V.jsx)(dS,{value:!0,children:(0,V.jsx)(`div`,{...bn(uS.base,i),"data-astryx-theme":e.name,"data-theme":t===`system`?void 0:t,children:n})})})}gS.displayName=`Theme`;var _S={size:`1em`,"aria-hidden":!0},vS={name:`neutral`,__built:!0,tokens:{"--font-size-4xs":`0.375rem`,"--font-size-3xs":`0.4375rem`,"--font-size-2xs":`0.5rem`,"--font-size-xs":`0.625rem`,"--font-size-sm":`0.75rem`,"--font-size-base":`0.875rem`,"--font-size-lg":`1.0625rem`,"--font-size-xl":`1.25rem`,"--font-size-2xl":`1.5rem`,"--font-size-3xl":`1.8125rem`,"--font-size-4xl":`2.1875rem`,"--font-size-5xl":`2.625rem`,"--text-heading-1-size":`var(--font-size-2xl)`,"--text-heading-1-weight":`var(--font-weight-semibold)`,"--text-heading-1-leading":`1.3333`,"--text-heading-2-size":`var(--font-size-xl)`,"--text-heading-2-weight":`var(--font-weight-semibold)`,"--text-heading-2-leading":`1.4`,"--text-heading-3-size":`var(--font-size-lg)`,"--text-heading-3-weight":`var(--font-weight-bold)`,"--text-heading-3-leading":`1.4118`,"--text-heading-4-size":`var(--font-size-base)`,"--text-heading-4-weight":`var(--font-weight-bold)`,"--text-heading-4-leading":`1.4286`,"--text-heading-5-size":`var(--font-size-sm)`,"--text-heading-5-weight":`var(--font-weight-semibold)`,"--text-heading-5-leading":`1.6667`,"--text-heading-6-size":`var(--font-size-xs)`,"--text-heading-6-weight":`var(--font-weight-semibold)`,"--text-heading-6-leading":`1.6`,"--text-body-size":`var(--font-size-base)`,"--text-body-weight":`var(--font-weight-normal)`,"--text-body-leading":`1.4286`,"--text-large-size":`var(--font-size-lg)`,"--text-large-weight":`var(--font-weight-semibold)`,"--text-large-leading":`1.4118`,"--text-label-size":`var(--font-size-base)`,"--text-label-weight":`var(--font-weight-medium)`,"--text-label-leading":`1.4286`,"--text-code-size":`var(--font-size-base)`,"--text-code-weight":`var(--font-weight-normal)`,"--text-code-leading":`1.4286`,"--text-supporting-size":`var(--font-size-sm)`,"--text-supporting-weight":`var(--font-weight-normal)`,"--text-supporting-leading":`1.6667`,"--text-display-1-size":`var(--font-size-5xl)`,"--text-display-1-weight":`var(--font-weight-normal)`,"--text-display-1-leading":`1.2381`,"--text-display-2-size":`var(--font-size-4xl)`,"--text-display-2-weight":`var(--font-weight-normal)`,"--text-display-2-leading":`1.2571`,"--text-display-3-size":`var(--font-size-3xl)`,"--text-display-3-weight":`var(--font-weight-normal)`,"--text-display-3-leading":`1.3793`,"--duration-fast-min":`95ms`,"--duration-fast":`125ms`,"--duration-fast-max":`165ms`,"--duration-medium-min":`225ms`,"--duration-medium":`300ms`,"--duration-medium-max":`400ms`,"--duration-slow-min":`525ms`,"--duration-slow":`700ms`,"--duration-slow-max":`935ms`,"--font-family-body":`Figtree, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif`,"--font-family-heading":`Figtree, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif`,"--font-family-code":`ui-monospace, "SF Mono", Monaco, Consolas, "Liberation Mono", "Courier New", monospace`,"--color-syntax-keyword":`light-dark(#700084, #efa8ff)`,"--color-syntax-string":`light-dark(#005600, #a6d2a2)`,"--color-syntax-comment":`light-dark(#737373, #a3a3a3)`,"--color-syntax-number":`light-dark(#6e3500, #ffb37f)`,"--color-syntax-function":`light-dark(#00458c, #a0caff)`,"--color-syntax-type":`light-dark(#700084, #efa8ff)`,"--color-syntax-variable":`light-dark(#171717, #e5e5e5)`,"--color-syntax-operator":`light-dark(#737373, #a3a3a3)`,"--color-syntax-constant":`light-dark(#6e3500, #ffb37f)`,"--color-syntax-tag":`light-dark(#89001a, #ffaeaa)`,"--color-syntax-attribute":`light-dark(#584400, #eec12f)`,"--color-syntax-property":`light-dark(#005348, #83dac9)`,"--color-syntax-punctuation":`light-dark(#6e6e6e, #a0a0a0)`,"--color-syntax-background":`light-dark(#fafafa, #0a0a0a)`,"--color-background-surface":`light-dark(#ffffff, #262626)`,"--color-background-body":`light-dark(#f1f1f1, #1b1b1b)`,"--color-background-card":`light-dark(#ffffff, #1b1b1b)`,"--color-background-popover":`light-dark(#ffffff, #1b1b1b)`,"--color-background-muted":`light-dark(#f1f1f1, #1b1b1b)`,"--color-accent":`light-dark(#262626, #ebebeb)`,"--color-accent-muted":`light-dark(#f1f1f1, #262626)`,"--color-neutral":`light-dark(#0000000F, #FFFFFF1A)`,"--color-overlay":`light-dark(#00000080, #000000CC)`,"--color-overlay-hover":`light-dark(#0000000D, #FFFFFF0D)`,"--color-overlay-pressed":`light-dark(#0000001A, #FFFFFF1A)`,"--color-text-primary":`light-dark(#171717, #fafafa)`,"--color-text-secondary":`light-dark(#525252, #a3a3a3)`,"--color-text-disabled":`light-dark(#a3a3a3, #525252)`,"--color-text-accent":`light-dark(#262626, #ebebeb)`,"--color-on-dark":`#ffffff`,"--color-on-light":`#171717`,"--color-on-accent":`light-dark(#ffffff, #171717)`,"--color-on-success":`light-dark(#ffffff, #171717)`,"--color-on-error":`light-dark(#ffffff, #171717)`,"--color-on-warning":`#171717`,"--color-icon-accent":`light-dark(#262626, #ebebeb)`,"--color-icon-primary":`light-dark(#171717, #fafafa)`,"--color-icon-secondary":`light-dark(#737373, #a3a3a3)`,"--color-icon-disabled":`light-dark(#a3a3a3, #525252)`,"--color-success":`light-dark(#007004, #9fe59b)`,"--color-error":`light-dark(#a50c25, #ffc6c1)`,"--color-warning":`light-dark(#745b00, #fdcf4f)`,"--color-success-muted":`light-dark(#c5e5c0, #84c9803D)`,"--color-error-muted":`light-dark(#facecb, #ff9e973D)`,"--color-warning-muted":`light-dark(#f8da9d, #deb4333D)`,"--color-border":`light-dark(#00000014, #FFFFFF1A)`,"--color-border-emphasized":`light-dark(#d4d4d4, #525252)`,"--color-skeleton":`light-dark(#ebebeb, #525252)`,"--color-shadow":`light-dark(#0000001A, #0000004D)`,"--color-tint-hover":`light-dark(black, white)`,"--color-background-red":`light-dark(#facecb, #ff9e973D)`,"--color-border-red":`light-dark(#e6bab8, #ff6f6c)`,"--color-icon-red":`light-dark(#89001a, #ff9e97)`,"--color-text-red":`light-dark(#89001a, #ffc6c1)`,"--color-background-orange":`light-dark(#fad0b5, #ffa2583D)`,"--color-border-orange":`light-dark(#e6bda2, #e2883e)`,"--color-icon-orange":`light-dark(#6e3500, #ffa258)`,"--color-text-orange":`light-dark(#6e3500, #ffc9a2)`,"--color-background-yellow":`light-dark(#f8da9d, #deb4333D)`,"--color-border-yellow":`light-dark(#e4c279, #c0990e)`,"--color-icon-yellow":`light-dark(#584400, #deb433)`,"--color-text-yellow":`light-dark(#584400, #fdcf4f)`,"--color-background-green":`light-dark(#c5e5c0, #84c9803D)`,"--color-border-green":`light-dark(#b2d1ac, #69ad67)`,"--color-icon-green":`light-dark(#0c5700, #84c980)`,"--color-text-green":`light-dark(#0c5700, #9fe59b)`,"--color-background-teal":`light-dark(#a5e3d6, #7ec6b83D)`,"--color-border-teal":`light-dark(#94d6c8, #63ab9d)`,"--color-icon-teal":`light-dark(#005348, #7ec6b8)`,"--color-text-teal":`light-dark(#005348, #99e2d3)`,"--color-background-cyan":`light-dark(#a3e0ef, #83c2d43D)`,"--color-border-cyan":`light-dark(#91d3e3, #67a7b8)`,"--color-icon-cyan":`light-dark(#00505f, #83c2d4)`,"--color-text-cyan":`light-dark(#00505f, #9edef0)`,"--color-background-blue":`light-dark(#c4ddfb, #9eb7ff3D)`,"--color-border-blue":`light-dark(#b1c9e7, #6d9cfe)`,"--color-icon-blue":`light-dark(#00458c, #9eb7ff)`,"--color-text-blue":`light-dark(#00458c, #c7d3ff)`,"--color-background-purple":`light-dark(#eccef3, #f297ff3D)`,"--color-border-purple":`light-dark(#d8bbdf, #dd74f0)`,"--color-icon-purple":`light-dark(#700084, #f297ff)`,"--color-text-purple":`light-dark(#700084, #fac1ff)`,"--color-background-pink":`light-dark(#fccadc, #ff99c33D)`,"--color-border-pink":`light-dark(#e7b7c8, #f273aa)`,"--color-icon-pink":`light-dark(#83004b, #ff99c3)`,"--color-text-pink":`light-dark(#83004b, #ffc3da)`,"--color-background-gray":`light-dark(#e5e5e5, var(--color-neutral))`,"--color-border-gray":`light-dark(#d4d4d4, #262626)`,"--color-icon-gray":`light-dark(#525252, #a3a3a3)`,"--color-text-gray":`light-dark(#262626, #e5e5e5)`,"--radius-none":`0px`,"--radius-inner":`0.375rem`,"--radius-element":`0.625rem`,"--radius-container":`0.75rem`,"--radius-page":`1.75rem`,"--radius-full":`9999px`,"--shadow-low":`0 2px 4px light-dark(oklch(0 0 0 / 5%), oklch(0 0 0 / 25%)), 0 4px 8px light-dark(oklch(0 0 0 / 10%), oklch(0 0 0 / 40%)), inset 0 0 0 1px light-dark(transparent, oklch(1 0 0 / 8%))`,"--shadow-med":`0 2px 4px light-dark(oklch(0 0 0 / 5%), oklch(0 0 0 / 35%)), 0 4px 12px light-dark(oklch(0 0 0 / 10%), oklch(0 0 0 / 50%)), inset 0 0 0 1px light-dark(transparent, oklch(1 0 0 / 12%))`,"--shadow-high":`0 4px 6px light-dark(oklch(0 0 0 / 10%), oklch(0 0 0 / 50%)), 0 12px 24px light-dark(oklch(0 0 0 / 15%), oklch(0 0 0 / 70%)), inset 0 0 0 1px light-dark(transparent, oklch(1 0 0 / 15%))`,"--shadow-inset-hover":`inset 0px 0px 0px 2px #0074e24D`,"--shadow-inset-selected":`inset 0px 0px 0px 2px #0074e280`,"--shadow-inset-success":`inset 0px 0px 0px 2px #1981004D`,"--shadow-inset-warning":`inset 0px 0px 0px 2px #ffce2f4D`,"--shadow-inset-error":`inset 0px 0px 0px 2px #e33f4a4D`},components:{heading:{"level:1":{fontFamily:`var(--font-family-heading)`,fontSize:`var(--text-heading-1-size)`,fontWeight:`var(--text-heading-1-weight)`,lineHeight:`var(--text-heading-1-leading)`},"level:2":{fontFamily:`var(--font-family-heading)`,fontSize:`var(--text-heading-2-size)`,fontWeight:`var(--text-heading-2-weight)`,lineHeight:`var(--text-heading-2-leading)`},"level:3":{fontFamily:`var(--font-family-heading)`,fontSize:`var(--text-heading-3-size)`,fontWeight:`var(--text-heading-3-weight)`,lineHeight:`var(--text-heading-3-leading)`},"level:4":{fontFamily:`var(--font-family-heading)`,fontSize:`var(--text-heading-4-size)`,fontWeight:`var(--text-heading-4-weight)`,lineHeight:`var(--text-heading-4-leading)`},"level:5":{fontFamily:`var(--font-family-heading)`,fontSize:`var(--text-heading-5-size)`,fontWeight:`var(--text-heading-5-weight)`,lineHeight:`var(--text-heading-5-leading)`},"level:6":{fontFamily:`var(--font-family-heading)`,fontSize:`var(--text-heading-6-size)`,fontWeight:`var(--text-heading-6-weight)`,lineHeight:`var(--text-heading-6-leading)`},"type:display-1":{fontFamily:`var(--font-family-heading)`,fontSize:`var(--text-display-1-size)`,lineHeight:`var(--text-display-1-leading)`},"type:display-2":{fontFamily:`var(--font-family-heading)`,fontSize:`var(--text-display-2-size)`,lineHeight:`var(--text-display-2-leading)`},"type:display-3":{fontFamily:`var(--font-family-heading)`,fontSize:`var(--text-display-3-size)`,lineHeight:`var(--text-display-3-leading)`}},text:{"type:body":{fontFamily:`var(--font-family-body)`,fontSize:`var(--text-body-size)`,lineHeight:`var(--text-body-leading)`},"type:large":{fontFamily:`var(--font-family-body)`,fontSize:`var(--text-large-size)`,lineHeight:`var(--text-large-leading)`},"type:label":{fontFamily:`var(--font-family-body)`,fontSize:`var(--text-label-size)`,lineHeight:`var(--text-label-leading)`},"type:code":{fontFamily:`var(--font-family-code)`,fontSize:`var(--text-code-size)`,lineHeight:`var(--text-code-leading)`},"type:supporting":{fontFamily:`var(--font-family-body)`,fontSize:`var(--text-supporting-size)`,lineHeight:`var(--text-supporting-leading)`},"type:display-1":{fontFamily:`var(--font-family-heading)`,fontSize:`var(--text-display-1-size)`,lineHeight:`var(--text-display-1-leading)`},"type:display-2":{fontFamily:`var(--font-family-heading)`,fontSize:`var(--text-display-2-size)`,lineHeight:`var(--text-display-2-leading)`},"type:display-3":{fontFamily:`var(--font-family-heading)`,fontSize:`var(--text-display-3-size)`,lineHeight:`var(--text-display-3-leading)`}},button:{"variant:destructive":{backgroundColor:`var(--color-error-muted)`,color:`var(--color-error)`}},badge:{"variant:info":{backgroundColor:`light-dark(#0074e2, #6d9cfe)`,color:`light-dark(#ffffff, #171717)`},"variant:neutral":{backgroundColor:`var(--color-background-gray)`,color:`var(--color-text-gray)`},"variant:success":{backgroundColor:`light-dark(#198100, #64af4c)`,color:`light-dark(#ffffff, #171717)`},"variant:warning":{backgroundColor:`#ffce2f`,color:`#171717`},"variant:error":{backgroundColor:`light-dark(#c9303a, #ff705d)`,color:`light-dark(#ffffff, #171717)`},"variant:red":{backgroundColor:`var(--color-background-red)`,color:`var(--color-text-red)`},"variant:orange":{backgroundColor:`var(--color-background-orange)`,color:`var(--color-text-orange)`},"variant:yellow":{backgroundColor:`var(--color-background-yellow)`,color:`var(--color-text-yellow)`},"variant:green":{backgroundColor:`var(--color-background-green)`,color:`var(--color-text-green)`},"variant:teal":{backgroundColor:`var(--color-background-teal)`,color:`var(--color-text-teal)`},"variant:cyan":{backgroundColor:`var(--color-background-cyan)`,color:`var(--color-text-cyan)`},"variant:blue":{backgroundColor:`var(--color-background-blue)`,color:`var(--color-text-blue)`},"variant:purple":{backgroundColor:`var(--color-background-purple)`,color:`var(--color-text-purple)`},"variant:pink":{backgroundColor:`var(--color-background-pink)`,color:`var(--color-text-pink)`},"variant:gray":{backgroundColor:`var(--color-background-gray)`,color:`var(--color-text-gray)`}},statusdot:{"variant:success":{backgroundColor:`light-dark(#198100, #64af4c)`},"variant:warning":{backgroundColor:`#ffce2f`},"variant:error":{backgroundColor:`light-dark(#c9303a, #ff705d)`},"variant:accent":{backgroundColor:`light-dark(#0074e2, #6d9cfe)`}},banner:{"status:info":{"--color-accent-muted":`var(--color-background-blue)`,"--color-text-primary":`var(--color-text-blue)`,"--color-text-secondary":`var(--color-text-blue)`,"--color-accent":`var(--color-text-blue)`},"status:success":{"--color-text-primary":`var(--color-text-green)`,"--color-text-secondary":`var(--color-text-green)`,"--color-success":`var(--color-text-green)`},"status:warning":{"--color-text-primary":`var(--color-text-yellow)`,"--color-text-secondary":`var(--color-text-yellow)`,"--color-warning":`var(--color-text-yellow)`},"status:error":{"--color-text-primary":`var(--color-text-red)`,"--color-text-secondary":`var(--color-text-red)`,"--color-error":`var(--color-text-red)`}},switch:{base:{"--color-background-gray":`var(--color-border-emphasized)`}},progressbar:{base:{"--color-background-muted":`var(--color-border-emphasized)`},"variant:accent":{"--color-accent":`#0074e2`},"variant:success":{"--color-success":`#198100`},"variant:warning":{"--color-warning":`#ffce2f`},"variant:error":{"--color-error":`#c9303a`}},card:{base:{padding:`var(--spacing-3)`}},section:{base:{padding:`var(--spacing-3)`}}},__onDark:{tokens:{"color-scheme":`dark`,"--color-text-primary":`var(--color-on-dark)`,"--color-icon-primary":`var(--color-on-dark)`,"--color-accent":`var(--color-on-dark)`}},__onLight:{tokens:{"color-scheme":`light`,"--color-text-primary":`var(--color-on-light)`,"--color-icon-primary":`var(--color-on-light)`,"--color-accent":`var(--color-on-light)`}},icons:{close:(0,V.jsx)(Re,{..._S}),chevronDown:(0,V.jsx)(te,{..._S}),chevronLeft:(0,V.jsx)(ne,{..._S}),chevronRight:(0,V.jsx)(re,{..._S}),chevronsLeft:(0,V.jsx)(R,{..._S}),chevronsRight:(0,V.jsx)(z,{..._S}),check:(0,V.jsx)(L,{..._S}),success:(0,V.jsx)(ie,{..._S}),error:(0,V.jsx)(ae,{..._S}),warning:(0,V.jsx)(Fe,{..._S}),info:(0,V.jsx)(ve,{..._S}),calendar:(0,V.jsx)(F,{..._S}),clock:(0,V.jsx)(se,{..._S}),externalLink:(0,V.jsx)(de,{..._S}),menu:(0,V.jsx)(be,{..._S}),moreHorizontal:(0,V.jsx)(ue,{..._S}),search:(0,V.jsx)(De,{..._S}),arrowUp:(0,V.jsx)(N,{..._S}),arrowDown:(0,V.jsx)(j,{..._S}),arrowsUpDown:(0,V.jsx)(M,{..._S}),funnel:(0,V.jsx)(ge,{..._S}),eyeSlash:(0,V.jsx)(fe,{..._S}),viewColumns:(0,V.jsx)(ce,{..._S}),copy:(0,V.jsx)(le,{..._S}),checkDouble:(0,V.jsx)(I,{..._S}),wrench:(0,V.jsx)(Le,{..._S}),stop:(0,V.jsx)(Me,{..._S}),microphone:(0,V.jsx)(xe,{..._S})}};function yS({children:e}){let t=fn(cS,e=>e.snapshot?.preferences?.theme)??`system`,[n,r]=(0,w.useState)(()=>window.matchMedia?.(`(prefers-color-scheme: dark)`).matches??!1);(0,w.useEffect)(()=>{let e=window.matchMedia?.(`(prefers-color-scheme: dark)`);if(!e)return;let t=()=>r(e.matches);return t(),e.addEventListener(`change`,t),()=>e.removeEventListener(`change`,t)},[]);let i=t===`dark`||t===`system`&&n?`dark`:`light`;return(0,w.useEffect)(()=>{document.documentElement.dataset.theme=i},[i]),(0,V.jsx)(cn,{i18n:Pt,children:(0,V.jsx)(gS,{theme:vS,mode:i,children:e})})}var bS=document.getElementById(`root`);if(!bS)throw Error(`OpenPI Web root is missing`);(0,ze.createRoot)(bS).render((0,V.jsx)(w.StrictMode,{children:(0,V.jsx)(yS,{children:(0,V.jsx)(lS,{})})})); \ No newline at end of file diff --git a/web/host/web-host.ts b/web/host/web-host.ts index 63a3f317..852119b2 100644 --- a/web/host/web-host.ts +++ b/web/host/web-host.ts @@ -547,8 +547,9 @@ export class WebHost { const result = await this.runtime.newSession(workspacePath, { commandId: body.commandId, }); - this.publish("session_created", { + if (!result.replayed) this.publish("session_created", { workspacePath, + sessionId: result.sessionId, commandId: body.commandId, ...(result.sessionPath ? { sessionPath: result.sessionPath } : {}), }); diff --git a/web/runtime/pi-runtime.ts b/web/runtime/pi-runtime.ts index b389a822..2bce0cc3 100644 --- a/web/runtime/pi-runtime.ts +++ b/web/runtime/pi-runtime.ts @@ -25,6 +25,7 @@ import { type WebRuntimeController, type WebRuntimeEvent, type WebSessionCreationOptions, + type WebSessionCreationResult, type WebThinkingProjection, type WebThinkingSelectionOptions, type WebTurnCancellationOptions, @@ -836,10 +837,24 @@ export class PiWebRuntime implements WebRuntimeController { return await requestAdmission; } + private sessionCreationReceipts?: Map; + newSession(workspacePath: string, options?: WebSessionCreationOptions) { - return this.serializeControllerMutation(() => - this.createNewSession(workspacePath, options), - ); + return this.serializeControllerMutation(async () => { + this.assertActive(); + const commandId = options?.commandId; + const receipts = this.sessionCreationReceipts ??= new Map(); + const previous = commandId ? receipts.get(commandId) : undefined; + if (previous) { + if (previous.workspacePath !== workspacePath) throw new Error("Session creation command belongs to another workspace"); + return { ...previous.result, replayed: true }; + } + // Do not evict receipts: forgetting a command would permit duplicate creation. + if (commandId && receipts.size >= 1024) throw new Error("Session creation receipt limit reached; select an existing Session or restart the Web host"); + const result = await this.createNewSession(workspacePath, options); + if (commandId) receipts.set(commandId, { workspacePath, result: { ...result } }); + return result; + }); } private async createNewSession( @@ -855,13 +870,16 @@ export class PiWebRuntime implements WebRuntimeController { ); await this.activateCandidate(replacement.runtime); this.hasSelectedWorkspace = true; + const sessionId = this.runtime.session.sessionManager.getSessionId(); const sessionPath = this.runtime.session.sessionManager.getSessionFile(); this.emit("session_switched", { + sessionId, ...(options?.commandId ? { commandId: options.commandId } : {}), ...(sessionPath ? { sessionPath } : {}), }); return { cancelled: false, + sessionId, ...(options?.commandId ? { commandId: options.commandId } : {}), ...(sessionPath ? { sessionPath } : {}), }; diff --git a/web/runtime/types.ts b/web/runtime/types.ts index 9c63a392..56d28973 100644 --- a/web/runtime/types.ts +++ b/web/runtime/types.ts @@ -111,7 +111,9 @@ export interface WebSessionCreationOptions { export interface WebSessionCreationResult { cancelled: boolean; + replayed?: boolean; commandId?: string; + sessionId: string; sessionPath?: string; } diff --git a/web/ui/src/protocol/client.ts b/web/ui/src/protocol/client.ts index aeb1c648..bbb77332 100644 --- a/web/ui/src/protocol/client.ts +++ b/web/ui/src/protocol/client.ts @@ -64,6 +64,13 @@ export interface SessionMutationResult { sessionPath?: string; } +export interface SessionCreationResult { + cancelled: boolean; + commandId: string; + sessionId: string; + sessionPath?: string; +} + export interface WorkspaceSelectionResult { cancelled?: boolean; path?: string; @@ -234,7 +241,7 @@ export class WebClient { } createSession(workspacePath: string, commandId: string) { - return this.request("/api/sessions", { + return this.request("/api/sessions", { method: "POST", body: JSON.stringify({ workspacePath, commandId }), }); diff --git a/web/ui/src/store/web-store.ts b/web/ui/src/store/web-store.ts index ba6ebb6a..5658f9a2 100644 --- a/web/ui/src/store/web-store.ts +++ b/web/ui/src/store/web-store.ts @@ -90,7 +90,14 @@ interface SessionActivation { kind: "create" | "select"; commandId?: string; expectedPath: string | null; - observedPath?: string | null; + expectedSessionId?: string; +} + +interface SessionTarget { + epoch: number; + sessionId: string; + sessionPath: string | null; + workspacePath: string; } export interface CommandDiscoveryState { @@ -154,7 +161,7 @@ export interface WebStoreActions { setWorkspace: (path: string | null) => void; renameWorkspace: (path: string, name: string) => Promise; removeWorkspace: (path: string) => Promise; - createSession: (workspacePath: string) => Promise; + createSession: (workspacePath: string) => Promise; selectSession: (path: string) => Promise; renameSession: (path: string, name: string) => Promise; archiveSession: (path: string) => Promise; @@ -221,6 +228,7 @@ export function createWebStore( optimisticKey: string; } | null = null; let sessionActivation: SessionActivation | null = null; + let creationRetry: { workspacePath: string; commandId: string } | null = null; let sessionSelectionTail = Promise.resolve(); let refreshTimer: number | null = null; let refreshInFlight = false; @@ -306,6 +314,19 @@ export function createWebStore( }); }; + const targetMatchesSnapshot = (target: SessionTarget) => { + const snapshot = get().snapshot; + return ( + target.epoch === sessionEpoch && + get().selectedWorkspace === target.workspacePath && + snapshot?.currentSessionId === target.sessionId && + snapshot.selectedSession?.id === target.sessionId && + snapshot.selectedSession.cwd === target.workspacePath && + (!target.sessionPath || + snapshot.selectedSession.path === target.sessionPath) + ); + }; + const clearCommandDiscovery = () => { commandDiscoveryGeneration++; commandDiscoveryController?.abort(); @@ -619,16 +640,19 @@ export function createWebStore( sessionActivation?.kind === "create" && sessionActivation.commandId === eventCommandId && event.type === "session_switched" && - typeof eventPath === "string" && - !knownPath + typeof eventSessionId === "string" && + (!sessionActivation.expectedSessionId || + sessionActivation.expectedSessionId === eventSessionId) && + (typeof eventPath !== "string" || !knownPath) ) { - sessionActivation.observedPath = eventPath; belongs = true; } else if ( sessionActivation?.kind === "create" && sessionActivation.commandId === eventCommandId && event.type === "session_created" && - typeof sessionActivation.observedPath === "string" + typeof eventSessionId === "string" && + (!sessionActivation.expectedSessionId || + eventSessionId === sessionActivation.expectedSessionId) ) { belongs = true; } @@ -816,7 +840,7 @@ export function createWebStore( cursor: get().cursor ?? 0, onConnected: () => { reconnectDelay = 500; - set({ connection: "connected", notice: null }); + set({ connection: "connected" }); }, onEvent: applyRuntimeEvent, onHeartbeat: () => scheduleSnapshotRefresh(0), @@ -973,6 +997,7 @@ export function createWebStore( if (path === current.selectedWorkspace && !current.sessionSwitching) return; ++sessionEpoch; + creationRetry = null; resetThinking(); clearCommandDiscovery(); promptAdmissionToken = null; @@ -1019,14 +1044,18 @@ export function createWebStore( } }, async createSession(workspacePath) { - if (!workspacePath || get().modelSelectionPending) return false; + if (!workspacePath || get().modelSelectionPending) return null; const current = get(); const epoch = ++sessionEpoch; resetThinking(); clearCommandDiscovery(); const commandId = + (creationRetry?.workspacePath === workspacePath + ? creationRetry.commandId + : undefined) ?? globalThis.crypto?.randomUUID?.() ?? `web-create-${Date.now()}-${epoch}`; + creationRetry = { workspacePath, commandId }; promptAdmissionToken = null; promptAdmission = null; set({ @@ -1046,7 +1075,7 @@ export function createWebStore( workspaceDraft: true, sessionSwitching: true, }); - let created = false; + let created: SessionTarget | null = null; const creation = sessionSelectionTail.then(async () => { if (epoch !== sessionEpoch) return; sessionActivation = { @@ -1054,20 +1083,33 @@ export function createWebStore( epoch, expectedPath: null, kind: "create", - observedPath: null, }; try { - const receipt = await client.createSession( - workspacePath, - commandId, - ); + const result = await client.createSession(workspacePath, commandId); if (epoch !== sessionEpoch) return; - if (receipt.cancelled || !receipt.sessionPath) { + if ( + result.cancelled || + result.commandId !== commandId || + typeof result.sessionId !== "string" || + !result.sessionId || + result.sessionId.length > 128 || + (result.sessionPath !== undefined && !result.sessionPath) + ) { throw new Error( - "Session creation was not confirmed. Please try again.", + "Session creation did not return a valid target identity.", ); } - set({ selectedPath: null }); + const target: SessionTarget = { + epoch, + sessionId: result.sessionId, + sessionPath: result.sessionPath ?? null, + workspacePath, + }; + if (sessionActivation?.epoch === epoch) { + sessionActivation.expectedSessionId = target.sessionId; + sessionActivation.expectedPath = target.sessionPath; + } + set({ selectedPath: target.sessionPath }); let refreshed = await actions.refreshSnapshot({ epoch }); if (!refreshed && epoch === sessionEpoch) { // A Session event may start a newer snapshot while this @@ -1081,23 +1123,31 @@ export function createWebStore( "The created Session could not be confirmed. Please try again.", ); } - const selected = get().snapshot?.selectedSession; - // A successful HTTP response alone cannot authorize a prompt: - // another browser may have activated a different Session meanwhile. - if ( - !selected || - selected.path !== receipt.sessionPath || - selected.cwd !== workspacePath || - selected.id !== get().snapshot?.currentSessionId - ) { - throw new Error( - "The created Session is no longer active in the selected workspace. Please try again.", + if (!targetMatchesSnapshot(target)) { + creationRetry = null; + showError( + new Error( + "The created Session is no longer active in the selected workspace. Please try again.", + ), ); + return; } set({ workspaceDraft: false, notice: null }); const draft = get().draftModel; - const sessionId = selected.id; - created = !draft || (await applyModel(draft, epoch, sessionId)); + if (draft && !(await applyModel(draft, epoch, target.sessionId))) { + return; + } + if (get().workspaceDraft || !targetMatchesSnapshot(target)) { + creationRetry = null; + showError( + new Error( + "The created Session is no longer active in the selected workspace. Please try again.", + ), + ); + return; + } + created = target; + creationRetry = null; } catch (error) { if (epoch !== sessionEpoch) return; set({ selectedPath: null }); @@ -1111,10 +1161,11 @@ export function createWebStore( }); sessionSelectionTail = creation.catch(() => undefined); await creation; - return created && epoch === sessionEpoch; + return epoch === sessionEpoch ? created : null; }, async selectSession(path) { if (!path) return; + creationRetry = null; const current = get(); clearCommandDiscovery(); set({ @@ -1352,15 +1403,34 @@ export function createWebStore( } const creating = get().workspaceDraft || !get().snapshot?.selectedSession?.id; - if (creating && !(await actions.createSession(workspace))) return false; + const createdTarget = creating + ? await actions.createSession(workspace) + : null; + if (creating && !createdTarget) { + if (!get().notice) { + set({ + notice: + "The active Session changed before the first message was sent. Your message was not sent.", + }); + } + return false; + } if (creating && get().draftModel) return false; - const sessionId = get().snapshot?.selectedSession?.id; + const selectedSession = get().snapshot?.selectedSession; + const target = + createdTarget ?? + (selectedSession + ? { + epoch: sessionEpoch, + sessionId: selectedSession.id, + sessionPath: selectedSession.path, + workspacePath: workspace, + } + : null); if ( - !sessionId || + !target || get().workspaceDraft || - get().selectedWorkspace !== workspace || - get().snapshot?.selectedSession?.cwd !== workspace || - sessionId !== get().snapshot?.currentSessionId || + !targetMatchesSnapshot(target) || get().sessionSwitching || get().promptAdmissionPending || (replacement && @@ -1370,16 +1440,12 @@ export function createWebStore( ) { return false; } - const epoch = sessionEpoch; + const { epoch, sessionId } = target; const draft = get().draftModel; if (draft && !(await applyModel(draft, epoch, sessionId))) return false; if ( - epoch !== sessionEpoch || - sessionId !== get().snapshot?.selectedSession?.id || - sessionId !== get().snapshot?.currentSessionId || get().workspaceDraft || - get().selectedWorkspace !== workspace || - get().snapshot?.selectedSession?.cwd !== workspace || + !targetMatchesSnapshot(target) || (replacement && (get().promptAdmissionRecovery?.commandId !== replacement.commandId ||