Skip to content

PulseCheck OS: Mission Control + /graph 3D viewers + autonomy/realtime#56

Open
PulseCheckAI wants to merge 22 commits into
outsourc-e:mainfrom
PulseCheckAI:feat/pulsecheck-mission-control-harmony
Open

PulseCheck OS: Mission Control + /graph 3D viewers + autonomy/realtime#56
PulseCheckAI wants to merge 22 commits into
outsourc-e:mainfrom
PulseCheckAI:feat/pulsecheck-mission-control-harmony

Conversation

@PulseCheckAI

Copy link
Copy Markdown

Summary

Brings the PulseCheck Mission Control design language and several platform capabilities to ClawSuite, plus a new 3D knowledge-graph surface. 212 files changed.

Highlights

  • Mission Control + Liquid Glass theme — full Mission Control screen, a Liquid Glass component kit, the canonical navy theme harmonized across 16/16 routes, and an SSR crash fix.
  • /graph — 3D knowledge & code graph viewers — a new /graph route embedding three Three.js viewers (Code-dependency graph, LightRAG Knowledge graph, and a "Temple" view) with PulseCheck brand styling (navy/cyan + pulse-gradient, Inter/Bricolage, liquid-glass control panel), 4-way label modes (All / Important / Hover / None), and a path-scoped CSP so the app stays strict 'self' while only /graphs/* get the viewer's CDN/font allowances.
  • Tier-1 autonomy loop — Agent Roster + System Integrations, counters persisted across restarts, stale-recovery sweep, retry-counter demotion, 405 on non-POST tick.
  • Supabase Realtime — live todos + agent_logs.
  • Gateway resilience — auto-heal shut-down singleton; recover via /api/gateway-restart.
  • Docs — v4.0.0 changelog draft; ln-614 fact-check remediation.

Commits (13)

  • d3c6b9d feat(theme): PulseCheck Mission Control harmony + SSR crash fix
  • 239e87d feat(mission-control): full Mission Control screen + Liquid Glass kit + navy theme
  • a50456e fix(gateway): auto-heal shut-down singleton; recover via /api/gateway-restart
  • 0ba1cfb feat(command-center): PulseOS branding, Tier 1 autonomy loop, Agent Roster + System Integrations
  • 0de115e fix(theme): card-bg consistency sweep — 16/16 routes on canonical navy
  • f0a7a0f fix(autonomy): stale-recovery sweep + retry-counter demotion
  • b2c0f1d fix(autonomy): persist counters across restarts + 405 on non-POST tick
  • 74710e2 chore(gitignore): ignore Playwright/audit screenshots + autonomy dev log
  • 90ec44b feat(dashboard): Supabase Realtime for live todos + agent_logs
  • ed89a80 docs: remediate ln-614 fact-check audit
  • 411555a docs(changelog): draft v4.0.0 release notes
  • 34dba09 feat: /graph route — embed Code/Knowledge/Temple 3D viewers in PulseOS
  • a5acca8 feat: scope /graphs CSP + Graph nav entry (completes /graph wiring)

Test plan

Verified locally:

  • pnpm typecheck — 0 errors
  • /graph loads; Code / Knowledge / Temple toggle switches viewers; label modes work
  • App routes serve strict CSP (frame-ancestors 'none', script-src 'self'); /graphs/* load Three.js + fonts under the scoped relaxed CSP

To verify by reviewers (prior-WIP areas on this branch):

  • Mission Control screen renders; navy theme consistent across routes
  • Autonomy-loop counters persist across a restart; non-POST tick returns 405
  • Supabase Realtime updates todos / agent_logs live
  • Gateway auto-heal recovers a shut-down singleton

Note: this is a large branch (212 files). The /graph viewer feature (34dba09, a5acca8) is independently verified; the remaining commits are accumulated Mission Control / autonomy / realtime work carried on the same branch.

Generated with claude-flow (https://github.com/ruvnet/claude-flow)

PulseCheckAI and others added 22 commits May 14, 2026 06:26
Theme:
- styles.css: swap accent palette to pulse-orange (#FF6B35 gradient through
  #E63946 red end), primary palette mapped to mc-bg-* surface tones
  (pure-black through near-white in .dark mode), Bricolage Grotesque + Inter
  + JetBrains Mono fonts. Add --pulse-{red,orange,amber,yellow} brand vars.
- Codebase-wide token swap (78 .tsx + 4 .ts/.css): neutral-* -> primary-*,
  orange-* -> accent-*. Semantic colors (red, emerald, yellow, blue, amber)
  untouched.

SSR hydration crash fix ("<AwaitInner> setState undefined"):
- package.json: pin @tanstack/react-router exact 1.166.7 (was ^1.166.7,
  resolved to 1.169.2 and shipped its own router-core, causing state-shape
  drift). Add npm overrides forcing single router-core@1.166.7 + store@0.9.1
  everywhere. Same exact-pinning for react-router-devtools, ssr-query,
  router-plugin.
- src/routes/__root.tsx: ssr: false root-wide (pure SPA mode while outsourc-e#53
  upstream hydration crash is unresolved). Add manual rehydrate calls
  for the 4 persist'd zustand stores in the post-mount useEffect.
- src/stores/{workspace,terminal-panel,task,mission}-store.ts: add
  skipHydration: true to persist config (prevents SSR localStorage access).
- src/routes/index.tsx: simplify to static redirect to /dashboard (was
  doing client-only conditional redirect that SSR couldn't match).

Gateway RPC drift (OpenClaw 2026.5.7):
- src/routes/api/gateway/nodes.ts: nodes.list -> node.list (renamed upstream).
- src/routes/api/session-status.ts: drop sessionKey param + non-existent
  session.status/sessions.status fallbacks; current gateway only accepts
  bare sessions.usage.

Dev ergonomics:
- src/routes/api/gateway/agents.ts: add CORS + dev-mode auth bypass on GET
  so the founder dashboard at file:///.../os/dashboard/index.html can fetch
  this endpoint as part of its ClawSuite-bridge feature.
- package.json scripts: vite dev --port 3010 --strictPort, drop the
  Windows-incompatible NODE_OPTIONS="..." prefix (cmd.exe parse error).
… + navy theme

Screen + components:
- New MissionControlScreen at /dashboard (replaces old gateway-backed DashboardScreen)
  with 4-KPI row, Action Required panel (stale >24h + off-track + recent failures),
  30-day activity tracker, top-agents/top-categories bar lists, recent activity feed.
  Reads live command_center.todos + command_center.agent_logs via Supabase REST.
  Health-first layout per dashboard-builder skill; thresholds drive deltaTone
  (success rate green ≥95%, amber 80-95%, red <80%).
- 9 vendored Mission Control components (src/components/mission-control/):
  KpiCard, SparkArea, StatusDot, BadgeDelta, Tracker, BarList, Callout,
  DonutGauge, PulseLogo.
- Deleted unused screens/dashboard/dashboard-screen.tsx (no longer imported).
- Widget shell pulse-gradient top bar (signature Mission Control bar).

Theme tokens (styles.css):
- RICH NAVY primary palette in .dark (oklch h=246, chroma 0.10-0.12) —
  unmistakably blue, premium against pulse-orange.
- Self-hosted variable fonts via @fontsource-variable: Bricolage Grotesque,
  Inter, JetBrains Mono. Family names match the bundled 'X Variable' files.
- PulseCheck AI Liquid Glass utility classes (glass-panel, glass-card,
  glass-tile, glass-pulse, glass-prominent, glass-bar) — backdrop-filter
  blur(20px) saturate(180%), color-mix primary, pulse-orange tinted borders,
  hover lift, prefers-reduced-motion honored.
- Decorative pulse-* utilities: pulse-text gradient, pulse-border-top,
  pulse-glow, pulse-status-dot, pulse-bar.
- Global Mission Control treatment (:where(h1-h4) -> Bricolage, focus rings
  pulse-orange, sidebar active gradient bar matching real .bg-accent-500/10
  + .text-accent-500 signature, code/kbd -> JetBrains Mono tabular-nums).
- Film grain overlay on body::before (subtle noise via SVG turbulence).

SMIL -> CSS conversions (DevTools performance advisory):
- icons/clawsuite.tsx cursor blink -> .oc-cursor-blink class
- orchestrator-avatar.tsx orchestrating rings x3 -> oa-opacity-pulse
- Dragon thinking smoke (cy + opacity SMIL) -> oa-smoke-rise translateY
- __root.tsx splash cursor SMIL -> inline <style> + .splashCur class
- Zero <animate> tags remain in src/.

Defensive hardening:
- vite.config.ts: clawsuite-hardening-headers plugin via configureServer —
  Permissions-Policy (denies unload, beforeunload, camera, mic, geo, etc.),
  Referrer-Policy strict-origin-when-cross-origin, X-Content-Type-Options
  nosniff, X-Frame-Options DENY, COOP same-origin-allow-popups.

Deps:
- Added sonner (toast library, common Tremor/shadcn-admin peer).
- Added @fontsource-variable/{bricolage-grotesque,inter,jetbrains-mono}.
…-restart

Root cause: once GatewayClient.shutdown() ran (HMR cleanup, SIGTERM during
SSR boundary, or manual restart), the module-scope singleton stayed in a
permanent "destroyed" state. Every subsequent gatewayRpc/onGatewayEvent
call routed to the corpse and threw "Gateway client is shut down" until a
hard dev-server restart.

Fix is two-layered:

1. getLiveClient() helper auto-replaces the destroyed singleton with a
   fresh instance on first use. The public facade (gatewayRpc,
   onGatewayEvent, gatewayConnectCheck) now routes through it, so every
   one of the 40 importing SSR routes transparently recovers — no manual
   click required.

2. /api/gateway-restart no longer calls the removed OpenClaw RPC
   gateway.restart. It now: (a) best-effort tries the upstream RPC for
   older OpenClaw, (b) ALWAYS runs the local gatewayReconnect() — which
   is the actual recovery path for the WS client. Returns 503 with an
   actionable hint when the gateway daemon itself is unreachable.

Verified live: POST /api/gateway-restart returns
{ok:true, reconnected:true, upstreamMessage:"unknown method: gateway.restart"}.

Drive-by: tighten CORS_HEADERS_GET type in agents.ts to
Record<string,string> so the conditional spread doesn't widen to a union
with undefined props.
…oster + System Integrations

Mission Control dashboard becomes a fully operational PulseOS Command Center.

Branding
- Rename 74 user-visible "ClawSuite" refs → "PulseOS" (sidebar wordmark with
  gradient OS, splash, page titles, onboarding, settings dialog, login,
  gateway-setup wizard, connection-error copy, format-session-name).
- "Mission Control" → "Command Center" everywhere user-visible.
- Real PulseCheck wave logo: ship docs/Logos/pulsecheck-logo-dark.{png,webp}
  as public/pulsecheck-wave.{png,webp}; pulse-logo component renders the
  source PNG cropped via object-fit:cover + object-position:left (1.4:1
  aspect captures the full wave, drops the wordmark whitespace).

Tier 1 autonomy loop (new)
- src/server/autonomy-loop.ts: polls command_center.todos, soft-locks via
  status=in_progress, maps category→agent (Marketing→jordan, Development→
  dev, Personal→alex, Work→main), dispatches via gatewayRpc('agent', {…,
  idempotencyKey: randomUUID()}), updates todo + writes agent_logs row.
  Opt-in self-start via PULSEOS_AUTONOMY_LOOP_ENABLED=true (default 60s).
  HMR-safe via globalThis interval guard.
- src/routes/api/autonomy-tick.ts (POST): manual + cron-driven trigger,
  auth + CSRF + rate-limited (30/min/IP).
- src/routes/api/autonomy-status.ts (GET): in-memory state for the panel.
- Mission Control "Autonomy Loop · Tier 1" panel: 4 stat tiles, last-tick
  row with picked title + agent + status, Tick now button.

Agent Roster + System Integrations panels
- Agent Roster (5 cards: Alex/Maya/Jordan/Dev/Sam) with per-agent glow
  color, role label, live last-task / today-count / model / last-active
  from command_center.agent_logs.
- New /api/system-integrations route probes filesystem + gateway for
  Codex CLI, Claude Dreams runner, Telegram channel, default-agent
  orchestrator; Mission Control renders the 4 honestly (no fabrication).
- Gateway Agents page no longer hardcodes Aurora-Main / Codex /
  Memory consolidator / Telegram in FALLBACK_AGENT_REGISTRY — only real
  registered agents render.

Theme harmonization (global)
- pulsecheck-navy as the default dark enterprise theme. One-shot localStorage
  migration in __root.tsx auto-upgrades ops-dark/premium-dark/sunset-brand
  to navy on first boot.
- Add Mission Control Navy to ThemeId union + DARK_THEMES + THEME_SET +
  THEMES registry + both Settings pickers.
- Global CSS rule under [data-theme='pulsecheck-navy']:
    body bg + --theme-bg = oklch(0.18 0.08 248)
    every text-primary-{500..950} → #ffffff
    every bg-white / bg-zinc / bg-gray / bg-slate / bg-neutral / bg-stone /
      bg-primary-{500..950} → var(--theme-bg) (excludes status bg-red/amber/
      yellow/emerald/green/accent so semantic colors keep their tint)
    every border-primary-{500..950} + border-white/zinc/gray/etc. →
      silver hairline rgba(170,178,195,0.4) (excludes status borders)
- Apple Liquid Glass treatment globally on every rounded-xl/2xl/3xl
  bordered surface that isn't a button/input/.glass-*/status-tinted:
    backdrop-filter: blur(20px) saturate(180%) + silver hairline +
    color-mix navy fill + depth shadow.
