From 49fccc3a272d222652f0725c084b833cf9a05a88 Mon Sep 17 00:00:00 2001 From: testikun <320479488+testikun@users.noreply.github.com> Date: Tue, 8 Sep 2026 15:24:19 +0800 Subject: [PATCH 1/2] fix(web): bind first prompt to created session --- docs/design/README.md | 1 + docs/design/WEB_SESSION_CREATION_TARGET.md | 86 +++++++ tests/web/openpi-web.e2e.ts | 87 +++++++ tests/web/pi-adapter.test.ts | 5 +- tests/web/pi-runtime.test.ts | 4 +- tests/web/web-host.test.ts | 28 ++- tests/web/web-store.spec.ts | 249 ++++++++++++++++++--- web/dist/app.js | 2 +- web/host/web-host.ts | 1 + web/runtime/pi-runtime.ts | 3 + web/runtime/types.ts | 1 + web/ui/src/protocol/client.ts | 9 +- web/ui/src/store/web-store.ts | 143 ++++++++---- 13 files changed, 534 insertions(+), 85 deletions(-) create mode 100644 docs/design/WEB_SESSION_CREATION_TARGET.md diff --git a/docs/design/README.md b/docs/design/README.md index 8a3fa5da..b9671a33 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 - [`COMPLETION_INBOX.md`](COMPLETION_INBOX.md) — shared owner, epoch, consumption, retry, and receipt contract for background completions 开发与热更新流程见 [`docs/development/OPENPI_WEB_DEVELOPMENT.md`](../development/OPENPI_WEB_DEVELOPMENT.md)。 diff --git a/docs/design/WEB_SESSION_CREATION_TARGET.md b/docs/design/WEB_SESSION_CREATION_TARGET.md new file mode 100644 index 00000000..d2e5b93f --- /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: added when the implementation is published +- 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/openpi-web.e2e.ts b/tests/web/openpi-web.e2e.ts index 490bae4b..e94b3d25 100644 --- a/tests/web/openpi-web.e2e.ts +++ b/tests/web/openpi-web.e2e.ts @@ -851,3 +851,90 @@ test("workspace selection survives refresh and creates the exact native Session await rm(workspace, { recursive: true, force: true }); } }); + +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 }), + ), + ); + } +}); diff --git a/tests/web/pi-adapter.test.ts b/tests/web/pi-adapter.test.ts index 60be746f..02d6ac22 100644 --- a/tests/web/pi-adapter.test.ts +++ b/tests/web/pi-adapter.test.ts @@ -32,7 +32,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: () => [], setModel: async () => { diff --git a/tests/web/pi-runtime.test.ts b/tests/web/pi-runtime.test.ts index 482ad00c..2fa900e3 100644 --- a/tests/web/pi-runtime.test.ts +++ b/tests/web/pi-runtime.test.ts @@ -1127,7 +1127,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, { @@ -1162,12 +1162,14 @@ 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", }); 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 bddddbbb..d3e73f5d 100644 --- a/tests/web/web-host.test.ts +++ b/tests/web/web-host.test.ts @@ -126,6 +126,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 } : {}), }; }, @@ -742,7 +743,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`, { @@ -751,7 +756,11 @@ 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"]); @@ -851,7 +860,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: () => [], setModel: async () => { @@ -951,7 +963,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: () => [], setModel: async () => { @@ -1081,7 +1096,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: () => [], setModel: async () => { diff --git a/tests/web/web-store.spec.ts b/tests/web/web-store.spec.ts index 173e98db..d8871b91 100644 --- a/tests/web/web-store.spec.ts +++ b/tests/web/web-store.spec.ts @@ -8,6 +8,7 @@ import type { } from "../../web/protocol/types.ts"; import { type CommandReceipt, + type SessionCreationResult, type SessionMutationResult, WebApiError, WebClient, @@ -145,9 +146,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", @@ -182,7 +183,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) { @@ -753,8 +761,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(); @@ -762,7 +770,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"]); @@ -779,8 +791,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, @@ -798,7 +810,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(); @@ -820,6 +837,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( @@ -898,7 +957,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); @@ -941,7 +1003,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( @@ -966,6 +1031,68 @@ 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); + }); + + 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())); @@ -1238,6 +1365,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"); @@ -1292,8 +1443,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( @@ -1302,7 +1453,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); @@ -1335,8 +1490,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 })), ); @@ -1345,7 +1500,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(); @@ -1366,7 +1526,9 @@ describe("workspace selection authority", () => { 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"); @@ -1374,7 +1536,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 })), ); @@ -1388,10 +1555,10 @@ describe("workspace selection authority", () => { 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), @@ -1401,7 +1568,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); @@ -1417,14 +1589,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 }), }, ])( @@ -1432,7 +1608,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([]); @@ -1456,13 +1633,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); @@ -1488,7 +1670,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 01ddf819..7d92b015 100644 --- a/web/dist/app.js +++ b/web/dist/app.js @@ -86,4 +86,4 @@ Try polyfilling it using "@formatjs/intl-pluralrules" `,t);continue}let r=e.charCodeAt(t);if(Rb(e,t,r)){let r=e.charCodeAt(t+5)===Fb?t+6:t+5,o=e.slice(r,n);if(f===0&&e.charCodeAt(n+1)===Nb){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 zb(e,t,r)?p=e.slice(e.charCodeAt(t+6)===Fb?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 Rb(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 zb(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 Bb(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 Vb(`event replay expired`);if(!i.ok||!i.body)throw Error(`event connection failed`);e.onConnected();let a=e.cursor,o=0,s=Lb({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 Vb(`invalid event cursor`);if(!(n.sequence<=a)){if(n.sequence!==a+1)throw new Vb(`event cursor gap`);if(a=n.sequence,n.type===`state_invalidated`)throw new Vb(`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)}}var Ub=`openpi.collapsed-workspaces`,Wb=`openpi.sidebar-collapsed`,Gb=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 Kb(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 qb(e){try{return window.sessionStorage.getItem(e)===`true`}catch{return!1}}function Jb(e,t){try{window.sessionStorage.setItem(e,JSON.stringify(t))}catch{}}function Yb(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 Xb(e=new Pc,t={}){let n=t.consumeEvents??Hb,r=0,i=0,a=0,o=null,s=null,c=null,l=Promise.resolve(),u=null,d=!1,f=!1,p=null,m=new Set,h=new Set,g=(e,t)=>{if(typeof t==`string`)for(e.add(t);e.size>32;){let t=e.values().next().value;t&&e.delete(t)}},_=()=>({activeTurn:null,turnCancellationPending:!1,turnTerminalStatus:null,pendingFollowUpsReceipt:null,liveMessages:[],liveRunning:!1,livePhase:`idle`,liveRetry:null,thinkingStarts:{},thinkingDurations:{}}),v=(e,t)=>({liveRunning:t===`running`||!e,livePhase:t===`running`?`running`:e?`idle`:`preparing`});return dn((t,y)=>{let b=e=>{t({notice:e instanceof Error?e.message:String(e)})},x=async(n,i,a)=>{if(y().modelSelectionPending)return!1;t({modelSelectionPending:!0});try{let o=await e.selectModel(n.provider,n.id,a);if(i!==r||a!==y().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 y().actions.refreshSnapshot({epoch:i})||i!==r||a!==y().snapshot?.selectedSession?.id)return!1;let s=y().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===y().snapshot?.selectedSession?.id&&b(e),!1}finally{i===r&&t({modelSelectionPending:!1})}},S=(e=160)=>{if(d){f=!0;return}u===null&&(u=window.setTimeout(async()=>{u=null,d=!0;try{await y().actions.refreshSnapshot()}finally{d=!1,f&&(f=!1,S())}},e))},C=e=>{let n=y(),i=e.detail??{},a=i.sessionId,l=[`session_start`,`session_switched`,`session_created`].includes(e.type);if(t({cursor:e.sequence}),n.sessionSwitching&&!l){S();return}if(typeof a==`string`&&a!==n.snapshot?.currentSessionId&&!l){S();return}if(l){let a=i.commandId;if(typeof a==`string`&&h.has(a)){S();return}let l=i.sessionPath,u=n.snapshot?.sessions.some(e=>e.path===l),d=!1;if(c?.kind===`select`?d=c.expectedPath===l:c?.kind===`create`&&c.commandId===a&&e.type===`session_switched`&&typeof l==`string`&&!u?(c.observedPath=l,d=!0):c?.kind===`create`&&c.commandId===a&&e.type===`session_created`&&typeof c.observedPath==`string`&&(d=!0),d&&c?.epoch!==r)return;if(d)t(_());else{let e=++r;o=null,s=null,t({..._(),promptAdmissionPending:!1,selectedPath:typeof l==`string`?l:null,draftModel:n.workspaceDraft?n.draftModel:null,modelSelectionPending:!1,sessionSwitching:!0}),y().actions.refreshSnapshot({epoch:e}).then(n=>{e===r&&t({selectedPath:n?y().selectedPath:null,sessionSwitching:!1})})}}else if(e.type===`prompt_accepted`){let e=m.has(String(i.commandId??``));t({...v(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`){g(m,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`)g(m,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})}e.type===`prompt_failed`&&(g(m,i.commandId),t({liveMessages:y().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}}),Gb.has(e.type)&&S()},w=async r=>{let i=500;for(;!r.aborted;){let a=!1;try{if(y().cursor===null){if(a=!0,!await y().actions.refreshSnapshot({resetCursor:!0}))throw Error(`snapshot unavailable`);a=!1}if(r.aborted)return;await n({client:e,cursor:y().cursor??0,onConnected:()=>{i=500,t({connection:`connected`,notice:null})},onEvent:C,onHeartbeat:()=>S(0),signal:r})}catch(e){if(r.aborted)return;t({connection:`reconnecting`}),!a&&await y().actions.refreshSnapshot({resetCursor:!0})||t(_()),await Yb(i,r),i=Math.min(i*2,5e3),e instanceof SyntaxError&&t({notice:`Invalid event data`})}}},T={start(){p||(p=new AbortController,w(p.signal))},stop(){p?.abort(),p=null,u!==null&&window.clearTimeout(u),u=null},async refreshSnapshot(n={}){let a=n.epoch??r,o=++i,c=y().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&&T.refreshSnapshot({...n,canonicalRetry:!0,epoch:a});let h=l.selectedSession?.cwd,g=l.workspaces.find(e=>e.current)?.path,v=l.workspaces.some(e=>e.path===y().selectedWorkspace)?y().selectedWorkspace:void 0,b=y().workspaceDraft?y().selectedWorkspace:l.workspaces.some(e=>e.path===h)?h:g??v??null,x=n.resetCursor;return t({...x?_():{},connection:y().connection===`connecting`?`connecting`:y().connection,cursor:x||y().cursor===null?l.cursor:Math.max(y().cursor??0,l.cursor),activeTurn:l.runtime.activeTurn??null,livePhase:l.runtime.status!==`running`&&!y().promptAdmissionPending&&!s?`idle`:y().livePhase,liveRetry:l.runtime.status!==`running`&&!y().promptAdmissionPending&&!s?null:y().liveRetry,liveRunning:l.runtime.status===`running`?!0:!y().promptAdmissionPending&&!s?!1:y().liveRunning,selectedPath:d?.path??l.selectedSession?.path??null,selectedWorkspace:b,snapshot:l}),!0}catch(e){return a!==r||o!==i?!1:(t({connection:`unavailable`}),b(e),!1)}},async chooseWorkspace(){let t=r;try{let n=await e.chooseWorkspace();if(t!==r||n.cancelled||!n.path)return;T.setWorkspace(n.path),await T.refreshSnapshot()}catch(e){t===r&&b(e)}},setWorkspace(e){let n=y();e===n.selectedWorkspace&&!n.sessionSwitching||(++r,o=null,s=null,t({..._(),selectedWorkspace:e,workspaceDraft:!0,sessionSwitching:!1,modelSelectionPending:!1,promptAdmissionPending:!1,notice:null}))},async renameWorkspace(t,n){try{await e.renameWorkspace(t,n),await T.refreshSnapshot()}catch(e){throw b(e),e}},async removeWorkspace(n){try{await e.removeWorkspace(n),t({selectedPath:null,selectedWorkspace:y().selectedWorkspace===n?null:y().selectedWorkspace}),await T.refreshSnapshot()}catch(e){b(e)}},async createSession(n){if(!n||y().modelSelectionPending)return!1;let i=++r,a=globalThis.crypto?.randomUUID?.()??`web-create-${Date.now()}-${i}`;o=null,s=null,t({..._(),mobileSidebarOpen:!1,promptAdmissionPending:!1,selectedPath:null,selectedWorkspace:n,workspaceDraft:!0,sessionSwitching:!0});let u=!1,d=l.then(async()=>{if(i===r){c={commandId:a,epoch:i,expectedPath:null,kind:`create`,observedPath:null};try{let o=await e.createSession(n,a);if(i!==r)return;if(o.cancelled||!o.sessionPath)throw Error(`Session creation was not confirmed. Please try again.`);t({selectedPath:null});let s=await T.refreshSnapshot({epoch:i});if(!s&&i===r&&(s=await T.refreshSnapshot({epoch:i})),i!==r)return;if(!s)throw Error(`The created Session could not be confirmed. Please try again.`);let c=y().snapshot?.selectedSession;if(!c||c.path!==o.sessionPath||c.cwd!==n||c.id!==y().snapshot?.currentSessionId)throw Error(`The created Session is no longer active in the selected workspace. Please try again.`);t({workspaceDraft:!1,notice:null});let l=y().draftModel,d=c.id;u=!l||await x(l,i,d)}catch(e){if(i!==r)return;t({selectedPath:null}),b(e),await T.refreshSnapshot({epoch:i})}finally{g(h,a),c?.epoch===i&&(c=null),i===r&&t({sessionSwitching:!1})}}});return l=d.catch(()=>void 0),await d,u&&i===r},async selectSession(n){if(!n)return;t({workspaceDraft:!1,draftModel:null,modelSelectionPending:!1});let i=++r;o=null,s=null,t({..._(),mobileSidebarOpen:!1,promptAdmissionPending:!1,selectedPath:n,sessionSwitching:!0});let a=l.then(async()=>{if(i===r){c={epoch:i,expectedPath:n,kind:`select`};try{if(await e.selectSession(n),i!==r)return;await T.refreshSnapshot({epoch:i})||t({selectedPath:null})}catch(e){if(i!==r)return;t({selectedPath:null}),b(e),await T.refreshSnapshot({epoch:i})}finally{c?.epoch===i&&(c=null),i===r&&t({sessionSwitching:!1})}}});l=a.catch(()=>void 0),await a},async renameSession(t,n){try{await e.renameSession(t,n),await T.refreshSnapshot()}catch(e){throw b(e),e}},async archiveSession(t){try{await e.archiveSession(t),await T.refreshSnapshot()}catch(e){b(e)}},async unarchiveSession(t){let n=r;try{return await e.unarchiveSession(t),n!==r||await T.refreshSnapshot({epoch:n})}catch(e){return n===r&&b(e),!1}},async selectModel(e){let[n,...i]=e.split(`/`),a=i.join(`/`),o=y();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.find(e=>e.provider===n&&e.id===a);e&&t({draftModel:e,notice:null});return}!s||s!==o.snapshot?.currentSessionId||await x({provider:n,id:a},r,s)},async cancelActiveTurn(){let n=y().activeTurn??y().snapshot?.runtime.activeTurn;if(!n||y().turnCancellationPending||y().sessionSwitching)return;let i=r;t({turnCancellationPending:!0});try{await e.cancelActiveTurn(n)}catch(e){if(i!==r)return;await T.refreshSnapshot({epoch:i}),i===r&&b(e)}finally{i===r&&t({turnCancellationPending:!1})}},async sendPrompt(n){let i=n.trim(),c=y().selectedWorkspace;if(!c||!i||y().sessionSwitching||y().promptAdmissionPending||y().modelSelectionPending)return!1;let l=y().workspaceDraft||!y().snapshot?.selectedSession?.id;if(l&&!await T.createSession(c)||l&&y().draftModel)return!1;let u=y().snapshot?.selectedSession?.id;if(!u||y().workspaceDraft||y().selectedWorkspace!==c||y().snapshot?.selectedSession?.cwd!==c||u!==y().snapshot?.currentSessionId||y().sessionSwitching||y().promptAdmissionPending)return!1;let d=r,f=y().draftModel;if(f&&!await x(f,d,u)||d!==r||u!==y().snapshot?.selectedSession?.id||u!==y().snapshot?.currentSessionId||y().workspaceDraft||y().selectedWorkspace!==c||y().snapshot?.selectedSession?.cwd!==c)return!1;let p=++a,h=s?.sessionId===u&&s.content===i,g=h?s.commandId:globalThis.crypto?.randomUUID?.()??`web-prompt-${Date.now()}-${p}`,_=h?s.optimisticKey:`optimistic-${g}`;s={sessionId:u,content:i,commandId:g,optimisticKey:_},o=p,t({liveMessages:h?y().liveMessages:[...y().liveMessages,{key:_,message:{role:`user`,content:i}}].slice(-8),notice:null,pendingFollowUpsReceipt:null,turnTerminalStatus:null,promptAdmissionPending:!0,scrollToBottom:y().scrollToBottom+1});try{let n=await e.prompt(u,i,g,h);if(d!==r||o!==p)return!1;let a=m.has(n.id);return s?.commandId===g&&(s=null),t({...v(a,y().livePhase),pendingFollowUpsReceipt:n.pendingFollowUps??null}),S(120),!0}catch(e){return d!==r||o!==p?!1:e instanceof Mc&&[`WORKSPACE_REQUIRED`,`SESSION_CONFLICT`,`PROMPT_REJECTED`,`COMMAND_CONFLICT`,`PROMPT_ADMISSION_CAPACITY`].includes(e.code??``)?(s?.commandId===g&&(s=null),t({liveMessages:y().liveMessages.filter(e=>e.key!==_),livePhase:`idle`,liveRetry:null,liveRunning:!1}),b(e),!1):(t({liveRunning:!0,livePhase:y().livePhase===`running`?`running`:`preparing`,liveRetry:null}),b(e),!1)}finally{d===r&&o===p&&(o=null,t({promptAdmissionPending:!1}))}},setQuery(e){t({query:e})},setSearchOpen(e){t({searchOpen:e,...e?{}:{query:``}})},toggleWorkspace(e){let n=new Set(y().collapsed);n.has(e)?n.delete(e):n.add(e),Jb(Ub,[...n]),t({collapsed:n})},toggleSidebar(e){if(e){t({mobileSidebarOpen:!y().mobileSidebarOpen});return}let n=!y().sidebarCollapsed;try{window.sessionStorage.setItem(Wb,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,collapsed:Kb(Ub),sidebarCollapsed:qb(Wb),mobileSidebarOpen:!1,query:``,searchOpen:!1,connection:`connecting`,notice:null,liveMessages:[],liveRunning:!1,livePhase:`idle`,liveRetry:null,thinkingStarts:{},thinkingDurations:{},promptAdmissionPending:!1,sessionSwitching:!1,scrollToBottom:0,actions:T}})}var Zb=Xb();function Qb(){let e=pn(Zb),{t}=cn(),{actions:n}=e,[r,i]=(0,w.useState)(`chat`),[a,o]=(0,w.useState)(null),s=e.snapshot?.models.find(e=>e.current),c=JSON.stringify([s?.provider,s?.id]),l=t=>{let n=e.snapshot,r=n?.selectedSession;e.workspaceDraft||!n||!r||e.sessionSwitching||r.id!==n.currentSessionId||o({sessionId:r.id,sessionPath:r.path,cwd:r.cwd,model:s?.label??``,modelKey:c,terminalId:t})},u=a&&!e.workspaceDraft&&!e.sessionSwitching&&a.sessionId===e.snapshot?.currentSessionId&&a.sessionPath===e.snapshot?.selectedSession?.path&&a.modelKey===c;(0,w.useEffect)(()=>{a&&!u&&o(null)},[a,u]),(0,w.useEffect)(()=>(n.start(),n.stop),[n]);let d=e.workspaceDraft?void 0:e.snapshot?.selectedSession,f=d?.entries.some(e=>e.type===`message`&&e.message)||e.liveMessages.length>0,p=!d||!f,m=(0,w.useCallback)(e=>n.sendPrompt(e),[n]);return(0,L.jsxs)(`div`,{className:`app-shell ${e.sidebarCollapsed?`sidebar-collapsed`:``} ${e.mobileSidebarOpen?`sidebar-open`:``}`,children:[(0,L.jsx)(iu,{snapshot:e.snapshot,selectedPath:e.workspaceDraft?null:e.selectedPath,selectedWorkspace:e.selectedWorkspace,collapsed:e.collapsed,query:e.query,searchOpen:e.searchOpen,mobileOpen:e.mobileSidebarOpen,actions:n}),e.sidebarCollapsed&&(0,L.jsx)(`button`,{className:`sidebar-expand`,type:`button`,"aria-label":t(`expandSidebar`),title:t(`expandSidebar`),onClick:()=>n.toggleSidebar(!1),children:(0,L.jsx)(Te,{})}),(0,L.jsxs)(`main`,{className:`conversation-shell ${d?`has-view`:``} ${p&&(e.workspaceDraft||r===`chat`)?`landing`:``}`,children:[(0,L.jsx)(`h1`,{className:`sr-only`,children:`OpenPI`}),(0,L.jsxs)(`header`,{className:`mobile-header`,children:[(0,L.jsx)(`button`,{type:`button`,"aria-label":t(`openSidebar`),onClick:()=>n.toggleSidebar(!0),children:(0,L.jsx)(Ce,{})}),(0,L.jsx)(`span`,{className:`connection-state ${e.connection}`,children:t(e.connection)})]}),d&&(0,L.jsxs)(`fieldset`,{className:`conversation-view-switch`,"aria-label":t(`conversationView`),children:[(0,L.jsx)(`button`,{type:`button`,"aria-pressed":r===`chat`,onClick:()=>i(`chat`),children:t(`chatView`)}),(0,L.jsx)(`button`,{type:`button`,"aria-pressed":r===`trajectory`,onClick:()=>i(`trajectory`),children:t(`trajectory`)})]}),e.sessionSwitching?(0,L.jsx)(`div`,{className:`conversation switching`,role:`status`,children:(0,L.jsxs)(`div`,{className:`conversation-running`,children:[(0,L.jsx)(`span`,{className:`conversation-running-dot`}),(0,L.jsx)(`span`,{children:t(`switchingSession`)})]})}):r===`trajectory`&&d&&e.snapshot?(0,L.jsx)(su,{snapshot:e.snapshot,running:e.liveRunning},d.path):p?(0,L.jsx)(`section`,{className:`conversation landing-conversation`,"aria-label":`Conversation`,children:(0,L.jsx)(`div`,{className:`landing-welcome`,children:(0,L.jsx)(gn,{animated:!0})})}):e.snapshot?(0,L.jsx)(jb,{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:m}):null,(0,L.jsx)(nu,{workspaceDraft:e.workspaceDraft,draftModel:e.draftModel,modelSelectionPending:e.modelSelectionPending,onInspect:l,activeTurn:e.activeTurn,turnCancellationPending:e.turnCancellationPending,turnTerminalStatus:e.turnTerminalStatus,pendingFollowUpsReceipt:e.pendingFollowUpsReceipt,snapshot:e.snapshot,selectedWorkspace:e.selectedWorkspace,sessionSwitching:e.sessionSwitching,promptAdmissionPending:e.promptAdmissionPending,liveRunning:e.liveRunning,landing:p,actions:n}),e.notice&&(0,L.jsxs)(`div`,{className:`notice`,role:`alert`,children:[(0,L.jsx)(`span`,{children:e.notice}),(0,L.jsx)(`button`,{type:`button`,"aria-label":t(`close`),onClick:n.clearNotice,children:(0,L.jsx)(ze,{})})]})]}),u&&(0,L.jsx)(Fc,{target:a,onClose:()=>o(null)},`${a.sessionId}:${a.sessionPath}:${a.terminalId??`status`}`),(0,L.jsx)(`button`,{className:`sidebar-scrim`,type:`button`,"aria-label":t(`close`),onClick:n.closeMobileSidebar})]})}var $b={base:{k1xSpc:`xjp7ctv`,kMwMTN:`x1tgivj0`,kMv6JI:`x9ynric`,$$css:!0},light:{kQNsl9:`x19aimcq`,$$css:!0},dark:{kQNsl9:`xntwwlm`,$$css:!0},system:{kQNsl9:`x108lcm5`,$$css:!0}},ex=w.createContext(!1);ex.displayName=`ThemeNestingContext`;var tx=new Set,nx=0;function rx(e){let t=(0,w.useId)();(0,w.useInsertionEffect)(()=>{if(e.__built)return;let n=`astryx-theme-${e.name}`;if(tx.has(n))return;`${e.name}`,`${e.name}${e.name}${e.name}${e.name}`;let{prose:r,component:i}=ic(e),a=rc();tx.add(n);let o=[()=>tx.delete(n)];if(a){if(nx++===0){let e=document.createElement(`style`);e.setAttribute(Ir(`theme-base`),``),e.textContent=`@layer astryx-base {\n${a}\n}`,document.head.appendChild(e)}o.push(()=>{--nx===0&&document.querySelector(`style[${Ir(`theme-base`)}]`)?.remove()})}if(r){let n=document.createElement(`style`);n.setAttribute(Ir(`theme-prose`),e.name),n.setAttribute(Ir(`id`),t),n.textContent=`@layer reset {\n${r}\n}`,document.head.appendChild(n)}if(i){let n=document.createElement(`style`);n.setAttribute(Ir(`theme`),e.name),n.setAttribute(Ir(`id`),t),n.textContent=`@layer astryx-theme {\n${i}\n}`,document.head.appendChild(n)}return(r||i)&&o.push(()=>{let n=document.querySelector(`style[${Ir(`theme-prose`)}="${e.name}"][${Ir(`id`)}="${t}"]`),r=document.querySelector(`style[${Ir(`theme`)}="${e.name}"][${Ir(`id`)}="${t}"]`);n?.remove(),r?.remove()}),()=>{for(let e of o)e()}},[e,t])}function ix(e,t,n){xl(()=>{if(!e&&!(typeof document>`u`))return t===`light`||t===`dark`?document.documentElement.setAttribute(`data-theme`,t):document.documentElement.removeAttribute(`data-theme`),document.documentElement.setAttribute(Ir(`theme`),n),()=>{document.documentElement.removeAttribute(`data-theme`),document.documentElement.removeAttribute(Ir(`theme`))}},[e,t,n])}function ax({theme:e,mode:t=`system`,children:n}){let r=(0,w.use)(ex);js(e),rx(e),ix(r,t,e.name);let i=t===`dark`?$b.dark:t===`light`?$b.light:$b.system,a=(0,w.useMemo)(()=>({theme:e,mode:t}),[e,t]);return(0,L.jsx)(oc,{value:a,children:(0,L.jsx)(ex,{value:!0,children:(0,L.jsx)(`div`,{...xn($b.base,i),"data-astryx-theme":e.name,"data-theme":t===`system`?void 0:t,children:n})})})}ax.displayName=`Theme`;var ox={size:`1em`,"aria-hidden":!0},sx={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,L.jsx)(ze,{...ox}),chevronDown:(0,L.jsx)(re,{...ox}),chevronLeft:(0,L.jsx)(ie,{...ox}),chevronRight:(0,L.jsx)(ae,{...ox}),chevronsLeft:(0,L.jsx)(oe,{...ox}),chevronsRight:(0,L.jsx)(se,{...ox}),check:(0,L.jsx)(P,{...ox}),success:(0,L.jsx)(F,{...ox}),error:(0,L.jsx)(ce,{...ox}),warning:(0,L.jsx)(Ie,{...ox}),info:(0,L.jsx)(xe,{...ox}),calendar:(0,L.jsx)(ne,{...ox}),clock:(0,L.jsx)(ue,{...ox}),externalLink:(0,L.jsx)(me,{...ox}),menu:(0,L.jsx)(Ce,{...ox}),moreHorizontal:(0,L.jsx)(pe,{...ox}),search:(0,L.jsx)(ke,{...ox}),arrowUp:(0,L.jsx)(j,{...ox}),arrowDown:(0,L.jsx)(k,{...ox}),arrowsUpDown:(0,L.jsx)(A,{...ox}),funnel:(0,L.jsx)(ye,{...ox}),eyeSlash:(0,L.jsx)(he,{...ox}),viewColumns:(0,L.jsx)(de,{...ox}),copy:(0,L.jsx)(fe,{...ox}),checkDouble:(0,L.jsx)(N,{...ox}),wrench:(0,L.jsx)(Re,{...ox}),stop:(0,L.jsx)(Ne,{...ox}),microphone:(0,L.jsx)(we,{...ox})}},cx={en:{translation:{conversationView:`Conversation view`,chatView:`Chat`,trajectory:`Trajectory`,trajectoryScope:`Recorded Session messages in order, not execution durations or the complete model request.`,trajectoryRunning:`Work is active. This view updates as saved records arrive; in-flight text is available in Chat.`,trajectoryTruncated:`Loaded history is partial: {{entries}} entries omitted, {{parts}} parts omitted, {{messages}} messages truncated.`,trajectoryEmpty:`No saved records yet.`,trajectoryOverview:`Record sequence overview`,trajectoryEarlier:`Show earlier records ({{count}} remaining in loaded history)`,trajectoryDetails:`Record details`,trajectoryRecordedAt:`Record timestamp`,trajectoryEvidenceTruncated:`This evidence was truncated in the Session projection.`,trajectoryArguments:`Tool arguments`,trajectoryRecordedContent:`Recorded content`,trajectoryThinking:`Recorded model reasoning`,trajectoryOutput:`Tool result`,trajectoryMissingResult:`No unambiguous result is included in the loaded records. This does not imply the tool is still running.`,trajectoryStructured:`Structured result`,trajectoryEventOnly:`Only this event’s type and timestamp are available.`,trajectory_user:`User prompt`,trajectory_assistant:`Assistant`,trajectory_call:`Tool call`,trajectory_result:`Unpaired result`,trajectory_event:`Session event`,trajectory_returned:`Tool returned successfully; background work may still be active.`,trajectory_error:`Tool returned an error`,trajectory_unknown:`Outcome not established`,runtimeStatus:`Runtime status`,refreshStatus:`Refresh status`,terminalDetails:`Terminal details`,inspectionChanged:`The active session changed. Reopen this panel.`,inspectionUnavailable:`Details are unavailable.`,inspectionLoading:`Loading status…`,thinkingUnavailable:`Thinking state is unavailable.`,trustUnavailable:`Project trust status is unavailable.`,authUnavailable:`Provider status is unavailable.`,executionState:`State`,terminalCommand:`Command`,terminalDirectory:`Directory`,startedAt:`Started`,exitCode:`Exit code`,detailTruncated:`Some details are shortened to keep this view bounded.`,standardOutput:`Standard output`,standardError:`Standard error`,outputTruncated:`{{count}} bytes omitted from this output view.`,outputRecovery:`The runtime reports a retained log. This view shows only a bounded excerpt.`,modelAndThinking:`Model and reasoning`,selectedModel:`Current model`,thinkingLevel:`Thinking level`,availableThinking:`Available levels`,unknownState:`Unknown`,projectTrust:`Project trust`,trust_trusted:`This session trusts the workspace.`,trust_untrusted:`Trust for this workspace has been denied.`,trust_restricted:`Project resources are restricted pending a trust decision.`,trust_unknown:`Trust state is not available.`,trustRefreshNeeded:`The saved decision and active session differ. Refresh the session through Pi to apply it.`,providerAvailability:`Provider credentials`,credentialConfigured:`Configured`,credentialMissing:`Not configured`,noProviders:`No providers reported.`,providersBounded:`This provider list is truncated.`,authNotVerified:`Configured credentials do not guarantee a successful model request.`,configurationViaPi:`Read-only status. Manage model credentials and trust through Pi; manage OpenPI options with /openpi-setup.`,statusCaptured:`Snapshot at {{time}} · refresh for the latest state`,conversationViews:`Conversation views`,currentConversations:`Current`,archivedConversations:`Archived`,restoreConversation:`Restore conversation`,restoringConversation:`Restoring…`,loadedArchives:`Archived conversations in the loaded history`,loadedHistoryBounded:`{{sessions}} more sessions and {{workspaces}} workspace summaries are not loaded. Search covers the loaded list only.`,noLoadedArchives:`No archived conversations in this loaded list.`,restoreFailed:`Could not confirm restoration. Refresh and try again.`,execution_running:`Running`,execution_done:`Completed`,execution_killed:`Stopped`,execution_timed_out:`Timed out`,execution_failed:`Failed`,execution_uncertain:`Uncertain`,activeOnlyHint:`Only the active Web session accepts messages.`,acceptedHint:`Message accepted by OpenPI Web.`,stopTurn:`Stop current turn`,stoppingTurn:`Stopping current turn…`,stoppedTurn:`Current turn stopped.`,pendingFollowUpsHint:`{{count}} messages queued`,addWorkspace:`Add workspace`,addWorkspaceMenu:`Add workspace...`,archiveConversation:`Archive conversation`,cancel:`Cancel`,chooseWorkspaceHint:`Choose a workspace and describe the work`,close:`Close`,closeSearch:`Close search`,collapseSidebar:`Collapse sidebar`,confirmEdit:`OK`,connected:`Connected`,connecting:`Connecting`,conversationName:`Conversation name`,conversationOptions:`Conversation options`,conversationTurns:`Conversation turns`,copiedMessage:`Copied`,copyMessage:`Copy message`,copyFailed:`Copy failed. Please select and copy the message manually.`,deleteWorkspace:`Delete workspace`,describeTask:`Describe a task`,editMessage:`Edit message`,enterHint:`Enter to send, Shift+Enter for a new line.`,expandSidebar:`Expand sidebar`,importWorkspace:`Import workspace`,loadingModels:`Loading models...`,modelPreparing:`Preparing task...`,modelRetrying:`Retrying model request...`,modelRunning:`Working...`,newSession:`New session`,noConversations:`No conversations yet`,noMatching:`No matching conversations`,noModels:`No models available`,noOutput:`no output`,noSessions:`No sessions`,openSidebar:`Open sidebar`,promptMessage:`Send a message to the active Web session`,promptReadonly:`A non-active session cannot receive prompts`,promptStart:`Choose a workspace to begin.`,promptTask:`Describe what you want to build.`,queuedHint:`The message will be queued after the current turn.`,reconnecting:`Reconnecting`,removeWorkspace:`Remove from sidebar`,renameConversation:`Rename conversation`,renameWorkspace:`Rename workspace`,save:`Save`,searchConversations:`Search conversations`,searchPlaceholder:`Search conversations...`,selectModel:`Select model`,selectWorkspace:`Select workspace`,send:`Send`,stepsLabel:`steps`,switchingSession:`Switching session...`,thinkingActive:`Thinking...`,thinkingDone:`Thinking`,unavailable:`Unavailable`,ungrouped:`Ungrouped`,untitledSession:`New session`,workspaceDeleteConfirm:`The folder and conversation records will be kept. Its conversations will move to Ungrouped.`,workspaceName:`Workspace name`,workspaces:`Workspaces`}},zh:{translation:{conversationView:`会话视图`,chatView:`对话`,trajectory:`执行轨迹`,trajectoryScope:`按记录顺序展示会话消息,不表示执行耗时,也不是完整模型请求。`,trajectoryRunning:`任务仍在进行。已保存记录到达后自动更新;实时生成内容可在“对话”中查看。`,trajectoryTruncated:`当前历史不完整:省略 {{entries}} 条记录、{{parts}} 个内容片段,{{messages}} 条消息被截断。`,trajectoryEmpty:`尚无已保存记录。`,trajectoryOverview:`记录顺序总览`,trajectoryEarlier:`显示更早记录(已加载历史中还有 {{count}} 条)`,trajectoryDetails:`记录详情`,trajectoryRecordedAt:`记录时间`,trajectoryEvidenceTruncated:`此证据在会话投影中已被截断。`,trajectoryArguments:`工具参数`,trajectoryRecordedContent:`已记录内容`,trajectoryThinking:`模型返回的推理内容`,trajectoryOutput:`工具结果`,trajectoryMissingResult:`当前记录中没有可明确配对的结果。这不表示工具仍在运行。`,trajectoryStructured:`结构化结果`,trajectoryEventOnly:`当前仅提供此事件的类型和记录时间。`,trajectory_user:`用户 Prompt`,trajectory_assistant:`模型消息`,trajectory_call:`工具调用`,trajectory_result:`未配对结果`,trajectory_event:`会话事件`,trajectory_returned:`工具已成功返回;其启动的后台任务可能仍在进行。`,trajectory_error:`工具返回错误`,trajectory_unknown:`尚无法确定结果`,runtimeStatus:`运行状态`,refreshStatus:`刷新状态`,terminalDetails:`终端详情`,inspectionChanged:`当前会话已切换,请重新打开详情。`,inspectionUnavailable:`暂时无法读取详情。`,inspectionLoading:`正在读取状态…`,thinkingUnavailable:`暂时无法读取思考等级。`,trustUnavailable:`暂时无法读取项目信任状态。`,authUnavailable:`暂时无法读取服务商状态。`,executionState:`状态`,terminalCommand:`命令`,terminalDirectory:`目录`,startedAt:`启动时间`,exitCode:`退出码`,detailTruncated:`部分详情已截断,以限制页面加载量。`,standardOutput:`标准输出`,standardError:`错误输出`,outputTruncated:`此输出视图省略了 {{count}} 字节。`,outputRecovery:`运行时报告已保留日志;此处仅显示有大小限制的片段。`,modelAndThinking:`模型与思考`,selectedModel:`当前模型`,thinkingLevel:`思考等级`,availableThinking:`支持的等级`,unknownState:`未知`,projectTrust:`项目信任`,trust_trusted:`当前会话信任此工作区。`,trust_untrusted:`此工作区的信任已被拒绝。`,trust_restricted:`项目资源受到限制,等待信任决定。`,trust_unknown:`尚无法确定信任状态。`,trustRefreshNeeded:`保存的信任决定与当前会话不同,请通过 Pi 刷新会话后生效。`,providerAvailability:`服务商凭据`,credentialConfigured:`已配置`,credentialMissing:`未配置`,noProviders:`未发现服务商。`,providersBounded:`服务商列表已截断。`,authNotVerified:`凭据已配置不代表模型请求一定成功。`,configurationViaPi:`此处为只读状态。模型凭据与信任由 Pi 管理,OpenPI 选项通过 /openpi-setup 配置。`,statusCaptured:`采集于 {{time}} · 刷新查看最新状态`,conversationViews:`会话视图`,currentConversations:`当前`,archivedConversations:`已归档`,restoreConversation:`恢复会话`,restoringConversation:`正在恢复…`,loadedArchives:`已加载历史中的归档会话`,loadedHistoryBounded:`另有 {{sessions}} 个会话和 {{workspaces}} 个工作区摘要未加载。搜索仅覆盖已加载列表。`,noLoadedArchives:`已加载列表中没有归档会话。`,restoreFailed:`暂时无法确认恢复结果,请刷新后重试。`,execution_running:`运行中`,execution_done:`已完成`,execution_killed:`已停止`,execution_timed_out:`已超时`,execution_failed:`失败`,execution_uncertain:`状态不确定`,activeOnlyHint:`只有当前 Web 会话可以接收消息。`,stopTurn:`停止当前轮次`,stoppingTurn:`正在停止当前轮次…`,stoppedTurn:`当前轮次已停止。`,pendingFollowUpsHint:`{{count}} 条消息正在排队`,acceptedHint:`OpenPI Web 已接收消息。`,addWorkspace:`添加工作区`,addWorkspaceMenu:`添加工作区...`,archiveConversation:`归档会话`,cancel:`取消`,chooseWorkspaceHint:`选择工作区并描述任务`,close:`关闭`,closeSearch:`关闭搜索`,collapseSidebar:`收起侧边栏`,confirmEdit:`确定`,connected:`已连接`,connecting:`正在连接`,conversationName:`会话名称`,conversationOptions:`会话选项`,conversationTurns:`会话轮次`,copiedMessage:`已复制`,copyMessage:`复制消息`,copyFailed:`复制失败,请选中消息后手动复制。`,deleteWorkspace:`删除工作区`,describeTask:`描述任务`,editMessage:`编辑消息`,enterHint:`按 Enter 发送,Shift+Enter 换行。`,expandSidebar:`展开侧边栏`,importWorkspace:`导入工作区`,loadingModels:`正在加载模型...`,modelPreparing:`正在准备任务...`,modelRetrying:`模型请求重试中...`,modelRunning:`正在运行...`,newSession:`新建会话`,noConversations:`暂无对话`,noMatching:`没有匹配的会话`,noModels:`没有可用模型`,noOutput:`无输出`,noSessions:`暂无会话`,openSidebar:`打开侧边栏`,promptMessage:`向当前 Web 会话发送消息`,promptReadonly:`非当前会话不能接收消息`,promptStart:`选择一个工作区开始`,promptTask:`描述你想要构建的任务`,queuedHint:`当前回合结束后将发送消息。`,reconnecting:`正在重连`,removeWorkspace:`从侧边栏移除`,renameConversation:`重命名会话`,renameWorkspace:`重命名工作区`,save:`保存`,searchConversations:`搜索会话`,searchPlaceholder:`搜索会话...`,selectModel:`选择模型`,selectWorkspace:`选择工作区`,send:`发送`,stepsLabel:`个步骤`,switchingSession:`正在切换会话...`,thinkingActive:`思考中...`,thinkingDone:`思考过程`,unavailable:`不可用`,ungrouped:`未分组`,untitledSession:`新会话`,workspaceDeleteConfirm:`文件夹与会话记录会保留,其中的会话会被放到“未分组”;再次打开此目录时将是一个干净的工作区。`,workspaceName:`工作区名称`,workspaces:`工作区`}}},lx=navigator.language?.toLowerCase().startsWith(`zh`)?`zh`:`en`;Ft.use(en).init({fallbackLng:`en`,initAsync:!1,interpolation:{escapeValue:!1},lng:lx,resources:cx}),document.documentElement.lang=lx===`zh`?`zh-CN`:`en`;function ux({children:e}){let t=pn(Zb,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,L.jsx)(ln,{i18n:Ft,children:(0,L.jsx)(ax,{theme:sx,mode:i,children:e})})}var dx=document.getElementById(`root`);if(!dx)throw Error(`OpenPI Web root is missing`);(0,Be.createRoot)(dx).render((0,L.jsx)(w.StrictMode,{children:(0,L.jsx)(ux,{children:(0,L.jsx)(Qb,{})})})); \ 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 Rb(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 zb(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 Bb(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 Vb(`event replay expired`);if(!i.ok||!i.body)throw Error(`event connection failed`);e.onConnected();let a=e.cursor,o=0,s=Lb({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 Vb(`invalid event cursor`);if(!(n.sequence<=a)){if(n.sequence!==a+1)throw new Vb(`event cursor gap`);if(a=n.sequence,n.type===`state_invalidated`)throw new Vb(`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)}}var Ub=`openpi.collapsed-workspaces`,Wb=`openpi.sidebar-collapsed`,Gb=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 Kb(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 qb(e){try{return window.sessionStorage.getItem(e)===`true`}catch{return!1}}function Jb(e,t){try{window.sessionStorage.setItem(e,JSON.stringify(t))}catch{}}function Yb(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 Xb(e=new Pc,t={}){let n=t.consumeEvents??Hb,r=0,i=0,a=0,o=null,s=null,c=null,l=Promise.resolve(),u=null,d=!1,f=!1,p=null,m=new Set,h=new Set,g=(e,t)=>{if(typeof t==`string`)for(e.add(t);e.size>32;){let t=e.values().next().value;t&&e.delete(t)}},_=()=>({activeTurn:null,turnCancellationPending:!1,turnTerminalStatus:null,pendingFollowUpsReceipt:null,liveMessages:[],liveRunning:!1,livePhase:`idle`,liveRetry:null,thinkingStarts:{},thinkingDurations:{}}),v=(e,t)=>({liveRunning:t===`running`||!e,livePhase:t===`running`?`running`:e?`idle`:`preparing`});return dn((t,y)=>{let b=e=>{t({notice:e instanceof Error?e.message:String(e)})},x=e=>{let t=y().snapshot;return e.epoch===r&&y().selectedWorkspace===e.workspacePath&&t?.currentSessionId===e.sessionId&&t.selectedSession?.id===e.sessionId&&t.selectedSession.cwd===e.workspacePath&&(!e.sessionPath||t.selectedSession.path===e.sessionPath)},S=async(n,i,a)=>{if(y().modelSelectionPending)return!1;t({modelSelectionPending:!0});try{let o=await e.selectModel(n.provider,n.id,a);if(i!==r||a!==y().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 y().actions.refreshSnapshot({epoch:i})||i!==r||a!==y().snapshot?.selectedSession?.id)return!1;let s=y().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===y().snapshot?.selectedSession?.id&&b(e),!1}finally{i===r&&t({modelSelectionPending:!1})}},C=(e=160)=>{if(d){f=!0;return}u===null&&(u=window.setTimeout(async()=>{u=null,d=!0;try{await y().actions.refreshSnapshot()}finally{d=!1,f&&(f=!1,C())}},e))},w=e=>{let n=y(),i=e.detail??{},a=i.sessionId,l=[`session_start`,`session_switched`,`session_created`].includes(e.type);if(t({cursor:e.sequence}),n.sessionSwitching&&!l){C();return}if(typeof a==`string`&&a!==n.snapshot?.currentSessionId&&!l){C();return}if(l){let l=i.commandId;if(typeof l==`string`&&h.has(l)){C();return}let u=i.sessionPath,d=n.snapshot?.sessions.some(e=>e.path===u),f=!1;if(c?.kind===`select`?f=c.expectedPath===u:(c?.kind===`create`&&c.commandId===l&&e.type===`session_switched`&&typeof a==`string`&&(!c.expectedSessionId||c.expectedSessionId===a)&&(typeof u!=`string`||!d)||c?.kind===`create`&&c.commandId===l&&e.type===`session_created`&&typeof a==`string`&&(!c.expectedSessionId||a===c.expectedSessionId))&&(f=!0),f&&c?.epoch!==r)return;if(f)t(_());else{let e=++r;o=null,s=null,t({..._(),promptAdmissionPending:!1,selectedPath:typeof u==`string`?u:null,draftModel:n.workspaceDraft?n.draftModel:null,modelSelectionPending:!1,sessionSwitching:!0}),y().actions.refreshSnapshot({epoch:e}).then(n=>{e===r&&t({selectedPath:n?y().selectedPath:null,sessionSwitching:!1})})}}else if(e.type===`prompt_accepted`){let e=m.has(String(i.commandId??``));t({...v(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`){g(m,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`)g(m,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})}e.type===`prompt_failed`&&(g(m,i.commandId),t({liveMessages:y().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}}),Gb.has(e.type)&&C()},T=async r=>{let i=500;for(;!r.aborted;){let a=!1;try{if(y().cursor===null){if(a=!0,!await y().actions.refreshSnapshot({resetCursor:!0}))throw Error(`snapshot unavailable`);a=!1}if(r.aborted)return;await n({client:e,cursor:y().cursor??0,onConnected:()=>{i=500,t({connection:`connected`,notice:null})},onEvent:w,onHeartbeat:()=>C(0),signal:r})}catch(e){if(r.aborted)return;t({connection:`reconnecting`}),!a&&await y().actions.refreshSnapshot({resetCursor:!0})||t(_()),await Yb(i,r),i=Math.min(i*2,5e3),e instanceof SyntaxError&&t({notice:`Invalid event data`})}}},E={start(){p||(p=new AbortController,T(p.signal))},stop(){p?.abort(),p=null,u!==null&&window.clearTimeout(u),u=null},async refreshSnapshot(n={}){let a=n.epoch??r,o=++i,c=y().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&&E.refreshSnapshot({...n,canonicalRetry:!0,epoch:a});let h=l.selectedSession?.cwd,g=l.workspaces.find(e=>e.current)?.path,v=l.workspaces.some(e=>e.path===y().selectedWorkspace)?y().selectedWorkspace:void 0,b=y().workspaceDraft?y().selectedWorkspace:l.workspaces.some(e=>e.path===h)?h:g??v??null,x=n.resetCursor;return t({...x?_():{},connection:y().connection===`connecting`?`connecting`:y().connection,cursor:x||y().cursor===null?l.cursor:Math.max(y().cursor??0,l.cursor),activeTurn:l.runtime.activeTurn??null,livePhase:l.runtime.status!==`running`&&!y().promptAdmissionPending&&!s?`idle`:y().livePhase,liveRetry:l.runtime.status!==`running`&&!y().promptAdmissionPending&&!s?null:y().liveRetry,liveRunning:l.runtime.status===`running`?!0:!y().promptAdmissionPending&&!s?!1:y().liveRunning,selectedPath:d?.path??l.selectedSession?.path??null,selectedWorkspace:b,snapshot:l}),!0}catch(e){return a!==r||o!==i?!1:(t({connection:`unavailable`}),b(e),!1)}},async chooseWorkspace(){let t=r;try{let n=await e.chooseWorkspace();if(t!==r||n.cancelled||!n.path)return;E.setWorkspace(n.path),await E.refreshSnapshot()}catch(e){t===r&&b(e)}},setWorkspace(e){let n=y();e===n.selectedWorkspace&&!n.sessionSwitching||(++r,o=null,s=null,t({..._(),selectedWorkspace:e,workspaceDraft:!0,sessionSwitching:!1,modelSelectionPending:!1,promptAdmissionPending:!1,notice:null}))},async renameWorkspace(t,n){try{await e.renameWorkspace(t,n),await E.refreshSnapshot()}catch(e){throw b(e),e}},async removeWorkspace(n){try{await e.removeWorkspace(n),t({selectedPath:null,selectedWorkspace:y().selectedWorkspace===n?null:y().selectedWorkspace}),await E.refreshSnapshot()}catch(e){b(e)}},async createSession(n){if(!n||y().modelSelectionPending)return null;let i=++r,a=globalThis.crypto?.randomUUID?.()??`web-create-${Date.now()}-${i}`;o=null,s=null,t({..._(),mobileSidebarOpen:!1,promptAdmissionPending:!1,selectedPath:null,selectedWorkspace:n,workspaceDraft:!0,sessionSwitching:!0});let u=null,d=l.then(async()=>{if(i===r){c={commandId:a,epoch:i,expectedPath:null,kind:`create`};try{let o=await e.createSession(n,a);if(i!==r)return;if(o.cancelled||o.commandId!==a||typeof o.sessionId!=`string`||!o.sessionId||o.sessionId.length>128||o.sessionPath!==void 0&&!o.sessionPath)throw Error(`Session creation did not return a valid target identity.`);let s={epoch:i,sessionId:o.sessionId,sessionPath:o.sessionPath??null,workspacePath:n};c?.epoch===i&&(c.expectedSessionId=s.sessionId,c.expectedPath=s.sessionPath),t({selectedPath:s.sessionPath});let l=await E.refreshSnapshot({epoch:i});if(!l&&i===r&&(l=await E.refreshSnapshot({epoch:i})),i!==r)return;if(!l)throw Error(`The created Session could not be confirmed. Please try again.`);if(!x(s)){b(Error(`The created Session is no longer active in the selected workspace. Please try again.`));return}t({workspaceDraft:!1,notice:null});let d=y().draftModel;if(d&&!await S(d,i,s.sessionId))return;if(y().workspaceDraft||!x(s)){b(Error(`The created Session is no longer active in the selected workspace. Please try again.`));return}u=s}catch(e){if(i!==r)return;t({selectedPath:null}),b(e),await E.refreshSnapshot({epoch:i})}finally{g(h,a),c?.epoch===i&&(c=null),i===r&&t({sessionSwitching:!1})}}});return l=d.catch(()=>void 0),await d,i===r?u:null},async selectSession(n){if(!n)return;t({workspaceDraft:!1,draftModel:null,modelSelectionPending:!1});let i=++r;o=null,s=null,t({..._(),mobileSidebarOpen:!1,promptAdmissionPending:!1,selectedPath:n,sessionSwitching:!0});let a=l.then(async()=>{if(i===r){c={epoch:i,expectedPath:n,kind:`select`};try{if(await e.selectSession(n),i!==r)return;await E.refreshSnapshot({epoch:i})||t({selectedPath:null})}catch(e){if(i!==r)return;t({selectedPath:null}),b(e),await E.refreshSnapshot({epoch:i})}finally{c?.epoch===i&&(c=null),i===r&&t({sessionSwitching:!1})}}});l=a.catch(()=>void 0),await a},async renameSession(t,n){try{await e.renameSession(t,n),await E.refreshSnapshot()}catch(e){throw b(e),e}},async archiveSession(t){try{await e.archiveSession(t),await E.refreshSnapshot()}catch(e){b(e)}},async unarchiveSession(t){let n=r;try{return await e.unarchiveSession(t),n!==r||await E.refreshSnapshot({epoch:n})}catch(e){return n===r&&b(e),!1}},async selectModel(e){let[n,...i]=e.split(`/`),a=i.join(`/`),o=y();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.find(e=>e.provider===n&&e.id===a);e&&t({draftModel:e,notice:null});return}!s||s!==o.snapshot?.currentSessionId||await S({provider:n,id:a},r,s)},async cancelActiveTurn(){let n=y().activeTurn??y().snapshot?.runtime.activeTurn;if(!n||y().turnCancellationPending||y().sessionSwitching)return;let i=r;t({turnCancellationPending:!0});try{await e.cancelActiveTurn(n)}catch(e){if(i!==r)return;await E.refreshSnapshot({epoch:i}),i===r&&b(e)}finally{i===r&&t({turnCancellationPending:!1})}},async sendPrompt(n){let i=n.trim(),c=y().selectedWorkspace;if(!c||!i||y().sessionSwitching||y().promptAdmissionPending||y().modelSelectionPending)return!1;let l=y().workspaceDraft||!y().snapshot?.selectedSession?.id,u=l?await E.createSession(c):null;if(l&&!u)return y().notice||t({notice:`The active Session changed before the first message was sent. Your message was not sent.`}),!1;if(l&&y().draftModel)return!1;let d=y().snapshot?.selectedSession,f=u??(d?{epoch:r,sessionId:d.id,sessionPath:d.path,workspacePath:c}:null);if(!f||y().workspaceDraft||!x(f)||y().sessionSwitching||y().promptAdmissionPending)return!1;let{epoch:p,sessionId:h}=f,g=y().draftModel;if(g&&!await S(g,p,h)||y().workspaceDraft||!x(f))return!1;let _=++a,w=s?.sessionId===h&&s.content===i,T=w?s.commandId:globalThis.crypto?.randomUUID?.()??`web-prompt-${Date.now()}-${_}`,D=w?s.optimisticKey:`optimistic-${T}`;s={sessionId:h,content:i,commandId:T,optimisticKey:D},o=_,t({liveMessages:w?y().liveMessages:[...y().liveMessages,{key:D,message:{role:`user`,content:i}}].slice(-8),notice:null,pendingFollowUpsReceipt:null,turnTerminalStatus:null,promptAdmissionPending:!0,scrollToBottom:y().scrollToBottom+1});try{let n=await e.prompt(h,i,T,w);if(p!==r||o!==_)return!1;let a=m.has(n.id);return s?.commandId===T&&(s=null),t({...v(a,y().livePhase),pendingFollowUpsReceipt:n.pendingFollowUps??null}),C(120),!0}catch(e){return p!==r||o!==_?!1:e instanceof Mc&&[`WORKSPACE_REQUIRED`,`SESSION_CONFLICT`,`PROMPT_REJECTED`,`COMMAND_CONFLICT`,`PROMPT_ADMISSION_CAPACITY`].includes(e.code??``)?(s?.commandId===T&&(s=null),t({liveMessages:y().liveMessages.filter(e=>e.key!==D),livePhase:`idle`,liveRetry:null,liveRunning:!1}),b(e),!1):(t({liveRunning:!0,livePhase:y().livePhase===`running`?`running`:`preparing`,liveRetry:null}),b(e),!1)}finally{p===r&&o===_&&(o=null,t({promptAdmissionPending:!1}))}},setQuery(e){t({query:e})},setSearchOpen(e){t({searchOpen:e,...e?{}:{query:``}})},toggleWorkspace(e){let n=new Set(y().collapsed);n.has(e)?n.delete(e):n.add(e),Jb(Ub,[...n]),t({collapsed:n})},toggleSidebar(e){if(e){t({mobileSidebarOpen:!y().mobileSidebarOpen});return}let n=!y().sidebarCollapsed;try{window.sessionStorage.setItem(Wb,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,collapsed:Kb(Ub),sidebarCollapsed:qb(Wb),mobileSidebarOpen:!1,query:``,searchOpen:!1,connection:`connecting`,notice:null,liveMessages:[],liveRunning:!1,livePhase:`idle`,liveRetry:null,thinkingStarts:{},thinkingDurations:{},promptAdmissionPending:!1,sessionSwitching:!1,scrollToBottom:0,actions:E}})}var Zb=Xb();function Qb(){let e=pn(Zb),{t}=cn(),{actions:n}=e,[r,i]=(0,w.useState)(`chat`),[a,o]=(0,w.useState)(null),s=e.snapshot?.models.find(e=>e.current),c=JSON.stringify([s?.provider,s?.id]),l=t=>{let n=e.snapshot,r=n?.selectedSession;e.workspaceDraft||!n||!r||e.sessionSwitching||r.id!==n.currentSessionId||o({sessionId:r.id,sessionPath:r.path,cwd:r.cwd,model:s?.label??``,modelKey:c,terminalId:t})},u=a&&!e.workspaceDraft&&!e.sessionSwitching&&a.sessionId===e.snapshot?.currentSessionId&&a.sessionPath===e.snapshot?.selectedSession?.path&&a.modelKey===c;(0,w.useEffect)(()=>{a&&!u&&o(null)},[a,u]),(0,w.useEffect)(()=>(n.start(),n.stop),[n]);let d=e.workspaceDraft?void 0:e.snapshot?.selectedSession,f=d?.entries.some(e=>e.type===`message`&&e.message)||e.liveMessages.length>0,p=!d||!f,m=(0,w.useCallback)(e=>n.sendPrompt(e),[n]);return(0,L.jsxs)(`div`,{className:`app-shell ${e.sidebarCollapsed?`sidebar-collapsed`:``} ${e.mobileSidebarOpen?`sidebar-open`:``}`,children:[(0,L.jsx)(iu,{snapshot:e.snapshot,selectedPath:e.workspaceDraft?null:e.selectedPath,selectedWorkspace:e.selectedWorkspace,collapsed:e.collapsed,query:e.query,searchOpen:e.searchOpen,mobileOpen:e.mobileSidebarOpen,actions:n}),e.sidebarCollapsed&&(0,L.jsx)(`button`,{className:`sidebar-expand`,type:`button`,"aria-label":t(`expandSidebar`),title:t(`expandSidebar`),onClick:()=>n.toggleSidebar(!1),children:(0,L.jsx)(Te,{})}),(0,L.jsxs)(`main`,{className:`conversation-shell ${d?`has-view`:``} ${p&&(e.workspaceDraft||r===`chat`)?`landing`:``}`,children:[(0,L.jsx)(`h1`,{className:`sr-only`,children:`OpenPI`}),(0,L.jsxs)(`header`,{className:`mobile-header`,children:[(0,L.jsx)(`button`,{type:`button`,"aria-label":t(`openSidebar`),onClick:()=>n.toggleSidebar(!0),children:(0,L.jsx)(Ce,{})}),(0,L.jsx)(`span`,{className:`connection-state ${e.connection}`,children:t(e.connection)})]}),d&&(0,L.jsxs)(`fieldset`,{className:`conversation-view-switch`,"aria-label":t(`conversationView`),children:[(0,L.jsx)(`button`,{type:`button`,"aria-pressed":r===`chat`,onClick:()=>i(`chat`),children:t(`chatView`)}),(0,L.jsx)(`button`,{type:`button`,"aria-pressed":r===`trajectory`,onClick:()=>i(`trajectory`),children:t(`trajectory`)})]}),e.sessionSwitching?(0,L.jsx)(`div`,{className:`conversation switching`,role:`status`,children:(0,L.jsxs)(`div`,{className:`conversation-running`,children:[(0,L.jsx)(`span`,{className:`conversation-running-dot`}),(0,L.jsx)(`span`,{children:t(`switchingSession`)})]})}):r===`trajectory`&&d&&e.snapshot?(0,L.jsx)(su,{snapshot:e.snapshot,running:e.liveRunning},d.path):p?(0,L.jsx)(`section`,{className:`conversation landing-conversation`,"aria-label":`Conversation`,children:(0,L.jsx)(`div`,{className:`landing-welcome`,children:(0,L.jsx)(gn,{animated:!0})})}):e.snapshot?(0,L.jsx)(jb,{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:m}):null,(0,L.jsx)(nu,{workspaceDraft:e.workspaceDraft,draftModel:e.draftModel,modelSelectionPending:e.modelSelectionPending,onInspect:l,activeTurn:e.activeTurn,turnCancellationPending:e.turnCancellationPending,turnTerminalStatus:e.turnTerminalStatus,pendingFollowUpsReceipt:e.pendingFollowUpsReceipt,snapshot:e.snapshot,selectedWorkspace:e.selectedWorkspace,sessionSwitching:e.sessionSwitching,promptAdmissionPending:e.promptAdmissionPending,liveRunning:e.liveRunning,landing:p,actions:n}),e.notice&&(0,L.jsxs)(`div`,{className:`notice`,role:`alert`,children:[(0,L.jsx)(`span`,{children:e.notice}),(0,L.jsx)(`button`,{type:`button`,"aria-label":t(`close`),onClick:n.clearNotice,children:(0,L.jsx)(ze,{})})]})]}),u&&(0,L.jsx)(Fc,{target:a,onClose:()=>o(null)},`${a.sessionId}:${a.sessionPath}:${a.terminalId??`status`}`),(0,L.jsx)(`button`,{className:`sidebar-scrim`,type:`button`,"aria-label":t(`close`),onClick:n.closeMobileSidebar})]})}var $b={base:{k1xSpc:`xjp7ctv`,kMwMTN:`x1tgivj0`,kMv6JI:`x9ynric`,$$css:!0},light:{kQNsl9:`x19aimcq`,$$css:!0},dark:{kQNsl9:`xntwwlm`,$$css:!0},system:{kQNsl9:`x108lcm5`,$$css:!0}},ex=w.createContext(!1);ex.displayName=`ThemeNestingContext`;var tx=new Set,nx=0;function rx(e){let t=(0,w.useId)();(0,w.useInsertionEffect)(()=>{if(e.__built)return;let n=`astryx-theme-${e.name}`;if(tx.has(n))return;`${e.name}`,`${e.name}${e.name}${e.name}${e.name}`;let{prose:r,component:i}=ic(e),a=rc();tx.add(n);let o=[()=>tx.delete(n)];if(a){if(nx++===0){let e=document.createElement(`style`);e.setAttribute(Ir(`theme-base`),``),e.textContent=`@layer astryx-base {\n${a}\n}`,document.head.appendChild(e)}o.push(()=>{--nx===0&&document.querySelector(`style[${Ir(`theme-base`)}]`)?.remove()})}if(r){let n=document.createElement(`style`);n.setAttribute(Ir(`theme-prose`),e.name),n.setAttribute(Ir(`id`),t),n.textContent=`@layer reset {\n${r}\n}`,document.head.appendChild(n)}if(i){let n=document.createElement(`style`);n.setAttribute(Ir(`theme`),e.name),n.setAttribute(Ir(`id`),t),n.textContent=`@layer astryx-theme {\n${i}\n}`,document.head.appendChild(n)}return(r||i)&&o.push(()=>{let n=document.querySelector(`style[${Ir(`theme-prose`)}="${e.name}"][${Ir(`id`)}="${t}"]`),r=document.querySelector(`style[${Ir(`theme`)}="${e.name}"][${Ir(`id`)}="${t}"]`);n?.remove(),r?.remove()}),()=>{for(let e of o)e()}},[e,t])}function ix(e,t,n){xl(()=>{if(!e&&!(typeof document>`u`))return t===`light`||t===`dark`?document.documentElement.setAttribute(`data-theme`,t):document.documentElement.removeAttribute(`data-theme`),document.documentElement.setAttribute(Ir(`theme`),n),()=>{document.documentElement.removeAttribute(`data-theme`),document.documentElement.removeAttribute(Ir(`theme`))}},[e,t,n])}function ax({theme:e,mode:t=`system`,children:n}){let r=(0,w.use)(ex);js(e),rx(e),ix(r,t,e.name);let i=t===`dark`?$b.dark:t===`light`?$b.light:$b.system,a=(0,w.useMemo)(()=>({theme:e,mode:t}),[e,t]);return(0,L.jsx)(oc,{value:a,children:(0,L.jsx)(ex,{value:!0,children:(0,L.jsx)(`div`,{...xn($b.base,i),"data-astryx-theme":e.name,"data-theme":t===`system`?void 0:t,children:n})})})}ax.displayName=`Theme`;var ox={size:`1em`,"aria-hidden":!0},sx={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,L.jsx)(ze,{...ox}),chevronDown:(0,L.jsx)(re,{...ox}),chevronLeft:(0,L.jsx)(ie,{...ox}),chevronRight:(0,L.jsx)(ae,{...ox}),chevronsLeft:(0,L.jsx)(oe,{...ox}),chevronsRight:(0,L.jsx)(se,{...ox}),check:(0,L.jsx)(P,{...ox}),success:(0,L.jsx)(F,{...ox}),error:(0,L.jsx)(ce,{...ox}),warning:(0,L.jsx)(Ie,{...ox}),info:(0,L.jsx)(xe,{...ox}),calendar:(0,L.jsx)(ne,{...ox}),clock:(0,L.jsx)(ue,{...ox}),externalLink:(0,L.jsx)(me,{...ox}),menu:(0,L.jsx)(Ce,{...ox}),moreHorizontal:(0,L.jsx)(pe,{...ox}),search:(0,L.jsx)(ke,{...ox}),arrowUp:(0,L.jsx)(j,{...ox}),arrowDown:(0,L.jsx)(k,{...ox}),arrowsUpDown:(0,L.jsx)(A,{...ox}),funnel:(0,L.jsx)(ye,{...ox}),eyeSlash:(0,L.jsx)(he,{...ox}),viewColumns:(0,L.jsx)(de,{...ox}),copy:(0,L.jsx)(fe,{...ox}),checkDouble:(0,L.jsx)(N,{...ox}),wrench:(0,L.jsx)(Re,{...ox}),stop:(0,L.jsx)(Ne,{...ox}),microphone:(0,L.jsx)(we,{...ox})}},cx={en:{translation:{conversationView:`Conversation view`,chatView:`Chat`,trajectory:`Trajectory`,trajectoryScope:`Recorded Session messages in order, not execution durations or the complete model request.`,trajectoryRunning:`Work is active. This view updates as saved records arrive; in-flight text is available in Chat.`,trajectoryTruncated:`Loaded history is partial: {{entries}} entries omitted, {{parts}} parts omitted, {{messages}} messages truncated.`,trajectoryEmpty:`No saved records yet.`,trajectoryOverview:`Record sequence overview`,trajectoryEarlier:`Show earlier records ({{count}} remaining in loaded history)`,trajectoryDetails:`Record details`,trajectoryRecordedAt:`Record timestamp`,trajectoryEvidenceTruncated:`This evidence was truncated in the Session projection.`,trajectoryArguments:`Tool arguments`,trajectoryRecordedContent:`Recorded content`,trajectoryThinking:`Recorded model reasoning`,trajectoryOutput:`Tool result`,trajectoryMissingResult:`No unambiguous result is included in the loaded records. This does not imply the tool is still running.`,trajectoryStructured:`Structured result`,trajectoryEventOnly:`Only this event’s type and timestamp are available.`,trajectory_user:`User prompt`,trajectory_assistant:`Assistant`,trajectory_call:`Tool call`,trajectory_result:`Unpaired result`,trajectory_event:`Session event`,trajectory_returned:`Tool returned successfully; background work may still be active.`,trajectory_error:`Tool returned an error`,trajectory_unknown:`Outcome not established`,runtimeStatus:`Runtime status`,refreshStatus:`Refresh status`,terminalDetails:`Terminal details`,inspectionChanged:`The active session changed. Reopen this panel.`,inspectionUnavailable:`Details are unavailable.`,inspectionLoading:`Loading status…`,thinkingUnavailable:`Thinking state is unavailable.`,trustUnavailable:`Project trust status is unavailable.`,authUnavailable:`Provider status is unavailable.`,executionState:`State`,terminalCommand:`Command`,terminalDirectory:`Directory`,startedAt:`Started`,exitCode:`Exit code`,detailTruncated:`Some details are shortened to keep this view bounded.`,standardOutput:`Standard output`,standardError:`Standard error`,outputTruncated:`{{count}} bytes omitted from this output view.`,outputRecovery:`The runtime reports a retained log. This view shows only a bounded excerpt.`,modelAndThinking:`Model and reasoning`,selectedModel:`Current model`,thinkingLevel:`Thinking level`,availableThinking:`Available levels`,unknownState:`Unknown`,projectTrust:`Project trust`,trust_trusted:`This session trusts the workspace.`,trust_untrusted:`Trust for this workspace has been denied.`,trust_restricted:`Project resources are restricted pending a trust decision.`,trust_unknown:`Trust state is not available.`,trustRefreshNeeded:`The saved decision and active session differ. Refresh the session through Pi to apply it.`,providerAvailability:`Provider credentials`,credentialConfigured:`Configured`,credentialMissing:`Not configured`,noProviders:`No providers reported.`,providersBounded:`This provider list is truncated.`,authNotVerified:`Configured credentials do not guarantee a successful model request.`,configurationViaPi:`Read-only status. Manage model credentials and trust through Pi; manage OpenPI options with /openpi-setup.`,statusCaptured:`Snapshot at {{time}} · refresh for the latest state`,conversationViews:`Conversation views`,currentConversations:`Current`,archivedConversations:`Archived`,restoreConversation:`Restore conversation`,restoringConversation:`Restoring…`,loadedArchives:`Archived conversations in the loaded history`,loadedHistoryBounded:`{{sessions}} more sessions and {{workspaces}} workspace summaries are not loaded. Search covers the loaded list only.`,noLoadedArchives:`No archived conversations in this loaded list.`,restoreFailed:`Could not confirm restoration. Refresh and try again.`,execution_running:`Running`,execution_done:`Completed`,execution_killed:`Stopped`,execution_timed_out:`Timed out`,execution_failed:`Failed`,execution_uncertain:`Uncertain`,activeOnlyHint:`Only the active Web session accepts messages.`,acceptedHint:`Message accepted by OpenPI Web.`,stopTurn:`Stop current turn`,stoppingTurn:`Stopping current turn…`,stoppedTurn:`Current turn stopped.`,pendingFollowUpsHint:`{{count}} messages queued`,addWorkspace:`Add workspace`,addWorkspaceMenu:`Add workspace...`,archiveConversation:`Archive conversation`,cancel:`Cancel`,chooseWorkspaceHint:`Choose a workspace and describe the work`,close:`Close`,closeSearch:`Close search`,collapseSidebar:`Collapse sidebar`,confirmEdit:`OK`,connected:`Connected`,connecting:`Connecting`,conversationName:`Conversation name`,conversationOptions:`Conversation options`,conversationTurns:`Conversation turns`,copiedMessage:`Copied`,copyMessage:`Copy message`,copyFailed:`Copy failed. Please select and copy the message manually.`,deleteWorkspace:`Delete workspace`,describeTask:`Describe a task`,editMessage:`Edit message`,enterHint:`Enter to send, Shift+Enter for a new line.`,expandSidebar:`Expand sidebar`,importWorkspace:`Import workspace`,loadingModels:`Loading models...`,modelPreparing:`Preparing task...`,modelRetrying:`Retrying model request...`,modelRunning:`Working...`,newSession:`New session`,noConversations:`No conversations yet`,noMatching:`No matching conversations`,noModels:`No models available`,noOutput:`no output`,noSessions:`No sessions`,openSidebar:`Open sidebar`,promptMessage:`Send a message to the active Web session`,promptReadonly:`A non-active session cannot receive prompts`,promptStart:`Choose a workspace to begin.`,promptTask:`Describe what you want to build.`,queuedHint:`The message will be queued after the current turn.`,reconnecting:`Reconnecting`,removeWorkspace:`Remove from sidebar`,renameConversation:`Rename conversation`,renameWorkspace:`Rename workspace`,save:`Save`,searchConversations:`Search conversations`,searchPlaceholder:`Search conversations...`,selectModel:`Select model`,selectWorkspace:`Select workspace`,send:`Send`,stepsLabel:`steps`,switchingSession:`Switching session...`,thinkingActive:`Thinking...`,thinkingDone:`Thinking`,unavailable:`Unavailable`,ungrouped:`Ungrouped`,untitledSession:`New session`,workspaceDeleteConfirm:`The folder and conversation records will be kept. Its conversations will move to Ungrouped.`,workspaceName:`Workspace name`,workspaces:`Workspaces`}},zh:{translation:{conversationView:`会话视图`,chatView:`对话`,trajectory:`执行轨迹`,trajectoryScope:`按记录顺序展示会话消息,不表示执行耗时,也不是完整模型请求。`,trajectoryRunning:`任务仍在进行。已保存记录到达后自动更新;实时生成内容可在“对话”中查看。`,trajectoryTruncated:`当前历史不完整:省略 {{entries}} 条记录、{{parts}} 个内容片段,{{messages}} 条消息被截断。`,trajectoryEmpty:`尚无已保存记录。`,trajectoryOverview:`记录顺序总览`,trajectoryEarlier:`显示更早记录(已加载历史中还有 {{count}} 条)`,trajectoryDetails:`记录详情`,trajectoryRecordedAt:`记录时间`,trajectoryEvidenceTruncated:`此证据在会话投影中已被截断。`,trajectoryArguments:`工具参数`,trajectoryRecordedContent:`已记录内容`,trajectoryThinking:`模型返回的推理内容`,trajectoryOutput:`工具结果`,trajectoryMissingResult:`当前记录中没有可明确配对的结果。这不表示工具仍在运行。`,trajectoryStructured:`结构化结果`,trajectoryEventOnly:`当前仅提供此事件的类型和记录时间。`,trajectory_user:`用户 Prompt`,trajectory_assistant:`模型消息`,trajectory_call:`工具调用`,trajectory_result:`未配对结果`,trajectory_event:`会话事件`,trajectory_returned:`工具已成功返回;其启动的后台任务可能仍在进行。`,trajectory_error:`工具返回错误`,trajectory_unknown:`尚无法确定结果`,runtimeStatus:`运行状态`,refreshStatus:`刷新状态`,terminalDetails:`终端详情`,inspectionChanged:`当前会话已切换,请重新打开详情。`,inspectionUnavailable:`暂时无法读取详情。`,inspectionLoading:`正在读取状态…`,thinkingUnavailable:`暂时无法读取思考等级。`,trustUnavailable:`暂时无法读取项目信任状态。`,authUnavailable:`暂时无法读取服务商状态。`,executionState:`状态`,terminalCommand:`命令`,terminalDirectory:`目录`,startedAt:`启动时间`,exitCode:`退出码`,detailTruncated:`部分详情已截断,以限制页面加载量。`,standardOutput:`标准输出`,standardError:`错误输出`,outputTruncated:`此输出视图省略了 {{count}} 字节。`,outputRecovery:`运行时报告已保留日志;此处仅显示有大小限制的片段。`,modelAndThinking:`模型与思考`,selectedModel:`当前模型`,thinkingLevel:`思考等级`,availableThinking:`支持的等级`,unknownState:`未知`,projectTrust:`项目信任`,trust_trusted:`当前会话信任此工作区。`,trust_untrusted:`此工作区的信任已被拒绝。`,trust_restricted:`项目资源受到限制,等待信任决定。`,trust_unknown:`尚无法确定信任状态。`,trustRefreshNeeded:`保存的信任决定与当前会话不同,请通过 Pi 刷新会话后生效。`,providerAvailability:`服务商凭据`,credentialConfigured:`已配置`,credentialMissing:`未配置`,noProviders:`未发现服务商。`,providersBounded:`服务商列表已截断。`,authNotVerified:`凭据已配置不代表模型请求一定成功。`,configurationViaPi:`此处为只读状态。模型凭据与信任由 Pi 管理,OpenPI 选项通过 /openpi-setup 配置。`,statusCaptured:`采集于 {{time}} · 刷新查看最新状态`,conversationViews:`会话视图`,currentConversations:`当前`,archivedConversations:`已归档`,restoreConversation:`恢复会话`,restoringConversation:`正在恢复…`,loadedArchives:`已加载历史中的归档会话`,loadedHistoryBounded:`另有 {{sessions}} 个会话和 {{workspaces}} 个工作区摘要未加载。搜索仅覆盖已加载列表。`,noLoadedArchives:`已加载列表中没有归档会话。`,restoreFailed:`暂时无法确认恢复结果,请刷新后重试。`,execution_running:`运行中`,execution_done:`已完成`,execution_killed:`已停止`,execution_timed_out:`已超时`,execution_failed:`失败`,execution_uncertain:`状态不确定`,activeOnlyHint:`只有当前 Web 会话可以接收消息。`,stopTurn:`停止当前轮次`,stoppingTurn:`正在停止当前轮次…`,stoppedTurn:`当前轮次已停止。`,pendingFollowUpsHint:`{{count}} 条消息正在排队`,acceptedHint:`OpenPI Web 已接收消息。`,addWorkspace:`添加工作区`,addWorkspaceMenu:`添加工作区...`,archiveConversation:`归档会话`,cancel:`取消`,chooseWorkspaceHint:`选择工作区并描述任务`,close:`关闭`,closeSearch:`关闭搜索`,collapseSidebar:`收起侧边栏`,confirmEdit:`确定`,connected:`已连接`,connecting:`正在连接`,conversationName:`会话名称`,conversationOptions:`会话选项`,conversationTurns:`会话轮次`,copiedMessage:`已复制`,copyMessage:`复制消息`,copyFailed:`复制失败,请选中消息后手动复制。`,deleteWorkspace:`删除工作区`,describeTask:`描述任务`,editMessage:`编辑消息`,enterHint:`按 Enter 发送,Shift+Enter 换行。`,expandSidebar:`展开侧边栏`,importWorkspace:`导入工作区`,loadingModels:`正在加载模型...`,modelPreparing:`正在准备任务...`,modelRetrying:`模型请求重试中...`,modelRunning:`正在运行...`,newSession:`新建会话`,noConversations:`暂无对话`,noMatching:`没有匹配的会话`,noModels:`没有可用模型`,noOutput:`无输出`,noSessions:`暂无会话`,openSidebar:`打开侧边栏`,promptMessage:`向当前 Web 会话发送消息`,promptReadonly:`非当前会话不能接收消息`,promptStart:`选择一个工作区开始`,promptTask:`描述你想要构建的任务`,queuedHint:`当前回合结束后将发送消息。`,reconnecting:`正在重连`,removeWorkspace:`从侧边栏移除`,renameConversation:`重命名会话`,renameWorkspace:`重命名工作区`,save:`保存`,searchConversations:`搜索会话`,searchPlaceholder:`搜索会话...`,selectModel:`选择模型`,selectWorkspace:`选择工作区`,send:`发送`,stepsLabel:`个步骤`,switchingSession:`正在切换会话...`,thinkingActive:`思考中...`,thinkingDone:`思考过程`,unavailable:`不可用`,ungrouped:`未分组`,untitledSession:`新会话`,workspaceDeleteConfirm:`文件夹与会话记录会保留,其中的会话会被放到“未分组”;再次打开此目录时将是一个干净的工作区。`,workspaceName:`工作区名称`,workspaces:`工作区`}}},lx=navigator.language?.toLowerCase().startsWith(`zh`)?`zh`:`en`;Ft.use(en).init({fallbackLng:`en`,initAsync:!1,interpolation:{escapeValue:!1},lng:lx,resources:cx}),document.documentElement.lang=lx===`zh`?`zh-CN`:`en`;function ux({children:e}){let t=pn(Zb,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,L.jsx)(ln,{i18n:Ft,children:(0,L.jsx)(ax,{theme:sx,mode:i,children:e})})}var dx=document.getElementById(`root`);if(!dx)throw Error(`OpenPI Web root is missing`);(0,Be.createRoot)(dx).render((0,L.jsx)(w.StrictMode,{children:(0,L.jsx)(ux,{children:(0,L.jsx)(Qb,{})})})); \ No newline at end of file diff --git a/web/host/web-host.ts b/web/host/web-host.ts index ee49fa04..9d7ae939 100644 --- a/web/host/web-host.ts +++ b/web/host/web-host.ts @@ -487,6 +487,7 @@ export class WebHost { }); 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 c9d5cdee..4dbdb0aa 100644 --- a/web/runtime/pi-runtime.ts +++ b/web/runtime/pi-runtime.ts @@ -715,13 +715,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 8c60e5c0..f5f2b780 100644 --- a/web/runtime/types.ts +++ b/web/runtime/types.ts @@ -97,6 +97,7 @@ export interface WebSessionCreationOptions { export interface WebSessionCreationResult { cancelled: boolean; commandId?: string; + sessionId: string; sessionPath?: string; } diff --git a/web/ui/src/protocol/client.ts b/web/ui/src/protocol/client.ts index 3c1b7af8..555a194c 100644 --- a/web/ui/src/protocol/client.ts +++ b/web/ui/src/protocol/client.ts @@ -52,6 +52,13 @@ export interface SessionMutationResult { sessionPath?: string; } +export interface SessionCreationResult { + cancelled: boolean; + commandId: string; + sessionId: string; + sessionPath?: string; +} + export interface WorkspaceSelectionResult { cancelled?: boolean; path?: string; @@ -138,7 +145,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 1afe2a05..0106007a 100644 --- a/web/ui/src/store/web-store.ts +++ b/web/ui/src/store/web-store.ts @@ -73,7 +73,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 WebStoreState { @@ -122,7 +129,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,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 applyModel = async ( model: { provider: string; id: string }, epoch: number, @@ -339,16 +359,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; } @@ -695,7 +718,7 @@ export function createWebStore( } }, async createSession(workspacePath) { - if (!workspacePath || get().modelSelectionPending) return false; + if (!workspacePath || get().modelSelectionPending) return null; const epoch = ++sessionEpoch; const commandId = globalThis.crypto?.randomUUID?.() ?? @@ -711,7 +734,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 = { @@ -719,20 +742,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 @@ -746,23 +782,28 @@ 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)) { + 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)) { + showError( + new Error( + "The created Session is no longer active in the selected workspace. Please try again.", + ), + ); + return; + } + created = target; } catch (error) { if (epoch !== sessionEpoch) return; set({ selectedPath: null }); @@ -776,7 +817,7 @@ export function createWebStore( }); sessionSelectionTail = creation.catch(() => undefined); await creation; - return created && epoch === sessionEpoch; + return epoch === sessionEpoch ? created : null; }, async selectSession(path) { if (!path) return; @@ -904,31 +945,43 @@ 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 ) { 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 - ) + if (get().workspaceDraft || !targetMatchesSnapshot(target)) return false; const admission = ++promptAdmissionSequence; const retrying = From 6bed11a9ee00079d683d1978dc58e8377a25e1ee Mon Sep 17 00:00:00 2001 From: testikun <320479488+testikun@users.noreply.github.com> Date: Tue, 8 Sep 2026 15:55:32 +0800 Subject: [PATCH 2/2] docs: link session target pull request --- docs/design/WEB_SESSION_CREATION_TARGET.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/design/WEB_SESSION_CREATION_TARGET.md b/docs/design/WEB_SESSION_CREATION_TARGET.md index d2e5b93f..8cb946b3 100644 --- a/docs/design/WEB_SESSION_CREATION_TARGET.md +++ b/docs/design/WEB_SESSION_CREATION_TARGET.md @@ -6,7 +6,7 @@ - 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: added when the implementation is published +- Related PR: [#490](https://github.com/openpi-dev/openpi/pull/490) - Supersedes: none ## Problem