From 5fa092b4819c43bec6fcdeb7aaccf113e85101e7 Mon Sep 17 00:00:00 2001 From: lepetitprince716-prog Date: Thu, 6 Aug 2026 15:49:30 -0400 Subject: [PATCH] =?UTF-8?q?fix(desktop-sdk):=20address=20adversarial=20rev?= =?UTF-8?q?iew=20=E2=80=94=20type=20honesty,=20real=20tile=20coverage,=20d?= =?UTF-8?q?ocs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Independent second-pass review found three gaps: - focusedUsage is null | UsageStats, not Partial — ClientSessionState.usage is the full type (app/types.ts) and its only write site seeds the four required fields before merging (gateway-event.ts). The earlier Partial annotation traced the wrong type (SessionRuntimeInfo, an RPC payload). Comment now names the genuinely optional fields instead. - The tile-focus contract test never seeded $sessionTiles/$sessionStates, so it proved focusedStoredSessionId follows a tile but could not distinguish focusedSessionId/focusedUsage working from broken. Seed a bound runtime with distinct usage and assert both readout atoms move. - The two public host.state references (website docs + bundled skill reference) enumerated the old six atoms; plugin authors would never discover the new ones. Both lists updated. tsc/eslint/vitest green (4/4). --- apps/desktop/src/sdk/host-state.test.ts | 23 ++++++++++++++++++- apps/desktop/src/sdk/index.ts | 6 ++--- .../references/desktop-plugins.md | 15 ++++++++---- .../developer-guide/desktop-plugin-sdk.md | 3 +++ 4 files changed, 38 insertions(+), 9 deletions(-) diff --git a/apps/desktop/src/sdk/host-state.test.ts b/apps/desktop/src/sdk/host-state.test.ts index 1dd2590092..00a94931aa 100644 --- a/apps/desktop/src/sdk/host-state.test.ts +++ b/apps/desktop/src/sdk/host-state.test.ts @@ -59,7 +59,7 @@ describe('host.state focused-session atoms', () => { }) it('follows the interacted tile while the primary-only atom stays put', async () => { - const { host, session } = await setup() + const { host, session, states } = await setup() const tree = await import('@/components/pane-shell/tree/store') const model = await import('@/components/pane-shell/tree/model') const { registry } = await import('@/contrib/registry') @@ -85,9 +85,30 @@ describe('host.state focused-session atoms', () => { const primaryBefore = session.$activeSessionId.get() const primarySelection = session.$selectedStoredSessionId.get() + // Bind the tile to a live runtime with its own usage, so the readout + // atoms (focusedSessionId / focusedUsage) — not just the navigation id — + // are proven to follow the tile. + const tileUsage = { + calls: 3, + input: 1200, + output: 300, + total: 1500, + context_used: 42000, + context_max: 200000, + context_percent: 21, + cost_usd: 0.0123 + } + + states.$sessionTiles.set([{ storedSessionId: 'tile-a', runtimeId: 'runtime-tile-a' }]) + states.$sessionStates.set({ + 'runtime-tile-a': { storedSessionId: 'tile-a', usage: tileUsage } as never + }) + // Focusing the tile zone moves the focused atoms onto the tile's session… tree.noteActiveTreeGroup('grp-side') expect(host.state.focusedStoredSessionId.get()).toBe('tile-a') + expect(host.state.focusedSessionId.get()).toBe('runtime-tile-a') + expect(host.state.focusedUsage.get()).toBe(tileUsage) // …while the primary-only atom a plugin used to rely on does not move. expect(host.state.activeSessionId.get()).toBe(primaryBefore) diff --git a/apps/desktop/src/sdk/index.ts b/apps/desktop/src/sdk/index.ts index 1d71e32287..05ad5208c9 100644 --- a/apps/desktop/src/sdk/index.ts +++ b/apps/desktop/src/sdk/index.ts @@ -146,9 +146,9 @@ export const host = { /** 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. - * Partial: the backend streams whichever fields changed, so every - * field read needs a fallback. */ - focusedUsage: readonlyAtom>($focusedUsage), + * The UsageStats-optional fields (context_*, cost_usd) arrive as the + * backend reports them, so read them with a fallback. */ + focusedUsage: readonlyAtom($focusedUsage), /** Gateway socket state: 'idle' | 'connecting' | 'open' | …. Not turn-busy. */ gateway: readonlyAtom($gatewayState), /** Current main model slug. */ diff --git a/skills/autonomous-ai-agents/hermes-agent/references/desktop-plugins.md b/skills/autonomous-ai-agents/hermes-agent/references/desktop-plugins.md index 4e34d70909..04cfbd3a53 100644 --- a/skills/autonomous-ai-agents/hermes-agent/references/desktop-plugins.md +++ b/skills/autonomous-ai-agents/hermes-agent/references/desktop-plugins.md @@ -56,11 +56,16 @@ The ONLY import surface is `@hermes/plugin-sdk` (plus `react` / - `host.state.*` — readonly reactive atoms: `activeSessionId`, `busy`, `awaitingResponse`, `busyBySession`, `cwd`, `gateway` (socket state, not - turn-busy), `model`, `profile`, `viewport`. - `busy` is true while the focused chat is working after a send (thinking - and streaming). `awaitingResponse` is true until the first assistant - payload. `busyBySession` maps runtime session id → mid-turn, for rosters - that watch every session. Read with `.get()` in handlers, + turn-busy), `model`, `profile`, `viewport`, plus the tile-aware focused + session atoms: `focusedSessionId` (runtime id — key for `session.*` RPC), + `focusedStoredSessionId` (durable id — navigation / list matching), and + `focusedUsage` (live streamed `UsageStats` of the focused session, no RPC + needed). `busy` is true while the focused chat is working after a send + (thinking and streaming). `awaitingResponse` is true until the first + assistant payload. `busyBySession` maps runtime session id → mid-turn, + for rosters that watch every session. Prefer the focused atoms for any + readout that should follow the user between tiles. Read with `.get()` + in handlers, `useValue(atom)` in components. - `host.request(method, params)` — gateway JSON-RPC (sessions, config, skills, cron — everything the app uses). diff --git a/website/docs/developer-guide/desktop-plugin-sdk.md b/website/docs/developer-guide/desktop-plugin-sdk.md index 73369e66ec..d0c4892277 100644 --- a/website/docs/developer-guide/desktop-plugin-sdk.md +++ b/website/docs/developer-guide/desktop-plugin-sdk.md @@ -378,6 +378,9 @@ host.state.activeSessionId // ReadableAtom host.state.awaitingResponse // ReadableAtom true until the first assistant payload host.state.busy // ReadableAtom focused chat is working after a send host.state.busyBySession // ReadableAtom> runtime id → mid-turn +host.state.focusedSessionId // ReadableAtom (runtime id of the FOCUSED session — tile-aware; prefer for session.* RPC) +host.state.focusedStoredSessionId // ReadableAtom (durable id — navigation / session-list matching) +host.state.focusedUsage // ReadableAtom (live streamed usage of the focused session, no RPC needed) host.state.cwd // ReadableAtom host.state.gateway // ReadableAtom socket state ('idle' | 'connecting' | 'open' | …) host.state.model // ReadableAtom