- Swap hardcoded dark hex bgs (#0d1117 / #0d0d0d / #0b0f1a / #232b3b /
  #fdfdf8) to var(--theme-bg) across agent-swarm, terminal, mobile-prompt,
  office-view.
- xterm terminal reads --theme-bg at instantiation.

Per-screen cleanup
- Sweep light-mode semantic palettes (border-emerald-200, bg-amber-100/70,
  text-red-700, etc.) → dark-mode tones (border-{c}-500/30, bg-{c}-500/10,
  text-{c}-400) across 30+ screen/component files.
- Outer padding normalized to px-6 md:px-8 + pt-6 md:pt-8 across every
  main screen.
- Debug Console: compact ActivityEventRow (single-line layout, ~28px from
  ~80px), gateway-unreachable amber callout with actionable remedy, raw
  JSON pre re-tinted to glass-translucent, standardized button heights
  (h-7 px-2 text-xs tabular-nums).
- Usage Analytics: StatCard adds tone prop (positive/negative/cost/volume/
  neutral) — Messages/Tool Calls/Avg Tokens/Sessions = blue, Cost = pulse-
  orange, Errors + Cache Hit Rate threshold-based green/red, Throughput
  green; values rendered in font-display.
- Activity tracker: unknown cells now silver-translucent (visible) instead
  of bg-primary-500/60 which the global crush was collapsing to body navy;
  monthly tracker bigger (14×42 cells), uppercase mono labels.
- Tasks/Skills/Files/Memory/Settings forms: add id/name/aria-label to every
  unnamed input/select/textarea/file (9 violations across 5 routes).

Fixes
- gateway-restart route uses local gatewayReconnect() (the upstream
  gateway.restart RPC was removed in OpenClaw 2026.5.7).
- Files page: replace Monaco (lazy-loaded from cdn.jsdelivr, blocked by
  CSP script-src 'self') with a styled textarea; reorders empty-state
  checks before the loadingFile check so a stale loading flag can't lock
  the pane.
- chat-sidebar gateway-status-dot uses brand pulse-orange when connected;
  buttonVariants.ghost dark-mode text fixed (was dark:text-primary-100
  which is dark navy in the inverted ramp — invisible on dark bg).
- Skills marketplace API: dedupe by slug (was returning each entry 10x)
  + CJK-ratio filter (<30% CJK chars in summary) so the English UI shows
  English-content skills only.

Hard-gate logging rule appended to all 5 agent-os/.md files so every agent
must write a row to command_center.agent_logs before final reply (powers
Agent Roster + Recent Activity + KPIs).

Skip vite.config.ts hardening-headers plugin (intentional — separate
concern; the headers ship as a meta-CSP via __root.tsx for now).
Final sweep against `[data-theme='pulsecheck-navy']` after the audit found 5
non-canonical surfaces. Every card-like element across all 16 sidebar routes
now resolves to one of:

- oklch(0.18 0.08 248)               — solid navy body / panel
- oklab(0.18 -0.03 -0.07 / 0.62)     — Liquid Glass translucent navy
- rgba(255, 255, 255, 0.04|0.06)     — subtle inner-row overlay (BarList
                                       tracks, Recent Activity rows, model
                                       badges with no semantic tint)
- bg-{red,amber,yellow,emerald,green,blue,violet,...}-500/10  — status /
                                       persona tint (kept; tint IS signal)

Fixed:
- mission-control/bar-list.tsx       — bar track from bg-primary-100 lift
                                       to rgba white 4%
- gateway/agent-avatar.tsx           — 10 AGENT_ACCENT_COLORS persona tokens
                                       from light pastels (bg-blue-100 +
                                       text-blue-600) to dark-mode tints
                                       (bg-blue-500/10 + text-blue-400);
                                       same for violet/rose/cyan/fuchsia/
                                       lime/sky and the accent + emerald +
                                       amber entries to match the pattern
- gateway/team-panel.tsx             — 9 MODEL_BADGE_COLOR entries swept;
                                       `auto` model now neutral white-6%
- gateway/config-wizards.tsx         — tier badge bg-green-100 / bg-blue-100
                                       → bg-emerald-500/10 / bg-blue-500/10
- mission-control screen             — Action Required + Recent Agent
                                       Activity row patterns from
                                       bg-primary-100 lift to white-4% with
                                       white-8% hover, matching BarList
Closes the two integrity gaps from the Tier 1 audit:

  HIGH — No stale-recovery: if dispatchToAgent hangs and the process
         dies, a row sticks at status='in_progress' forever.
  MEDIUM — No retry counter: a permanently-broken task gets re-picked
         every tick, logging endless failure rows.

Schema (command_center.todos, idempotent ALTER):
- attempts        integer NOT NULL DEFAULT 0
- last_attempt_at timestamptz

Module changes (src/server/autonomy-loop.ts):
- MAX_ATTEMPTS = 3 → after this many failures the task is demoted
  to track_status='Off Track' so listPendingTodos stops returning it.
- STALE_TIMEOUT_MS = 10 min → in_progress rows older than this get
  swept back to 'todo' with attempts++ at the top of every tick.
- DISPATCH_TIMEOUT_MS = 90s → Promise.race ceiling on gatewayRpc
  so the loop can't hang forever waiting on an unresponsive agent.
- rescueStaleInProgress() runs first; results counted as totalRescued.
- lockTodo writes last_attempt_at on every lock.
- completeTodo takes the current attempts count, bumps to attempts+1,
  and demotes to Off Track when newAttempts >= MAX_ATTEMPTS on failure.
- dispatchToAgent takes (taskId, attempt) and builds a deterministic
  idempotencyKey of shape `${taskId}:${attempt}:${rand8}` — same
  task+attempt yields a key the gateway can dedupe on protocol level.
- AutonomyTickResult.reason narrows to a literal union:
  'no-pending-tasks' | 'inflight-already' | 'lost-race-to-lock' —
  honest reporting on why a tick was a no-op (was conflated before).
- getAutonomyState() exposes totalRescued in the panel/curl probe.

Verified live with synthetic STALE probe: row inserted at status=
in_progress + attempts=1 + last_attempt_at=30min ago → tick rescued
it (attempts→2, status→todo, At Risk) → picked + dispatched + done
(attempts→3, On Track). Probes cleaned up after.

Two LOW gaps from the audit remain by design:
- counter persistence (totals reset on dev-server restart) — fine for
  dev; production would need a stats table
- /api/autonomy-tick GET returns SPA HTML instead of 405 — minor
Closes the two LOW gaps from the prior audit.

Counter persistence
- New table `command_center.autonomy_stats` (singleton row, id='singleton')
  with total_ticks / total_dispatched / total_failed / total_rescued /
  updated_at columns.
- `hydrateStatsFromSupabase()` runs at the top of every autonomyTick() —
  guarded by state.statsHydrated so it only fires once per module load.
  Seeds in-memory counters from the persisted row.
- `flushStatsToSupabase()` async-fires after each tick. Not awaited so
  it never blocks the dispatch happy path. Loss of one write is
  acceptable; the next tick re-flushes.
- Dev-server restarts no longer zero the dashboard's "Ticks / Dispatched
  / Failed" KPIs.

Method handling on /api/autonomy-tick
- POST behavior unchanged (auth + CSRF + rate-limit + dispatch).
- GET / PUT / PATCH / DELETE now return 405 with body
  `{ ok:false, error: "Method not allowed. Use POST." }` and an
  `Allow: POST` header. Ops debugging with `curl` no longer gets the
  SPA fallback HTML.

Verified live:
- `curl -i GET /api/autonomy-tick` → `HTTP/1.1 405` ✓
- `POST` after restart loads totals from the singleton row, increments
  to total_ticks=1, flushes updated_at=15:59:06 ✓
The Mission Control build process this branch landed (PulseOS branding,
Tier 1 autonomy, theme harmonization) leaned heavily on ad-hoc Playwright
probes that wrote screenshots to the repo root. Deleted the 146 stale
artifacts and added explicit patterns so `git status` stays readable for
the next person who walks into this repo.

Also ignores autonomy-dev.log (background dev-server stdout when started
with `npm run dev > autonomy-dev.log`) and the audit-shots/ + audit{N}/
output directories used by the bg-consistency / form-field / route-walk
audits.
Replaces the 30s REST poll on Mission Control with a postgres_changes
WebSocket subscription. The dashboard now updates the instant rows are
written to command_center.{todos, agent_logs} — by the autonomy loop,
by Claude on the side, by SQL from psql, by anything.

Backing infra (already migrated in this turn):
- ALTER PUBLICATION supabase_realtime ADD TABLE command_center.todos
- ALTER PUBLICATION supabase_realtime ADD TABLE command_center.agent_logs
- ALTER PUBLICATION supabase_realtime ADD TABLE command_center.autonomy_stats

Client:
- New lib/supabase-client.ts singleton (HMR-safe via globalThis cache)
  using @supabase/supabase-js ^2.105.4 (added to package.json).
- Project-scoped publishable key + persistSession:false (no auth flow
  needed; tables ship `FOR ALL USING (true)` RLS policies).
- eventsPerSecond:5 cap on the realtime params.

Mission Control screen:
- Keeps the existing supabaseGet REST fetch on mount for cold-start
  hydration of the last 200 todos + last 200 logs.
- Subscribes to channel `mission-control-live`:
    .on('postgres_changes', { schema:'command_center', table:'todos' })
    .on('postgres_changes', { schema:'command_center', table:'agent_logs' })
- Each payload: INSERT → prepend (clipped to 200), UPDATE → replace by id,
  DELETE → filter by id. Local state stays in sync with DB.
- Cleans up the channel via supabase.removeChannel() on unmount.
- The 60s polling effect is gone; the only timer left is the 30s `now`
  re-render (for relative-time labels) and the 10s autonomy-status poll
  (which reads in-process state, not Supabase, so realtime doesn't help).

Verified: WS opens to wss://…/realtime/v1/websocket on dashboard mount;
TS compiles clean; no autonomy-loop interactions changed.
Run the ln-614-docs-fact-checker against os/dashboard-clawsuite/**/*.md and
fix every finding except the editorial one. Audit report + remediation log
captured at docs/audits/ln-614--global.md.

HIGH-1 resolved -- dev-server port mismatch (docs said 3000, vite binds 3010)
  README.md (4 lines), SETUP.md (5 lines, incl. scripts table + .env mistake
  row), CONTRIBUTING.md (1 line), docs/remote-access.md (Tailscale target /
  LAN example / WSL portproxy + Docker callout now annotated), docs/mobile-
  setup.md (3 lines, incl. tailscale-ip examples).
  docker-compose 3000:3000 prod mapping intentionally preserved -- prod build
  defaults to PORT=3000 and is now explicitly distinguished from dev.

HIGH-2 resolved -- docs/workspace-ux-architecture.md (2026-03-10 spec, 7 of 11
  "implemented" file paths missing in this repo) now leads with an ARCHIVED
  banner pointing at the current src/screens/mission-control/ layout.

HIGH-3 partial -- README badge 3.0.0 -> 4.0.0 (matches package.json). CHANGELOG
  v4.0.0 entry still missing -- left for editorial decision.

HIGH-4 resolved -- docs/CLAWSUITE-ARCHITECTURE.md skills count 3,000+ -> 2,000+
  to align with README.

HIGH-5 resolved -- added "typecheck": "tsc --noEmit" to package.json scripts
  (was documented in CONTRIBUTING.md but not implemented).

HIGH-6 resolved -- docs/CLAWSUITE-ARCHITECTURE.md "Existing (already
  integrated)" endpoint block replaced phantom POST /api/chat and
  GET /api/sessions/:id/history with the real chat-and-session routes that
  actually live in src/routes/api/ (sessions.ts, sessions/$sessionKey.status,
  sessions/send, chat-events, chat-abort).

MEDIUM-1 withdrawn -- /api/mission-history at AGENT-HUB-AUDIT.md:145 is
  future-tense ("Consider..."), not a false claim. Should never have been
  flagged.

MEDIUM-2 rolled into HIGH-2 (foreign repo path called out in archive banner).

MEDIUM-3 resolved -- README "66+ components" softened to "across the entire
  app" (number was unverifiable against any concrete asset set).

Prettier's PostToolUse hook normalized markdown-table padding across the
edited docs -- semantically identical to the pre-edit content.
Closes the last remaining HIGH-3 audit gap (CHANGELOG had no v4 entry
despite package.json being at 4.0.0). Marked as Draft, 2026-05-14 since
the tag has not been cut yet.

782 commits since v3.0.0 grouped into 9 sections:
  - Mission Control + Tier 1 Autonomy (new)
  - Conductor (renamed from Agent Hub)
  - PulseOS Branding + Apple iOS 26 Liquid Glass theme
  - Operations + Mobile
  - Skills / Cron / Files
  - Electron Desktop App
  - Remote & Mobile Access
  - Bug Fixes
  - Security
  - Tooling & Infrastructure
  - Docs
  - Architecture

Header notes that PulseCheck-fork-specific items (autonomy loop,
Realtime, PulseOS branding) are mixed with generic ClawSuite features
(Conductor, Electron, perf, a11y), since this CHANGELOG lives in the
fork at pulsecheck-ai/os/dashboard-clawsuite/.

Editorial — review and adjust before tagging v4.0.0.
Add a /graph surface that iframes the generated viewers from public/graphs/ with a Code/Knowledge/Temple toggle, plus a command-palette entry. CSP scoping (vite.config) and the mobile overflow-nav item are left in the working tree — those files carry unrelated uncommitted WIP.

Co-Authored-By: claude-flow <ruv@ruv.net>
Move CSP into a path-scoped Vite plugin: strict 'self' for the app, relaxed (jsdelivr + Google Fonts + frame-ancestors 'self') only for /graphs/* viewers. Add the Graph item to the mobile overflow nav.

Note: both files also carried pre-existing uncommitted working-tree changes that came along — WS proxy auth gating in vite.config.ts, and ask-brain/operations nav fixes in the overflow panel.

Co-Authored-By: claude-flow <ruv@ruv.net>
…cial stack

Checkpoint of all uncommitted work on this feature branch. This spans MULTIPLE
prior sessions (intel cockpit, LinkedIn / Postiz / RSS / Media / Gmail / HubSpot
modules, mission-control, gateway) PLUS this session's additions below — committed
together as a safety checkpoint, not all authored in one session.

This session's additions:
- GraphQL gateway (Yoga + Pothos) at /api/graphql:
  - Intel read surface: intelSources, intelItems (+ DataLoader source),
    askIntel (LightRAG RAG, capped at 30s), knowledgeHealthy
  - Margin reads (tenant-scoped): marginLeaksTop, marginOpsSummary
- Integration Hub module: /integrations screen (KPIs + connection & catalog
  cards), /api/integrations (+/catalog) over the real integrations.* schema
  (504-row catalog, tenant_connections, source_systems), sidebar entry
- Knowledge Graph sidebar module wired to /graph (LightRAG / code / temple)
- Pollinations free image-per-post toggle in the RSS autopost pipeline
- Audit fix-pass: getHubStats double-fetch, catalog error state, NaN guards,
  search-filter sanitizer whitelist, documentationUrl scheme guard,
  a11y (focus rings, text/pill contrast, aria-pressed, heading levels)
- pnpm: packageManager pin (pnpm@11.1.2) + engines node 22.x; removed stray
  package-lock.json (pnpm authoritative)
- .gitignore: exclude env backups / scratch / build cache
- docs: graphql-gateway-architecture, pulseos-unification-program

Secrets + scratch excluded via .gitignore (.env.bak*, env-verify.txt,
_tc_rss.txt, *.tsbuildinfo, .claude-flow/). Verified no secret/credential
files staged.

Co-Authored-By: claude-flow <ruv@ruv.net>
Realistic animated aurora background (Nano Banana 2 / gemini-3-pro-image), full-bleed with ken-burns pan+zoom and a subtle aurora shimmer; glass auth card; 'Pulse' white + 'OS' orange->gold gradient matching the dashboard. Boot splash recolored to the warm brand palette to match. All decorative motion gated behind prefers-reduced-motion; auth contract unchanged.
The TanStack Start (Vite-native) build emits a Web fetch handler that does not listen, serve static client assets, or proxy the gateway. serve.mjs wraps it into a self-listening Node server: static dist/client, SSR/API/server-fns via the fetch handler, the /ws-gateway (auth-gated through /api/auth-check) + /api/gateway-proxy + /gateway-ui + /workspace-api proxies, and the dev server's hardening headers + path-scoped CSP. package.json start + ecosystem.config.cjs pulseos now run it (NODE_ENV=production, node --env-file-if-exists=.env). Verified live: os.pulsecheckai.app serves the prod build behind the password gate; pm2 online restarts=0.
…ty audit)

P0: serve.mjs now auth-gates the same-origin gateway/workspace proxies (/api/gateway-proxy, /gateway-ui, /workspace-api, /ws-gateway). They previously bypassed the CLAWSUITE_PASSWORD perimeter and exposed the OpenClaw gateway unauthenticated on the public origin (verified: tunnel /api/gateway-proxy/ went 200 -> 401).

S1: extracted CSP + hardening headers + proxy topology to one shared module (src/server/security-headers.mjs + .d.mts) imported by both vite.config.ts (dev) and serve.mjs (prod). No more dev/prod drift.

S2: scripts/dashboard-smoke.mjs is now a prod-aware smoke test + P0 regression guard (asserts proxies return 401 unauthenticated).

D1: opt-in durable sessions in auth-middleware.ts (CLAWSUITE_PERSIST_SESSIONS=1, default off, graceful fallback) so 30-day sessions can survive a restart.

Deferred: D2 (remove CSP 'unsafe-inline') needs an app-wide nonce rollout. Verified: tsc 0 errors, vite build OK, smoke 8/0 against os.pulsecheckai.app.
…OT, unit tests

Two review agents (security + typescript) re-audited the prior fix and found a real P1 plus cleanups; all addressed and verified live.

P1: isAuthed() now fails CLOSED — it denied-by-default on the SSR /api/auth-check 4s timeout/parse-error before (the timeout returned authRequired:false, which opened the proxy gate under server slowness). auth-check.ts timeout fallback also now returns authRequired:true.

P1-hardening: /api/gateway/agents GET gate is tied to isPasswordProtectionEnabled() instead of an IS_DEV/NODE_ENV module constant that could bake wrong at build time.

S-fix-1: serve.mjs routing is now driven by PROXY_ROUTES (no more fake/unused SSOT); matchProxyRoute + safeStaticPath extracted to the shared module and unit-tested (20 vitest cases incl. path-traversal + encoded/absolute invariants).

S-fix-2: serve.mjs runs a boot-time perimeter self-check that logs whether the gateway proxy is gated, every start. P2: strip any upstream clawsuite-auth Set-Cookie; auth-middleware consolidates vite-detection and warns at runtime about the 0600/NTFS caveat when CLAWSUITE_PERSIST_SESSIONS is on.

Verified: tsc 0 errors, vitest 20/20, vite build OK, tunnel /api/gateway-proxy + /api/gateway/agents return 401 unauthenticated, boot self-check OK.
…quest CSP nonce

serve.mjs generates a per-request nonce, injects it as the x-csp-nonce request header, and emits script-src 'self' 'nonce-<n>' (no more 'unsafe-inline' for scripts in prod). style-src 'unsafe-inline' is retained on purpose — React's inline style={{}} props are style attributes that nonces don't cover.

getRouter() reads the nonce via createIsomorphicFn (src/csp-nonce.ts) so the server-only getRequestHeader import is stripped from the client bundle (satisfies TanStack's import-protection plugin) -> router.options.ssr.nonce -> TanStack stamps nonce= on its SSR-injected scripts. __root.tsx stamps the same nonce on the 4 inline IIFEs (theme, themeColor, splash, readiness).

dev keeps 'unsafe-inline' (vite injects no nonce header) so HMR is unaffected; /graphs/* static viewers keep 'unsafe-inline' (their inline scripts aren't nonced).

Verified: tsc 0, vite build OK; on :3011 AND the live tunnel the CSP nonce == the script nonce (1 nonce across 22 scripts) and the full authenticated dashboard hydrates with ZERO CSP violations; proxies still 401.
…der, twilio/users/security/workspace routes, auth-users, i18n (96 new + 32 modified, secret-scanned clean)
… (voice/twilio/supabase/linkedin/oauth) as placeholders, zero secret values
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant