From 155cd5c20b330c682887c82057a843144bbef076 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 17 Jul 2026 09:47:15 +0000 Subject: [PATCH 1/2] chore(deps): pin AllMyStuff to v0.2.46 Picks up the node-side fleet fix (a claimed KVM converges into the fleet signed roster, so the embedded allmystuff-serve authorizes the customer app's KVM controls - reboot/Wi-Fi/unclaim - over the mesh tunnel). The fix lives in the allmystuff-node package, which produces both the allmystuff-serve binary CEC embeds (.allmystuff-rev) and the git-dep crate CEC links, so the pin is what carries it in. v0.2.45 predates it. CEC needs no GUI change for this round - its KVM card already reads the owner advert, and its store already reacts to session-ended - so the pin bump is the whole of CEC's contribution. gui/src-tauri/Cargo.lock is left as-is: it can't regenerate until the v0.2.46 tag is published, and CI never builds the Tauri backend (its AllMyStuff git deps resolve only after the release ships). It refreshes on the next pnpm tauri build / release once the tag exists. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01YYdqmManehKSKxwzcfeeU4 --- .allmystuff-rev | 2 +- gui/src-tauri/Cargo.toml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.allmystuff-rev b/.allmystuff-rev index 0c1b4f0..eef44dc 100644 --- a/.allmystuff-rev +++ b/.allmystuff-rev @@ -1 +1 @@ -v0.2.45 +v0.2.46 diff --git a/gui/src-tauri/Cargo.toml b/gui/src-tauri/Cargo.toml index e607083..3939bab 100644 --- a/gui/src-tauri/Cargo.toml +++ b/gui/src-tauri/Cargo.toml @@ -51,11 +51,11 @@ tauri-plugin-single-instance = "2" # media planes, and the node control socket the client drives. Reused in "CEC # client (customer) mode". Git deps on the sibling branch that adds the CEC # node commands; not yet published, so this won't resolve in this sandbox. -allmystuff-node = { git = "https://github.com/mrjeeves/AllMyStuff", tag = "v0.2.45" } +allmystuff-node = { git = "https://github.com/mrjeeves/AllMyStuff", tag = "v0.2.46" } # The CEC wire contract + Support-ID derivation, and the three-choice consent # store — the customer's per-technician approvals. -allmystuff-cec-protocol = { git = "https://github.com/mrjeeves/AllMyStuff", tag = "v0.2.45" } -allmystuff-cec-consent = { git = "https://github.com/mrjeeves/AllMyStuff", tag = "v0.2.45" } +allmystuff-cec-protocol = { git = "https://github.com/mrjeeves/AllMyStuff", tag = "v0.2.46" } +allmystuff-cec-consent = { git = "https://github.com/mrjeeves/AllMyStuff", tag = "v0.2.46" } # The client's OWN OS-service installer (never AllMyStuff's), so installing the # background service never clobbers an existing AllMyStuff install. From c3c202368e0901f6abf3dc39df3658a26f41a58e Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 17 Jul 2026 10:10:16 +0000 Subject: [PATCH 2/2] fix(access): the Viewing chip tracks the console, not the session "Viewing your screen" stayed lit after the technician closed their remote-control console. The chip keyed on session state, and a session outlives the console on purpose (chat rides it; only Forget/revoke or a lapsed grant ends it) - so "connected" read as "viewing" forever. The chip now keys on what the technician's live routes actually carry, via the node's new cec://viewing event + cec_viewing command (AllMyStuff v0.2.46): screen route live = "Viewing your screen", input route live = "Controlling your screen". Closing the console clears it within a couple of seconds; a chat-only connection never shows it at all. The status dot and chat stay keyed on the session, which remains truthful ("connected"). - gui/src-tauri: cec_viewing command proxied to the node (the generic event pump already relays cec://viewing). - tauri.ts: cecViewing() + onCecViewing() (ViewingMap). - store: viewing state, event subscription, hydrate pull in refresh(), and the accessRows join; AccessRow gains `viewing`. - AccessList: chip renders on r.viewing.screen, text picks Controlling when r.viewing.control (actual input authority in use, not the session's requested want_control flag). svelte-check 106 files 0 errors; pnpm build clean. The Tauri backend compiles only against the published v0.2.46 tag (per this repo's CI note), mirroring cec_grants exactly. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01YYdqmManehKSKxwzcfeeU4 --- gui/src-tauri/src/main.rs | 15 +++++++++++++++ gui/src/store.svelte.ts | 14 ++++++++++++++ gui/src/tauri.ts | 24 ++++++++++++++++++++++++ gui/src/types.ts | 4 ++++ gui/src/ui/AccessList.svelte | 9 +++++++-- 5 files changed, 64 insertions(+), 2 deletions(-) diff --git a/gui/src-tauri/src/main.rs b/gui/src-tauri/src/main.rs index 331f409..c1d99c2 100644 --- a/gui/src-tauri/src/main.rs +++ b/gui/src-tauri/src/main.rs @@ -386,6 +386,20 @@ async fn cec_grants(state: State<'_, AppState>) -> Result { Ok(v) } +/// What each connected technician is actually doing right now — the node's +/// `cec_viewing` projection `{ techs: { : { screen, control } } }`, +/// derived from live routes rather than session state. The pull half of the +/// `cec://viewing` event (which the generic pump already relays), so the +/// access list paints the chip correctly on a mid-session app start. +#[tauri::command] +async fn cec_viewing(state: State<'_, AppState>) -> Result { + state + .node + .request("cec_viewing", json!({})) + .await + .map_err(|e| e.to_string()) +} + /// Set this computer's friendly name (shown to the technician on the mesh). A /// convenience beyond the core dial/approve contract. #[tauri::command] @@ -960,6 +974,7 @@ fn run_gui() -> ExitCode { cec_revoke, cec_forget_node, cec_grants, + cec_viewing, cec_set_label, cec_chat_send, cec_chat_history, diff --git a/gui/src/store.svelte.ts b/gui/src/store.svelte.ts index 414ace4..041c577 100644 --- a/gui/src/store.svelte.ts +++ b/gui/src/store.svelte.ts @@ -25,6 +25,7 @@ import { cecDeny, cecForgetNode, cecGrants, + cecViewing, cecPending, cecRevoke, cecSetLabel, @@ -42,6 +43,7 @@ import { onCecHelp, onCecRequest, onCecSession, + onCecViewing, serviceInstall, serviceStatus, serviceStop, @@ -146,6 +148,12 @@ class CecStore { pending = $state([]); /** Live sessions keyed by session id. */ sessions = $state>({}); + /** What each technician's live routes actually carry right now (canonical + * tech id → screen/control), pushed by the node on every change. This is + * the truth the "Viewing/Controlling your screen" chip renders: a session + * outlives the console (chat rides it), so session state alone would keep + * the chip lit after the technician closed the console. */ + viewing = $state>({}); /** Standing approvals ("who can reach me"). */ grants = $state([]); service = $state(null); @@ -347,6 +355,7 @@ class CecStore { agent_name: g.agent_name || "A CEC technician", grant: g, live: live.find((s) => canonicalTech(s.tech) === key) ?? null, + viewing: this.viewing[key] ?? null, }; }); for (const s of live) { @@ -361,6 +370,7 @@ class CecStore { agent_name: s.agent_name || "A CEC technician", grant: null, live: s, + viewing: this.viewing[key] ?? null, }); } return rows; @@ -397,6 +407,7 @@ class CecStore { this.unlisteners.push(await onCecRequest((r) => this.onRequest(r))); this.unlisteners.push(await onCecSession((s) => this.onSession(s))); this.unlisteners.push(await onCecGrants((g) => (this.grants = g))); + this.unlisteners.push(await onCecViewing((v) => (this.viewing = v))); this.unlisteners.push( await onCecChat((e) => this.appendChat(e.peer, e.message)), ); @@ -520,6 +531,9 @@ class CecStore { this.status = await cecStatus(); this.pending = await cecPending(); this.grants = await cecGrants(); + // Pull the live viewing map too, so an app that starts (or reconnects) + // mid-session paints the chip without waiting for the next transition. + this.viewing = await cecViewing(); // The node is the truth for the ask (it withdraws it itself on approval, // and a restart drops it) — mirror it whenever the status lands, but never // mid-request: an in-flight ask/cancel (busy) owns the flag, so a status diff --git a/gui/src/tauri.ts b/gui/src/tauri.ts index 4efe279..25dc6a0 100644 --- a/gui/src/tauri.ts +++ b/gui/src/tauri.ts @@ -213,6 +213,17 @@ export async function cecGrants(): Promise { return Array.isArray(r) ? r : []; } +/** What each technician's live routes actually carry right now — screen + * (viewing) and control (driving input) — keyed by canonical tech id. This, + * not session state, is what the "Viewing your screen" chip keys on: a + * session outlives the console (chat rides it), so session state over-claims. + * Empty in web mode / on error. */ +export type ViewingMap = Record; +export async function cecViewing(): Promise { + const r = await tryInvoke<{ techs?: ViewingMap }>("cec_viewing"); + return r?.techs ?? {}; +} + /** Set this computer's friendly name (shown to the technician on the mesh). * A convenience beyond the core dial/approve contract; see README. */ export function cecSetLabel(label: string): Promise { @@ -333,6 +344,19 @@ export async function onCecGrants( ); } +/** What technicians' live routes carry changed (console opened/closed, control + * granted route came up/down) — repaint the Viewing/Controlling chip. The + * node's consent sweep pushes the whole map on every transition. */ +export async function onCecViewing( + cb: (viewing: ViewingMap) => void, +): Promise<() => void> { + if (!isTauri()) return () => {}; + const { listen } = await import("@tauri-apps/api/event"); + return listen<{ techs?: ViewingMap }>("cec://viewing", (e) => + cb(e.payload.techs ?? {}), + ); +} + /** A chat line landed on some technician's transcript — a received line, or the * echo of one we just sent. `peer` is the technician's canonical device id (the * thread key); `message` is the line. Drives the chat panel live. */ diff --git a/gui/src/types.ts b/gui/src/types.ts index e690fe1..3211695 100644 --- a/gui/src/types.ts +++ b/gui/src/types.ts @@ -159,6 +159,10 @@ export interface AccessRow { grant: Grant | null; /** The technician's live session right now, or null when not connected. */ live: LiveSession | null; + /** What their live routes actually carry right now — the chip's truth. A + * connected session with `viewing` null/false means chat only (or the + * console was closed): no "Viewing your screen". */ + viewing: { screen: boolean; control: boolean } | null; } /** The OS background-service status, from the `cec-support-service` crate. */ diff --git a/gui/src/ui/AccessList.svelte b/gui/src/ui/AccessList.svelte index 5f720c2..79ac3cf 100644 --- a/gui/src/ui/AccessList.svelte +++ b/gui/src/ui/AccessList.svelte @@ -95,9 +95,14 @@ {#if r.grant && remaining(r.grant)} {remaining(r.grant)} {/if} - {#if dot === "live"} + {#if r.viewing?.screen} + - {r.live?.want_control ? "Controlling your screen" : "Viewing your screen"} + {r.viewing.control ? "Controlling your screen" : "Viewing your screen"} {/if}