feat(ui): Brain Orb — Cornelius-class agent page: static render + live scope control (abilityai/trinity-enterprise#58)#1389
Conversation
…s (static-render foundation) (abilityai/trinity-enterprise#58) Capability-gated /agents/:name/brain page that renders an agent's live 3D knowledge-graph orb from data the agent produces in its own container. This is the static-render foundation — voice, scope-mutation, KB-write actions, transcript capture, and headless-skill injection are deferred to later epic children. Default OFF, so zero impact on existing agents and the UI. - First-party CSP-clean assets (public/brain-orb/): the verbatim orb's inline module is externalized to orb.js; three/marked/DOMPurify/JetBrains-Mono are vendored locally; it runs under prod CSP script-src 'self' / font-src 'self' with NO nginx change (the #979 trap was agent-origin + inline; this is first-party + external). Note bodies are DOMPurify-sanitized (H-005). - Frontend: lazy /agents/:name/brain route (platform-flag beforeEnter guard); AgentBrainOrb.vue thin chrome + same-origin iframe; JWT handed to the iframe via origin-pinned postMessage (never a URL) — no new ticket primitive; Brain tab gated on brainOrbAvailable AND the template.yaml `brain-orb` capability. - Backend: GET /api/agents/{name}/brain-orb/data — AuthorizedAgentByName read proxy via agent_httpx_client (#1159), byte pass-through (no re-serialize), flag-gated 404, 503/504/502 error mapping. - Agent-server: GET /api/brain-orb/data streams data.json via FileResponse (agent owns generation — Invariant #8; auto-gated by the #1159 middleware). - brain_orb_available feature flag (BRAIN_ORB_ENABLED, default OFF). - 10 unit tests (backend proxy branches + agent-server read); architecture.md, feature-flows/brain-orb.md, requirements §46. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…in-place rebuild (abilityai/trinity-enterprise#58)
Adds the button-driven "self-rendering" scope-control loop on top of the
static-render foundation. No Gemini/voice — that stays deferred to a later child.
- Orb: route the scope panel's fetches through the per-agent brain-orb proxy
base carrying the platform JWT (replaces the localhost voice proxy + per-start
X-Orb-Token); decouple setScope from the voice-session token (gate on the
Trinity embed); re-fetch /brain-orb/data after a remount; un-hide the scope
panel (S key). Voice + action panels stay hidden.
- Agent-server: GET /api/brain-orb/scopes + POST /api/brain-orb/scope run the
agent's ~/.trinity/brain-orb/{scopes,scope} convention hooks (mirrors
~/.trinity/pre-check) via hardened async subprocess (timeout-kill, output cap,
JSON-parse + non-zero-exit guards); 404 when a hook is absent. The agent owns
scope state + the re-export (Invariant #8).
- Backend: GET .../brain-orb/scopes (AuthorizedAgentByName, read) + POST
.../brain-orb/scope (OwnedAgentByName — the only mutating brain-orb route;
64 KB body cap, 200s timeout); shared gate/proxy helper, byte pass-through.
- 14 more unit tests (24 total) incl. real-subprocess hook coverage (stdin
forwarding, timeout->504, invalid-JSON->502, non-zero-exit->502). Docs:
architecture.md, requirements §46 FR-6, feature-flows/brain-orb.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ad-only KB search (abilityai/trinity-enterprise#60)
Adds the voice control surface on top of the Phase 1/2 orb (static render +
live scope control). The browser holds its own Gemini Live session — it connects
DIRECTLY to Gemini using a short-lived, config-locked ephemeral token minted by
Trinity — so the voice→tool→orb loop stays in-browser and Trinity never proxies
the audio.
Backend:
- POST /api/agents/{name}/brain-orb/voice-token (AuthorizedAgentByName, per-user
rate-limited): mints an ephemeral Gemini Live token via a dedicated v1alpha
genai client (NOT the cached voice singleton). live_connect_constraints lock
the model + the whole config incl. a READ/VISUAL/SCOPE-only tool manifest;
uses=1; ~60s new-session window; expiry = VOICE_MAX_DURATION. Response field is
`ephemeral_token` (never `token`, which would flip orb.js's Phase-4 write
surface on). services/brain_orb_voice_service.py.
- POST /api/agents/{name}/brain-orb/tool (read-only): proxies to the agent's
~/.trinity/brain-orb/search hook (scope-aware, no writes; 404 when absent).
- BRAIN_ORB_VOICE_ENABLED flag → brain_orb_voice_available (= flag && GEMINI_API_KEY),
distinct from brain_orb_available.
Frontend:
- New CSP-clean voice tile: public/brain-orb/voice/{orb.html,voice.js,mic-worklet.js}.
Adapted from the Cornelius page with the hardcoded API key and p5-from-CDN
stripped, logic externalized (script-src 'self'), the mic worklet shipped as a
same-origin file. Hand-rolled Gemini Live client uses the Constrained method +
access_token param + setup{model} (config locked in the token).
- orb.js: mints the token via the JWT-gated broker and relays only the ephemeral
token to the nested voice iframe over postMessage (JWT never enters the iframe
or a URL); repoints navigate_to_note's vault fallback at the read-only /tool;
un-hides the voice tile on the voice flag. Same-origin postMessage guards.
- AgentBrainOrb.vue: allow="microphone" on the host iframe; relays voiceAvailable.
Writes stay off by construction (locked manifest + no /session route). Tests
extend tests/unit/test_brain_orb.py (39 pass): mint gating/field-name/v1alpha/
rate-limit, /tool proxy, search hook, and the locked-manifest guard. The live
ephemeral handshake + end-to-end loop are /verify-only (need a live agent+Gemini).
Docs: requirements §46 FR-7, feature-flows/brain-orb.md Phase 3, architecture.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…or the voice tile (abilityai/trinity-enterprise#60) Local deploy surfaced that the pinned google-genai==1.12.1 has NO `auth_tokens` attribute, so the Brain Orb voice-token mint 502'd (`'Client' object has no attribute 'auth_tokens'`) — the ephemeral-token API was added in a later release. - docker/backend/Dockerfile: google-genai 1.12.1 → 1.63.0. Verified in the local stack: POST /voice-token returns a real auth_tokens/… token (model+voice locked, 11 read/visual/scope tools, zero write tools, no bare `token` field), and services/gemini_voice.py (existing VOICE-001 backend-proxied path) still imports cleanly under 1.63. A live VOICE-001 voice-session smoke test is a pre-merge item. - docker-compose.yml: pass BRAIN_ORB_ENABLED + BRAIN_ORB_VOICE_ENABLED through to the backend container (they were never wired into compose, so the flags couldn't reach config.py even when set in .env). - Docs: brain-orb.md / voice-chat.md updated for the new pin. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…orb animation (abilityai/trinity-enterprise#60)
Two fixes surfaced by local testing on a real Cornelius agent:
1. STRICT GATING — the orb is no longer launchable on a non-Cornelius agent, even
via a raw URL. The route guard (router/index.js beforeEnter) now fetches
GET /api/agents/{name}/info and redirects to AgentDetail unless the agent
declares the `brain-orb` capability (in addition to the platform flag) — not
just the tab. Previously the raw /agents/{name}/brain URL loaded an empty orb
for any agent; now a non-capable (or stopped) agent redirects. The voice tile
inside the page then only needs the platform voice flag (capability already
guaranteed by the guard).
2. VOICE ORB ANIMATION RESTORED — the audio-reactive orb in the voice tile (the
p5 smoke/core sketch) was cut when p5 was removed for loading from a CDN
(CSP script-src 'self'). Re-added by vendoring p5 locally
(voice/vendor/p5.min.js — no eval/Function, CSP-clean) and restoring the
sketch + an output AnalyserNode, so the orb breathes when idle and pulses with
Cornelius's speech (enqueueAudio routes each output buffer through outAnalyser).
Verified end-to-end on a locally-deployed Cornelius agent (brain-orb capability):
the orb renders the real graph, the Brain tab appears only on Cornelius, a raw
URL to a non-Cornelius agent redirects, and the voice tile shows the animated orb;
the /tool search + /scopes hooks and the ephemeral-token mint all function.
Docs: requirements §46 FR-2/FR-7, feature-flows/brain-orb.md, architecture.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Resolve by running |
…b (abilityai/trinity-enterprise#60) - Auto-start: opening the voice tile (V key / dock chip) now starts the conversation automatically instead of requiring a manual "Start" click. The tile auto-starts on load, and re-opening an already-loaded tile re-starts via an `orb-voice-start` message. The Start button remains as a retry if the mic is denied (auto-start → ERROR). - Voice-orb jank: cap the p5 audio-reactive orb to pixelDensity(1). The sketch is fill-bound (many radial gradients/frame); at retina 2x it costs ~4x and starves the main WebGL knowledge orb of frames, so the big orb visibly jerks during its slow rotation/fly. 1x is imperceptible on the small voice orb and frees the main render loop. (The main orb's animation loop is unchanged — byte-identical to the upstream Cornelius source, dt-clamped; the jank was pure frame contention added by the restored voice orb inside the Trinity SPA + nested iframes.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…abilityai/trinity-enterprise#60) - Brain TAB is now an in-page panel (BrainPanel.vue) — brain settings placeholder + an "Open Brain Orb" launch button — instead of auto-jumping to the full-page orb on tab select (removed the route-push watcher). Clearer, and leaves room for real brain settings. - Header brain LOGO: a small orb glyph in the AgentDetail header (AgentHeader), capability-gated (platform flag AND the agent's brain-orb capability), that opens the orb page directly. - Every orb WIDGET now has a small, always-visible close ✕: the `.tile-x` is no longer hover-only (opacity .32, brightens on hover), and lens/scope/search — which had none — get one too. Search gains a `find` dock chip to re-open; the briefing cards re-inject their ✕ after their innerHTML re-render. Every panel is closeable and re-openable from the dock. - Also cap the main WebGL orb to pixelRatio 1.5 on hi-DPR displays (was 2.0) for GPU headroom inside the Trinity SPA — a retina jank mitigation (verify on device). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- orb.js renderNoteInto: route every path to innerHTML through DOMPurify.sanitize (marked output AND the raw-markdown fallbacks), degrading to textContent when DOMPurify is somehow absent. Clears the js/xss + js/xss-through-dom alerts and hardens the previously unsanitized fallback paths (H-005). - Add .github/codeql/codeql-config.yml with paths-ignore for vendored third-party bundles (three.js/marked/DOMPurify under public/**/vendor, **/vendor/**, **/*.min.js) and wire it via config-file. Clears the js/insecure-randomness alert in vendored three.module.js (not our code). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ink) (abilityai/trinity-enterprise#61) Completes the Brain Orb write surface with the low-risk half of Phase 4: owner/admin-only capture-a-note and link-two-notes. Split from the full Phase 4 during /autoplan (two independent reviews) — run_skill (headless claude -p exec), automatic transcript capture, and transcript injection are deferred to trinity-enterprise#66 (they carry the exec/injection risk and depend on an unproven Gemini-Live-transcription path). Backend: - routers/agent_brain_orb.py: GET /brain-orb/actions + POST /brain-orb/action (OwnedAgentByName). Action verb enum-validated at the boundary (run_skill/ capture_transcript -> 400, never forwarded), body-capped (413), rate-limited per (user, agent, action), audit-logged, Idempotency-Key deduped (Invariant #18, key folded per verb; effect_guard doesn't fit — no execution_id). - services/brain_orb_voice_service.py: can_write folds capture_note/link_notes into the LOCKED voice manifest only for owners; shared users keep the read-only Phase-3 manifest. can_write computed in the mint route. - config.py + routers/settings.py: BRAIN_ORB_WRITE_ENABLED kill-switch (default OFF, distinct from BRAIN_ORB_ENABLED) -> brain_orb_write_available. Agent-server (Invariant #5 mirror): - routers/brain_orb.py: GET/POST /api/brain-orb/action[s] via the hardened _run_hook over the agent's ~/.trinity/brain-orb/action convention hook (agent owns the write, Invariant #8; stdin-only, no shell string). Frontend: - orb.js: rewire initActions/postAction/doCapture/doLink from the dead localhost voice proxy (X-Orb-Token) to the broker + Bearer JWT + Idempotency-Key; un-hide #actions via body.brain-orb-write only after the broker confirms owner + flag + hook; add capture_note/link_notes to ORB_TOOLS; double-submit re-entrancy guard. - orb-trinity.css / AgentBrainOrb.vue / stores/sessions.js: gate the panel on the write flag, relay writeAvailable in the init handshake. Tests: 14 new cases (owner-gate 403, flag-off 404, no-hook 404, enum 400, 413, 429 + claim-release, idempotency replay/409, owner-only voice manifest, agent-server action hook). 62/62 green. Security: /review 0 critical, /cso --diff 0 findings. Docs: requirements §46 FR-8, feature-flows/brain-orb.md Phase 4a, learnings (effect_guard vs Idempotency-Key), cso-diff-2026-07-01 report. Refs abilityai/trinity-enterprise#61 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…he flag reaches the container (abilityai/trinity-enterprise#61) The Phase 4a write flag existed in config.py + settings feature-flags but was never added to the backend `environment:` block in docker-compose.yml (mirrors BRAIN_ORB_ENABLED/BRAIN_ORB_VOICE_ENABLED at lines 40-41). Without it the flag never reaches the container, so brain_orb_write_available stays false and the write routes 404 regardless of .env. Surfaced during the localhost demo. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ost-session processing (abilityai/trinity-enterprise#66)
Mirrors the original cornelius-internal voice proxy (resources/agent-visualization/
voice/{proxy_server,transcript_io}.py): the client captures per-turn transcription,
the agent renders + saves it, Trinity brokers. De-scoped per direction — transcript
saving (for debug/analyze) + an optional post-session prompt; run_skill (arbitrary
exec) stays out.
- Mint (brain_orb_voice_service): add input_audio_transcription +
output_audio_transcription to the LOCKED LiveConnectConfig so the constrained
ephemeral token returns per-turn transcription. Verified: auth_tokens.create
accepts it and the mint returns a real token (de-risks the F6.1 unknown).
- voice.js: buffer content.inputTranscription/outputTranscription into conversation
events (session_start/user_turn/model_turn/tool_call/session_end); flush in
endConversation() (the correct seam — onclose early-returns on wsClosedByUs) AND
the error-close branch; relay {session_id, events} to the parent orb.
- orb.js: on 'orb-capture-transcript' (owner + ACTIONS.enabled) →
postAction('capture_transcript', {..., process:true}) with session_id as the
Idempotency-Key (double session-end = one save). postAction gains an optional key.
- Backend (agent_brain_orb): accept capture_transcript + process_transcript verbs
(still OwnedAgentByName, audited); raise the action body cap to 1 MiB for whole
conversations. run_skill remains rejected (400).
- Agent-server (brain_orb): raise the /action body cap to 1 MiB to match.
- Agent action hook (cornelius template): render markdown transcript (ported
transcript_io) → resources/inbox/Voice Conversations/; process_transcript runs the
agent's configured prompt (~/.trinity/brain-orb/voice-postprocess.md — configuring
it is the opt-in) over the transcript via a DETACHED claude -p with the transcript
on STDIN (no shell string → no command injection), writing a processed note.
Security posture (owner-gated exec): OwnedAgentByName + owner-authored prompt +
owner's-own-transcript; transcript via stdin closes command injection. Residual
(follow-up): the post-process claude -p uses --dangerously-skip-permissions (full
tools) — a tool-constrained sub-invocation + transcript credential-sanitization are
the hardening path if this graduates beyond a debug tool.
Verified on localhost: mint accepts transcription config; synthetic voice events
render + save to the KB; process_transcript spawns the detached claude -p (ran end
to end — only blocked by the agent's Claude credit balance, not code). Full
live-audio transcription streaming is a manual voice-session check. 64 unit tests
green.
Refs abilityai/trinity-enterprise#66
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…te control (abilityai/trinity-enterprise#67, #68) Fixes the feedback from the live voice test: notes/links created via the orb landed in the inbox but never appeared on the graph, and there was no way to confirm integration happened. #67 (mechanism): - Backend `POST /brain-orb/refresh` (OwnedAgentByName, 200s timeout mirroring /scope, audited `brain_orb_refresh`) → agent-server `POST /api/brain-orb/refresh` → the `action` hook's `refresh` verb reindexes + re-exports data.json (folds inbox notes + _links.md edges into the graph; agent owns generation, Invariant #8). - orb.js `refreshGraph()` refetches /data and rebuilds in place (same machinery as setScope). Auto-triggered after capture/link; voice writes debounced ~4s so a burst coalesces into one rebuild. #68 (observability): - Visible "↻ integrate & refresh" control in the actions panel, an "integrating…" state, and a "graph updated · +N notes, +M links" / "graph up to date" confirmation toast so the user can see the write land. Verified on localhost: capture → refresh folds the note in as a real graph node (1072 → 1079 nodes, +3 edges), and the UI control rebuilds with the confirmation toast. 70 unit tests green (6 new refresh cases). Refs abilityai/trinity-enterprise#67 abilityai/trinity-enterprise#68 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…yai/trinity-enterprise#70)
Voice-test feedback: navigating to a note or highlighting a topic/group lit the
target but didn't zoom/center to fit it — it settled at a fixed distance
(focusNode→195, highlightGroup→250) regardless of where the selection sat or how
spread out it was, so the highlight read as "not focused."
Add frameNodes(indices): centers on the selection's centroid direction AND derives
a fit distance from the selection's max shell radius + angular spread vs the camera
FOV (single note zooms in; a spread-out cluster zooms out), clamped to the existing
zoom bounds and never inside the node shell. Route focusNode and highlightGroup(frame)
through it, replacing the fixed 195/250.
Verified on localhost: navigate to a single note 330→144 (zooms in; was fixed 195);
highlight a cluster computes a per-group fit; the highlighted group is centered and
framed. (Client-side WebGL camera math — no pytest surface; verified in-browser.)
Fixes abilityai/trinity-enterprise#70
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ding refresh on miss (abilityai/trinity-enterprise#70) Second round of voice-test feedback: after "create a node, link it to Dopamine, now show me the connection," the user saw the Dopamine node but not the edge to the new note. Two causes, both under the navigate/focus (#70) umbrella: 1. Framing showed a LONE node. focusNode already lit up the node + neighbours + drew the connecting edges (setHover), but frameNodes([i]) fit only the single node — so the other endpoint of a just-made link sat off-screen. Now frame the node TOGETHER with its highlighted neighbours (hoverSet), so both ends of the edge are in view. 2. Timing race. Voice writes integrate on a ~4s debounce, so navigate_to_note could fire before the new note was a graph node and fall back to the vault reader. Now, on a miss, if an integration is pending, flush it (refreshGraph) and retry before the vault fallback — so "create X → show me X" lands on the real graph node. Verified on localhost: navigate to the just-created "Random Node 24" frames it with Dopamine and draws the connecting edge (both endpoints visible, link line rendered). Fixes abilityai/trinity-enterprise#70 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…points (abilityai/trinity-enterprise#71, #72) Two more voice-test issues in the write→refresh loop: #72 — selection lost on refresh. applyData()→teardownGraph() wipes lastInspectedIdx/hover and node indices change, so a capture/link auto-refresh deselected whatever the user was looking at mid-task. refreshGraph() now snapshots the selection BY ID (inspected node, or highlighted group) before the rebuild and re-selects/re-frames it after; and it SKIPS the rebuild entirely when nothing changed (added_nodes==0 && added_edges==0) so a no-op refresh never disturbs the selection. Camera view was already preserved by teardown. #71 — link to a non-existent note silently "succeeded" (dangling reference, false success, no renderable edge). The agent action hook's `link` verb now validates BOTH endpoints resolve to real notes (graph node or inbox file) and returns a clear "note not found — create it first" error otherwise (agent-owned per Invariant #8; the orb already surfaces r.error). This repo change is the contract note + the orb selection fix; the demo cornelius hook was updated to match and the real agent adopts the same. Verified on localhost: selecting Dopamine then refreshing (with a pending capture forcing a real rebuild) keeps Dopamine selected + inspector shown; linking to a non-existent "Random Node 25" now returns a clear not-found error. Fixes abilityai/trinity-enterprise#71 abilityai/trinity-enterprise#72 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ntegrates it (abilityai/trinity-enterprise#72) Feedback: "add the node" captures + refreshes a few seconds later, but the new node isn't selected — you had to ask to select it separately. Now a capture sets a _pendingFocusTitle that refreshGraph() consumes after the rebuild: it focuses the just-created note (inspector + highlight + framing) once it's folded into the graph, taking priority over restoring the prior selection. Falls back to restoring the prior selection when there's no pending capture. Purely client-side — the agent doesn't need a second navigate_to_note call. Verified on localhost: a voice capture_note with no follow-up navigate ends with the new note auto-selected in the inspector after the debounced refresh. Refs abilityai/trinity-enterprise#72 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…toggle + prompt editor) (abilityai/trinity-enterprise#73) The Brain tab was a launch button + a "no settings yet" placeholder. Give it its first real control: configure the post-voice-session processing (#66) from the UI. - BrainPanel.vue: "Post-voice processing" section — an on/off toggle ("run a processing step after each voice session") + a prompt textarea + Save, loaded on mount. Shown only when brain_orb_write_available + agent running. - Backend GET/PUT /api/agents/{name}/brain-orb/postprocess (OwnedAgentByName, write-flag-gated, PUT audited) → agent-server GET/PUT /api/brain-orb/postprocess (direct file I/O). Config is agent-owned (Invariant #8): ~/.trinity/brain-orb/voice-postprocess.json = {enabled, prompt}, with the legacy .md as a prompt-only read fallback. - The action hook's _spawn_postprocess now gates on the `enabled` flag (default OFF) — disabling keeps the saved prompt but skips the claude -p. Verified on localhost: the Brain tab shows the toggle + prompt (loaded from config); saving writes voice-postprocess.json and GET reflects it; with enabled:false a capture_transcript still saves the transcript but the post-process is skipped ("post-processing disabled"). 76 unit tests green (6 new). Refs abilityai/trinity-enterprise#73 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…gn system (abilityai/trinity-enterprise#73) The Brain-tab Save + Open Brain Orb buttons used bg-state-autonomous-500/600 (amber, and a non-standard lighter shade — the only -500/-600 primary buttons in the app). The app-wide primary-action convention is bg-blue-600 hover:bg-blue-700 (22 usages); focus rings are focus:ring-blue-500 (35 usages). Aligned the buttons, the enable checkbox accent, and the prompt textarea focus ring to blue. Amber (state-autonomous) is kept ONLY for the Brain Orb identity (the orb icon + BETA badge). No shared Button component exists, so this matches the established Tailwind convention rather than a component. Verified on localhost: both buttons compute rgb(37,99,235) = blue-600. Refs abilityai/trinity-enterprise#73 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…meout stagger, safe audit (abilityai/trinity-enterprise#61) Applies the fixes from the final pre-merge review of the Phase-4 branch: - F1 (med): /refresh drove the agent action hook but was gated only on BRAIN_ORB_ENABLED — the write kill-switch wasn't airtight. Added _require_write_enabled() + test_refresh_write_flag_off_404. - F3 (low/med): /action backend timeout == agent-server timeout (60/60); a premature backend 504 could release the idempotency claim → double-write on retry. Staggered backend to 90s > agent 60s (mirrors the scope route). - F4 (low): /postprocess PUT capped at 1MiB vs the agent-server's 64KiB; capped the backend at 64KiB to match. - F5 (low): audit awaited after complete() unguarded → an audit failure 500s a completed write (fresh-key retry double-writes). Added _safe_audit best-effort wrapper on all post-write audit calls. - orb-tool postMessage handler lacked an origin check; 4a added owner-write tools to ORB_TOOLS. Added the origin guard (matches the two sibling handlers; CSP frame-ancestors already mitigated it). - F2 (med): the committed 4a-only CSO report declared "0 findings / no claude -p" — stale vs the shipped exec/transcript surface. Rewrote it as the full-branch final review (findings + resolution). Auth boundaries, can_write split, enum gate, stdin-only exec, and idempotency ordering all verified clean by an independent reviewer. 77 unit tests green. Learning captured (kill-switch completeness across incremental routes). Refs abilityai/trinity-enterprise#61 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat(ui): Brain Orb Phase 3 — client-held Gemini Live voice tile + read-only KB search (Abilityai/trinity-enterprise#60)
# Conflicts: # docs/memory/learnings.md # docs/memory/requirements.md
…NG session-id fallback (abilityai/trinity-enterprise#58) - js/insecure-randomness (high): _uuid()'s non-secure-context fallback used Math.random(); the id becomes the capture_transcript Idempotency-Key, so the fallback now builds a v4 UUID from crypto.getRandomValues. - js/cross-window-information-leak (medium): the transcript relay posted to '*'; all orb↔voice-iframe postMessage sends (both directions, incl. the ephemeral token reply) now target window.location.origin — receivers already enforced same-origin, this pins the send side too. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…hardening, flags, route wiring (abilityai/trinity-enterprise#61) /update-tests review of the Phase 1-4b surface. 26 new tests, all passing (103 total across the three files against the live backend). tests/unit/test_brain_orb.py (+18, file total 95): - POST /scope owner-gate 403 — the one mutating route missing the proof its Phase-4 siblings had (incomplete-fix-chain class) - PUT /postprocess 413 body cap (review F4) - idempotency semantics: key folds the action verb; claim released on agent 502/504; success completes with the parsed snapshot; missing key = fail-open (Invariant #18) - audit-sink failure never masks a completed write (review F5, action+refresh) - agent-server _run_hook hardening: output cap → 502, non-executable hook → 404, scope/search 413 body caps - postprocess (#73): invalid JSON 400, 8000-char prompt truncation, legacy .md fallback, corrupt-JSON degrades to defaults - voice mint: _build_system_instruction clause selection + 2000-char prompt cap; new-session window < token expiry tests/test_settings.py (+3 SMOKE): feature-flags requires auth; brain_orb_* keys present + boolean; write⇒available compound-gate invariant (#61 kill-switch layering) — first coverage of any brain-orb feature-flag key. tests/test_brain_orb_api.py (new, 5 SMOKE): live-backend route-wiring proof (#1069 escape class) — the unit suite mounts the router on a synthetic app, so only these prove main.py registration + the real AuthorizedAgentByName / OwnedAgentByName chain. Test-runner catalog updated in the trinity-dev submodule (rides separately). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review — Brain Orb (automated review, Claude)Substantial, carefully-engineered PR. The trust boundary is right: backend proxy applies flag + 1. PR body materially understates the shipped scope (please update before merge). The summary says "Voice (Gemini Live), KB-write actions, transcript capture … remain deferred" and describes "two phases", but the diff ships all of it: 2. 3. Open-core policy check. This lands a lot of core backend + agent-server code and detailed public design docs ( Minor
Not blocking beyond items 1–3 (1 is a docs fix, 2–3 are confirmations). |
test(brain-orb): close coverage gaps — owner gate, idempotency, hooks, flags, route wiring (ent#61)
Summary
/agents/:name/brainpage that renders a Cornelius-class agent's live 3D knowledge-graph orb from data the agent produces in its own container — plus live scope control (mount/unmount → agent re-export → in-place rebuild).BRAIN_ORB_ENABLED+ a per-agentbrain-orbcapability token → zero impact on existing agents or the UI.Phase 1 — static render + read path
The orb is shipped as verbatim first-party static assets (
src/frontend/public/brain-orb/) hosted in a thin Vue view via a same-origin iframe — first-party + external scripts, exactly what prod CSPscript-src 'self'permits, so it runs with no nginx change (#979 only bit because it iframed agent-origin + inline scripts).three/marked/DOMPurify/JetBrains-Mono vendored locally; note bodies DOMPurify-sanitized (H-005). JWT reaches the iframe's fetch via origin-pinnedpostMessage(never a URL) — no new ticket primitive. Read proxyGET /api/agents/{name}/brain-orb/data(AuthorizedAgentByName) → agent-serverGET /api/brain-orb/datastreamsdata.json(FileResponse).Phase 2 — live scope control
The scope panel (
Skey) lists the agent's vault scopes; toggling one mutates the active set, the agent re-exports at the new scope (rewritingdata.json), and the orb re-fetches/dataand rebuilds in place. The old localhost voice-proxyX-Orb-Tokenis replaced by the platform JWT + an owner gate.GET .../brain-orb/scopes(AuthorizedAgentByName, read) +POST .../brain-orb/scope(OwnedAgentByName— the only mutating brain-orb route; 64 KB body cap, 200s timeout). Shared gate/proxy helper, byte pass-through.~/.trinity/brain-orb/{scopes,scope}convention hooks (mirrors~/.trinity/pre-check, feat(scheduler): agent-owned pre-check hook — let agents veto scheduled invocations deterministically #454) via hardened async subprocess (timeout-kill, output cap, JSON-parse + non-zero-exit guards); 404 when a hook is absent. The agent owns scope state + the re-export (Invariant security: implement safe tar extraction with symlink/hardlink validation #8); Trinity never runsexport_data.py.Changes
public/brain-orb/{index.html, orb.js, styles.css, orb-trinity.css, vendor/*}— CSP-clean; scope fetches routed through the brokered proxy w/ Bearer; scope panel un-hidden/agents/:name/brainroute (flagbeforeEnter);AgentBrainOrb.vueiframe host +postMessageJWT; Brain tab gated on flag +brain-orbcapabilityrouters/agent_brain_orb.py—data/scopes(read) +scope(owner mutation);brain_orb_availableflag (BRAIN_ORB_ENABLED, default OFF)agent_server/routers/brain_orb.py— dataFileResponse+ scope-hook runnerfeature-flows/brain-orb.md; requirements §46 (FR-1…6)Invariants: #5 agent-server mirror · #8 agent owns generation + scope state · #4 route order · #15 nesting. No DB change, no migration, no new secret, no MCP tool.
Test Plan
tests/unit/test_brain_orb.py— 24 passing: backend data/scopes/scope proxies (flag-off, read vs owner gate, success pass-through, 404/413/502/503/504 mapping) + agent-server data read + real-subprocess scope hooks (present/absent, stdin forwarding, timeout→504, invalid-JSON→502, non-zero-exit→502).vite buildclean; orb asset port verified in a browser against the real 5.6 MBdata.json(renders the graph; scope panel opens onS; voice/action panels hidden; only benign caught fetches).~/.trinity/brain-orb/hooks (manual; noted in the flow doc).Notes
abilityai/trinity-enterprisetracker but the code is core, so it lands here.Fixesdoes not auto-close across repos — closed at release.Fixes abilityai/trinity-enterprise#58
🤖 Generated with Claude Code