feat(desktop-sdk): expose focused-session state atoms to plugins

Disk plugins read app state exclusively through host.state, which only
exposed the primary workspace tab ($activeSessionId). In the multi-tile
layout, clicking a tile never touches that atom — and tile focus is a
pure renderer concern, invisible to both gateway RPC and the event
stream — so a plugin cannot follow the session the user is actually
looking at.

The core statusbar solves this same problem by reading the focused-
session atoms (use-statusbar-items.tsx). Widen the generic plugin
surface with the same signals, per the contribution rubric:

- host.state.focusedSessionId — runtime id of the focused session
  (interacted tile, else the primary), the key for session.* RPC
- host.state.focusedStoredSessionId — durable id for navigation and
  session-list matching
- host.state.focusedUsage — live streamed UsageStats projection
  (context_used/max/percent, tokens, cost_usd), no RPC needed

Additive only; no existing behavior changes. tsc --noEmit clean.
Verified end-to-end with a disk plugin that now tracks the focused
session across tiles.
This commit is contained in:
lepetitprince716-prog
2026-08-06 12:23:14 -04:00
committed by Teknium
parent be38224b49
commit 68bd8befd2

View File

@@ -45,8 +45,14 @@ import {
$gatewayState,
$selectedStoredSessionId
} from '@/store/session'
import { $focusedSessionState, $focusedStoredSessionId, $sessionStates } from '@/store/session-states'
import {
$focusedRuntimeId,
$focusedSessionState,
$focusedStoredSessionId,
$sessionStates
} from '@/store/session-states'
import { runGatewayRestart } from '@/store/system-actions'
import type { UsageStats } from '@/types/hermes'
// -- state: readonly views over the app's live atoms -------------------------
@@ -109,6 +115,10 @@ if (typeof window !== 'undefined') {
$narrowViewport.listen(refresh)
}
/** Live usage of the FOCUSED session, projected out of the streamed session
* state — the same readout the core statusbar's context chip paints. */
const $focusedUsage = computed($focusedSessionState, state => state?.usage ?? null)
export const host = {
state: {
/** Runtime id of the active chat session (null on a fresh draft). */
@@ -126,6 +136,17 @@ export const host = {
busyBySession: readonlyAtom<Record<string, boolean>>($busyBySession),
/** Active workspace cwd ('' when detached). */
cwd: readonlyAtom<string>($currentCwd),
/** Runtime id of the FOCUSED chat session — the interacted tile, else the
* primary. Prefer this over `activeSessionId` for any readout that
* should follow the user between tiles (context, tokens, cost). */
focusedSessionId: readonlyAtom<null | string>($focusedRuntimeId),
/** Stored (durable) id of the focused session — for navigation and
* session-list matching, where runtime ids don't survive reloads. */
focusedStoredSessionId: readonlyAtom<null | string>($focusedStoredSessionId),
/** Live usage snapshot of the focused session (`context_used` /
* `context_max` / `context_percent`, token counts, `cost_usd`) —
* streamed by the backend, no RPC needed. Null while unresolved. */
focusedUsage: readonlyAtom<null | UsageStats>($focusedUsage),
/** Gateway socket state: 'idle' | 'connecting' | 'open' | …. Not turn-busy. */
gateway: readonlyAtom<string>($gatewayState),
/** Current main model slug. */