Repository navigation
feat(tui_gateway): client-identity source so desktop/dashboard/mobile aren't all "tui" - #175
Merged
Merged
Conversation
… aren't all "tui"
The desktop app, the in-browser dashboard chat, and the Ink stdio TUI all drive
the same tui_gateway JSON-RPC server, which hardcoded platform="tui" on every
agent it built. So every desktop-app turn recorded platform="tui" in blackbox
(turns.platform) — the source dimension on the token/cost dashboard couldn't
tell the desktop client from the terminal, and a future mobile app would fold
in too.
Let a client self-declare a `source` on session.create/resume; thread it
through _make_agent -> AIAgent(platform=...) (which already feeds the blackbox
on_session_end hook), and store it on the session dict so the deferred/lazy
build, /new reset, resume, and branch paths all preserve it. `source` is
untrusted client input feeding a persisted DB dimension, so it is sanitized to
a conservative slug (`^[a-z][a-z0-9_-]{0,23}$`) with a `tui` fallback — the
label stays open-ended (new clients name themselves; no central enum to keep in
sync, per the existing "fixed set of platform names goes stale" note).
Desktop app: stamp source:'desktop' once, centrally, in the requestGateway
wrapper for the session-originating methods (create/resume) — never overriding
an explicit caller source (e.g. the dashboard sidecar's 'tool').
No schema/blackbox change (the platform pipe already existed); stdio Ink and
internal rebuilds keep the historical "tui" default so nothing regresses.
Historical desktop turns are indistinguishable from real TUI turns, so the
clean split starts at deploy (no backfill possible).
Tests: tui_gateway/test_client_source_platform.py (sanitizer table + _make_agent
platform derivation); desktop use-gateway-request.test.tsx (injection contract:
stamps create/resume, skips other methods, never overrides explicit source).
The new source kwarg on _make_agent tripped three narrow monkeypatch fakes in tests/test_tui_gateway_server.py (session_id/session_db-only signatures). Widen them to **_kw so they tolerate the new kwarg, matching the sibling fakes that already splat. No behavior change.
_sanitize_client_source used str(raw or ''), so a JSON-RPC boolean true would coerce to the slug 'true' instead of falling back to 'tui'. Guard with isinstance(raw, str) up front; add True/0/[] cases to the sanitizer table.
Kyzcreig
added a commit
that referenced
this pull request
Jul 4, 2026
* feat(dashboard): web chat client self-declares source='dashboard' The in-browser dashboard chat (web/), the Electron desktop app, and the Ink TUI all drive the same tui_gateway JSON-RPC server, which stamps platform='tui' by default. PR #175 taught the Electron app + backend to thread a client 'source' so turns are attributed per-client (desktop/dashboard/mobile) on the token dashboard — but the WEB dashboard client (web/src/lib/gatewayClient.ts) was never updated, so browser-dashboard turns still recorded as 'tui'. Override GatewayClient.request to stamp source:'dashboard' on the session-originating methods (session.create/session.resume) when the caller hasn't set an explicit source (the sidebar sidecar session deliberately sends source:'tool'). Mirrors the Electron app's requestGateway injection exactly. Backend already sanitizes + threads source -> AIAgent(platform=...) (#175); no backend change needed. Test: web/src/lib/gatewayClient.test.ts (4) — stamps create/resume, skips other methods, never overrides an explicit source. * ci: re-trigger checks (apps/desktop/node_modules ENOTDIR runner flake, not a code issue)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The desktop app, the in-browser dashboard chat, and the Ink stdio TUI all drive the same
tui_gatewayJSON-RPC server, which hardcodedplatform="tui"on every agent it built. So every desktop-app turn recordsplatform="tui"in blackbox (turns.platform) — the source dimension on the token/cost dashboard can't tell the desktop client from the terminal, and a future mobile app would fold in too.Verified against a live install:
api_server/desktopnever appear in any blackbox DB; desktop chat rides/api/ws → tui_gateway.dispatch(same path as Ink over stdio), so it inherits the"tui"label.Fix
sourceonsession.create/session.resume._make_agent → AIAgent(platform=...)(which already feeds the blackboxon_session_endhook) and store it on the session dict so the deferred/lazy build,/newreset, resume, and branch paths all preserve it (they previously reset to"tui").sourceis untrusted client input feeding a persisted DB dimension, so it's sanitized to a conservative slug (^[a-z][a-z0-9_-]{0,23}$) with atuifallback. The label stays open-ended (new clients name themselves; no central enum — per the existing "fixed set of platform names goes stale" note in this file).source:'desktop'once, centrally, in therequestGatewaywrapper for the session-originating methods — never overriding an explicit caller source (e.g. the dashboard sidecar's'tool').No schema/blackbox change (the
platformpipe already existed). Stdio Ink and internal rebuilds keep the historical"tui"default, so nothing regresses. Cache-safe: the label is fixed per session, set before agent creation, never mutated mid-conversation.No backfill possible: historical desktop turns are indistinguishable from real TUI turns, so the clean split starts at deploy.
Tests
tests/tui_gateway/test_client_source_platform.py— sanitizer table (case/trim/reject-hostile) +_make_agentplatform derivation (declared → platform;None→tui; hostile →tui).apps/desktop/src/app/gateway/hooks/use-gateway-request.test.tsx— injection contract: stamps create/resume, skips other methods, never overrides an explicit source.test_protocol.pyfakes to accept the new_make_agent(source=...)kwarg.tsc --noEmitclean.