From c46c96f024de03b13dc38932f86263ebff609c62 Mon Sep 17 00:00:00 2001 From: seekskyworld Date: Thu, 3 Sep 2026 18:43:32 +0800 Subject: [PATCH 1/2] feat(web): project Pi trust state in snapshots Signed-off-by: seekskyworld --- web/adapter/pi-adapter.ts | 1 + web/protocol/types.ts | 1 + web/runtime/pi-runtime.ts | 11 +++++++++++ web/runtime/types.ts | 1 + 4 files changed, 14 insertions(+) diff --git a/web/adapter/pi-adapter.ts b/web/adapter/pi-adapter.ts index fb4c0bc9..dd66b36b 100644 --- a/web/adapter/pi-adapter.ts +++ b/web/adapter/pi-adapter.ts @@ -490,6 +490,7 @@ export class PiWebAdapter { status: this.runtime.isIdle() ? ("idle" as const) : ("running" as const), + trust: this.runtime.projectTrustState ?? "unknown", capabilities: webCapabilitySnapshot(this.runtime.sessionManager), }, truncation: { diff --git a/web/protocol/types.ts b/web/protocol/types.ts index a7429b6e..90181b4e 100644 --- a/web/protocol/types.ts +++ b/web/protocol/types.ts @@ -113,6 +113,7 @@ export interface WebSnapshot { models: WebModelSummary[]; runtime: { status: "idle" | "running" | "unknown"; + trust: "trusted" | "untrusted" | "unknown"; capabilities: WebCapabilitySnapshot; }; truncation: WebSnapshotTruncation; diff --git a/web/runtime/pi-runtime.ts b/web/runtime/pi-runtime.ts index cf792d39..1ea69196 100644 --- a/web/runtime/pi-runtime.ts +++ b/web/runtime/pi-runtime.ts @@ -86,6 +86,17 @@ export class PiWebRuntime implements WebRuntimeController { private disposePromise?: Promise; private hasSelectedWorkspace: boolean; + get projectTrustState() { + if (!this.hasSelectedWorkspace) return "unknown" as const; + try { + return this.runtime.session.settingsManager.isProjectTrusted() + ? ("trusted" as const) + : ("untrusted" as const); + } catch { + return "unknown" as const; + } + } + private constructor( runtime: AgentSessionRuntime, webSessionDirectory: string, diff --git a/web/runtime/types.ts b/web/runtime/types.ts index 2b66c3f0..11f2f0d2 100644 --- a/web/runtime/types.ts +++ b/web/runtime/types.ts @@ -53,6 +53,7 @@ export interface WebRuntimeController { readonly workspaceSelected: boolean; readonly sessionDirectory: string; readonly sessionManager: SessionManager; + readonly projectTrustState?: "trusted" | "untrusted" | "unknown"; isIdle(): boolean; sendPrompt(content: string, options?: WebPromptOptions): Promise; newSession( From 32e8fd517952216365079ef671ecbf0616cf3ab1 Mon Sep 17 00:00:00 2001 From: seekskyworld Date: Thu, 3 Sep 2026 18:45:08 +0800 Subject: [PATCH 2/2] feat(web): add session unarchive operation Signed-off-by: seekskyworld --- web/adapter/pi-adapter.ts | 8 ++++++++ web/host/web-host.ts | 7 +++++++ 2 files changed, 15 insertions(+) diff --git a/web/adapter/pi-adapter.ts b/web/adapter/pi-adapter.ts index dd66b36b..66c93395 100644 --- a/web/adapter/pi-adapter.ts +++ b/web/adapter/pi-adapter.ts @@ -284,6 +284,14 @@ export class PiWebAdapter { }); } + async unarchiveSession(path: string) { + await this.ensureArchivesLoaded(); + await this.enqueueArchiveMutation(async (draft) => { + const session = await this.requireSession(path); + draft.delete(resolve(session.path)); + }); + } + async removeWorkspace(path: string) { await this.ensureWorkspaceStateLoaded(); const canonical = resolve(path); diff --git a/web/host/web-host.ts b/web/host/web-host.ts index 6203e647..b1f0597d 100644 --- a/web/host/web-host.ts +++ b/web/host/web-host.ts @@ -425,6 +425,13 @@ export class WebHost { this.publish("session_archived", { sessionPath: path }); return this.json(response, 200, { path, archived: true }); } + if (url.pathname === "/api/sessions/unarchive" && request.method === "POST") { + const path = url.searchParams.get("path"); + if (!path) return this.json(response, 400, { error: "session path is required" }); + await this.adapter.unarchiveSession(path); + this.publish("session_unarchived", { sessionPath: path }); + return this.json(response, 200, { path, archived: false }); + } if (url.pathname === "/api/sessions/select" && request.method === "POST") { const body = await this.readJson(request); if (typeof body.path !== "string" || body.path.trim().length === 0) {