2009 Commits

Author SHA1 Message Date
Aurora release bot
8cd3c54594 chore(dashboard): demote operator_tip to default-hidden (storage v4)
Eric's call after living with iter 013 for a few minutes: removed
the Operator Tip and the dashboard reads cleaner without it.
Sessions Intelligence's `flex-1` stretch already absorbs the
bottom-of-column space, so an extra card was redundant.

- DEFAULT_HIDDEN now includes `operator_tip`.
- STORAGE_VERSION 3 \u2192 4 so the existing migration path applies the
  new default to returning users while preserving any explicit hides
  they had.
- Tip card is still registered in the catalog and reachable from the
  edit-mode picker for users who want a contextual nudge.

Tests/build:
- 12/12 aggregator tests
- pnpm build clean
2026-05-03 14:55:56 -04:00
Aurora release bot
a33ee133a7 fix(dashboard): tip above sessions, sessions stretch to fill column
Iter 013 per Eric:
> put tip above session intelligence and make that longer to fill
> the gap at bottom

- Reordered main column: OperatorTip (compact) \u2192 Sessions Intelligence
  (the bottom anchor that grows to fill).
- SessionsIntelligenceCard root is now `h-full flex-1` and its row
  list is `flex-1 overflow-hidden` so the card consumes the
  remaining vertical space in the column.
- Bumped row cap from 8 \u2192 14. The card now has the room.
- Main column wrapper is `min-h-full flex flex-col` and the sessions
  WidgetShell is wrapped in a `min-h-0 flex-1 flex flex-col` so the
  flex-1 actually expands rather than collapsing to content.
- WidgetShell edit-mode wrapper uses `h-full` so widgets that opted
  into flex-1/h-full still expand correctly when in edit mode.

Tests/build:
- 12/12 aggregator tests
- pnpm build clean
- tsc clean for dashboard files
2026-05-03 14:55:41 -04:00
Aurora release bot
661e63bcb4 feat(dashboard): contextual Operator Tip card fills bottom-left gap
Iter 012 per Eric's iter-011 ask:
> small gap at the bottom by sessions intelligence \u00b7 what should we
> put there standard? tip of the day? or something?

New OperatorTipCard component: a smart 'tip of the moment' card that
sits below Sessions Intelligence in the main column.

Why contextual rather than static:
- A static rotating tip would feel like marketing filler.
- A scoring function per tip lets the dashboard surface the *most
  relevant* tip given current state (low cache hit rate, stale
  cron, config drift, restart pending, recent achievement, sudden
  drop in sessions, top-model concentration risk, etc.).
- 11 tips total; context-specific ones score 40-80, evergreens
  3-5 so they only surface when nothing better is relevant.
- Refresh icon cycles to the next-best tip; persists last-shown
  index in localStorage so refreshes don't always snap to the top.
- Optional CTA per tip routes to the most relevant page (jobs /
  settings / analytics / skills / etc).

Visual: matches the rail card chrome (rounded-xl, gradient bg,
top accent strip, soft glow), with a 36x36 lightbulb chip on the
left and a tip body + tone-colored 'Tip · X/N' eyebrow on the
right. Tone color tracks the relevance category (warn / info /
positive).

Catalog: registered as 'operator_tip', column 'main', visible by
default. Lives at the bottom of the left column under Sessions
Intelligence so it visually balances the side rail extending past.

Tests/build:
- 12/12 aggregator tests
- pnpm build clean
- tsc clean for dashboard files
2026-05-03 14:55:41 -04:00
Aurora release bot
7255b1d33b fix(dashboard): drop redundant 'Operator console v0.12.0' eyebrow
Iter 011 polish per Eric:
> remove operator console v.12 \u00b7 lower workspace a bit cause the
> gateway version is under

Gateway version was already shipping in the OpsStrip ('\u2666 GATEWAY
V0.12.0'). The header eyebrow was duplicating it within ~30px on the
same screen, which read as visual clutter.

- Removed the 'Operator console \u00b7 v\u003cversion\u003e' subtitle entirely.
- Title row is now a single bold 'Hermes Workspace' lockup with
  vertical-centered alignment so it sits visually centered against
  the action cluster on the right (instead of biased to the top of
  the row from the dropped subtitle).
- Logo + glow ring stay; nothing else moves.

Tests/build:
- 12/12 aggregator tests
- pnpm build clean
2026-05-03 14:55:41 -04:00
Aurora release bot
c3be84d407 feat(dashboard): rebrand header, add Velocity + Cost Ledger to menu
Iteration 010 per Eric's iteration-009 ask:
> kept cache its clean fits fine \u00b7 should we remove dashboard text and
> just make hermes workspace larger? there's missing space in middle
> \u00b7 maybe center hermes workspace? \u00b7 add any additional widgets in
> the menu

== Header rebrand ==

- Dropped the redundant 'Dashboard' eyebrow (the page IS the dashboard).
- Promoted 'Hermes Workspace' to the primary heading at text-2xl,
  bold, tight-tracked. Now commands the left.
- Eyebrow becomes 'Operator console \u00b7 v\u003cgateway-version\u003e' wired
  off `overview.status.version` so the build/version tag is live.
- Logo: bumped 36px \u2192 44px, wrapped in an accent-tinted square with
  a soft outer glow ring. Reads as a real product mark, not a tiny
  avatar.
- Kept anchored left rather than centering. Ops dashboards (Linear,
  Vercel, Datadog) all anchor brand left + actions right because
  that's the spatial hierarchy operators expect; centering wastes
  the most premium real estate. Put the explanation in the section
  comment so future changes know why.

== New menu-only widgets ==

VelocityCard (`velocity`):
- Big number: sessions/day average over the analytics window.
- Delta vs the prior half of the window with tone scaling
  (green for positive, warning for >25% drop).
- Sub-stat: API calls/day.
- Tiny daily sparkline (bars).

CostLedgerCard (`cost_ledger`):
- Per-model cost table that splits paid providers from
  subscription/included rows so the dollar figure is honest.
- Paid rows sorted by descending cost, included rows by descending
  tokens.
- Header chip shows total billed across paid rows only.
- Subscription pattern set covers codex / anthropic-oauth / minimax /
  ollama / lmstudio / pc1-* / pc2-* / gemma / llama / qwen.

Both default-hidden so the stock dashboard stays clean; they live
in the edit-mode menu. Fed off existing analytics payload so no
backend changes required.

== Storage migration v2 \u2192 v3 ==

DEFAULT_HIDDEN expanded to: logs_tail, provider_mix, velocity,
cost_ledger.

`readLayout` now does a real schema migration: when a stored
layout's version field is older than STORAGE_VERSION, union the
user's explicit hides with the new defaults so existing installs
don't suddenly sprout widgets they never asked for. Provider Mix
stays hidden post-upgrade, matching Eric's call to keep just Cache.

Tests/build:
- pnpm exec vitest run src/server/dashboard-aggregator.test.ts (12/12)
- pnpm build (passes)
- tsc clean for dashboard files
- v2 \u2192 v3 migration verified: existing `{hidden:['logs_tail']}`
  becomes `{hidden:['logs_tail','provider_mix','velocity','cost_ledger']}`
2026-05-03 14:55:41 -04:00
Aurora release bot
a7db9b9f02 feat(dashboard): provider mix donut + cache efficiency tile
Iteration 009 per Eric's iteration-008 ask:
> theres just a space from top models to achievements maybe we can
> put another widget there or something? can we add any more graphs
> or charts on the dashboard?

== New widgets, both wired into the right-side stack of the top
analytics row ==

Provider Mix card (`ProviderMixCard`):
- Collapses `analytics.topModels[]` by provider family with a heuristic
  (claude-* \u2192 anthropic, gpt-/o1/codex \u2192 openai, gemma/llama/qwen \u2192
  local, gemini \u2192 google, grok \u2192 xai, minimax, etc.).
- Renders a CSS conic-gradient donut with the dominant family % +
  label inside the hole, plus an inline legend table for the top 4
  families and a '+N more' affordance when there are more.
- Family colors cycle through accent / accent-secondary / success /
  warning / danger / purple / cyan / yellow so siblings are visually
  distinct without us shipping a real palette.

Cache Efficiency card (`CacheEfficiencyCard`):
- Big % stat: cache_read / (cache_read + input).
- Sub-stat: total cache tokens / total input tokens, plus the ratio
  multiplier (cache_read / input).
- Daily hit-rate sparkline (bars, not line) so a zero day pops.
- Pure derive from the existing analytics payload.

== Layout ==

The analytics chart row used to be: [chart 8 cols] [Top Models 4 cols].
The right side hosted a single short card next to a tall chart, which
left the dead vertical Eric flagged.

Now the right column is a flex stack of:
  1. Top Models
  2. Provider Mix
  3. Cache Efficiency
This fills the chart-height, gives operators 3x the data density at
the top of the dashboard, and stops the gap between the top row and
the rail below it.

All three cards register in `useDashboardLayout` so they're toggleable
via edit mode just like everything else.

Tests/build:
- pnpm exec vitest run src/server/dashboard-aggregator.test.ts (12/12)
- pnpm build (passes)
- tsc clean for dashboard files
2026-05-03 14:55:41 -04:00
Aurora release bot
123f22b6a1 feat(dashboard): premium icons, rail stretch, attention as own row
Iteration 008 per Eric's iteration-007 feedback.

== Premium header icons ==

Replaced emoji glyphs in the action row with Hugeicons stroke
icons + better button chrome:

- New Chat: BubbleChatAddIcon on a real accent gradient button
  (was translucent tinted background) with inset highlight, soft
  drop shadow, and an animated overlay sheen on hover.
- Terminal: ConsoleIcon
- Skills: PuzzleIcon
- Edit: Edit02Icon \u2192 CheckmarkCircle02Icon when active
- Settings: Settings02Icon

SecondaryAction component now takes a Hugeicons icon (not an emoji
string), uses a subtle card gradient background, accent-colored
icon on hover, and matching uppercase tracking for visual unity
with the primary New Chat button.

Edit + Settings icon-only buttons get the same treatment so the
whole right-hand cluster reads as one premium control group.

== Side rail height/balance ==

- Achievements: query bumped to `achievements=5` (was 3) and the
  card renders every unlock the aggregator returns. Switched from
  compact \u2192 full-detail rows so the card has body. Together this
  fills the gap between Top Models and the rest of the rail.
- Side rail container: `min-h-full` so the column stretches to
  Sessions Intelligence height instead of collapsing to content.
- Mix & rhythm card: `flex-1 h-full` + `justify-between` so it
  consumes the remaining vertical space at the bottom of the rail
  and aligns flush with Sessions Intelligence's lower edge.

== Attention bar separated ==

Eric: 'is it cluttered or should be higher or lower / separate?'

- Lifted the AttentionMarquee out of OpsStrip into its own
  dedicated row above the gateway strip. Now a self-contained
  warning-tinted chamber with its own border + gradient. Clearly
  reads as 'things to look at' separate from 'gateway is up'.
- OpsStrip reverted to its single-row layout (no more nested
  vertical stack).
- When there are no incidents, the row simply doesn't render \u2014
  no empty frame.

Build/tests:
- pnpm exec vitest run src/server/dashboard-aggregator.test.ts (12/12)
- pnpm build (passes)
- tsc clean for src/screens/dashboard/* and src/server/dashboard*
2026-05-03 14:55:41 -04:00
Aurora release bot
fa55ff0f78 feat(dashboard): attention marquee in ops strip, achievements up, logs off
Iteration 007 per Eric's iteration-006 feedback:

== Attention marquee ==

- New `AttentionMarquee` component renders the existing
  `incidents[]` payload as a right-to-left ticker. Lives inside
  `OpsStrip` as a dedicated full-width row that only appears when
  there is something to surface.
- Animates via CSS keyframes (`@keyframes oc-attention-marquee`,
  32s linear infinite). Pauses on hover so the operator can read a
  long item. Respects `prefers-reduced-motion` and disables the
  animation in that case.
- Track is duplicated once for the seamless wrap-around. Soft fade
  mask on the right edge for the 'ticker continues' affordance.
- Each item routes to the most context-appropriate page (cron \u2192
  /jobs, config \u2192 /settings, log/gateway \u2192 /jobs as fallback) or
  to the incident's own `href` when set.
- Glyph + severity color picked from existing `source` and
  `severity` enums on `DashboardIncident` so the marquee stays
  fully data-driven.

== Side rail rebalance ==

- AttentionCard removed from the side rail (its data is now in the
  marquee). Import dropped.
- AchievementsCard moved to the *top* of the side rail. The right-of-
  chart visual position effectively places it 'under Top Models'
  which is what Eric asked for.
- New rail order:
  1. Achievements
  2. Skills usage
  3. Mix & rhythm
- Mix & rhythm stays \u2014 it's the only chart left in this column and
  the unique non-deep-route insight (token shape + 24h heatmap).

== Layout v2 ==

- WidgetId catalog drops `attention` (not a widget anymore).
- New `logs_tail` default state is HIDDEN. Eric's read: it's a
  triage tool, not a dashboard staple. Power users can re-add via
  the edit-mode panel.
- `useDashboardLayout` now writes a `version: 2` field to its
  storage payload and seeds first-load state from `DEFAULT_HIDDEN`
  when no localStorage entry exists, so the new defaults apply
  cleanly to fresh installs without nuking returning users' explicit
  hides.
- Reset button now returns to the iteration defaults rather than
  show-everything, so first-time Reset doesn't surprise users with
  Logs they never wanted.

Tests/build:
- pnpm exec vitest run src/server/dashboard-aggregator.test.ts (12/12)
- pnpm build (passes)
- All edit-mode toggling round-trips cleanly with the v2 schema.
2026-05-03 14:55:41 -04:00
Aurora release bot
32ddf28802 feat(dashboard): edit mode + side rail visual cohesion
Iteration 006. Two product asks from Eric's iteration-005 feedback.

== Edit mode ==

New `useDashboardLayout` hook owns:
- which widgets are hidden (persisted to localStorage key
  `dashboard.layout.v1`)
- whether the dashboard is in edit mode (session state)

Catalog of 8 toggleable widgets with column metadata (main vs rail):
analytics_chart, top_models, sessions_intelligence, logs_tail,
attention, skills_usage, achievements, mix_rhythm.

UI:
- New `WidgetShell` wrapper: zero-overhead passthrough when edit mode
  is off; in edit mode adds a dashed accent outline + an X close
  button in the top-right corner of the widget. Click X to hide.
- New `EditModePanel` banner: only renders when edit mode is active.
  Shows widget toggle pills grouped by column (Main / Side rail) so
  the operator can re-add hidden widgets. Includes Reset (show all)
  and Done buttons. Visible-count chip shows '5 of 8 widgets shown'.
- New header pencil icon (\u270f\ufe0f) toggles edit mode; flips to checkmark
  (\u2713) when active, with accent border to make state obvious.

Layout adaptiveness:
- Top row: Analytics chart and Top Models share a 12-col grid. If
  one is hidden, the other expands to fill (col-span-12) so we don't
  end up with a half-empty row.
- All sections check `layout.isVisible(id)` before rendering so
  hidden widgets cost zero DOM nodes.

== Side rail visual cohesion ==

Image review of iteration-005 flagged the rail cards as having
ragged heights and inconsistent action-link styling. Fixes:

- AchievementsCard: replaced the legacy `rounded-md bg-card/40`
  chrome with the same `rounded-xl border` + top gradient accent +
  diagonal card gradient that AttentionCard / TokenMixHourCard /
  SkillsUsageCard already use. The 'View all \u2192' button no longer
  sits as a separate full-width row; merged into the title-row
  microcopy ('48 unlocked \u00b7 view all \u2192') so the rail breathes.
- SkillsUsageCard: matches the same diagonal gradient background
  (was flat var(--theme-card)). Title color promoted from muted to
  text. 'manage \u2192' microcopy gets the accent color on hover so all
  rail action affordances behave the same.

Tests/build:
- pnpm exec vitest run src/server/dashboard-aggregator.test.ts (12/12)
- pnpm build (passes)
- localStorage key namespaced `dashboard.layout.v1` so future schema
  changes can bump cleanly.
2026-05-03 14:55:41 -04:00
Aurora release bot
d86ee7d364 feat(dashboard): drop Cost tile, hoist Active Model to hero, fuse mix/rhythm
Iteration 005 per Eric's feedback after the iteration-004 screenshots.

Hero KPI row:
- Drop the Cost tile entirely. Even with the 'partial / included'
  trust label it kept reading as misleading on a workload that's
  almost entirely Codex/OAuth.
- New ActiveModelKpi tile in the 4th slot. Shows active model name +
  Online/Offline pulse + share-of-calls chip + provider/sessions
  microcopy + ctx length. Same gradient form factor as the other
  three tiles so the row stays balanced.
- HeroMetrics now takes an optional extraTile slot so the dashboard
  can compose the row without coupling the metrics widget to model
  data.

Side rail (final order, top to bottom):
  1. Attention
  2. Skills usage
  3. Achievements
  4. Mix & rhythm  (new)
- Drop the rail-side Active Model card (its data is now in the hero).
- Move Skills + Achievements up so the rail leads with the cards Eric
  finds most useful.
- New TokenMixHourCard fuses the previously separate Token Mix and
  Hour of Day cards into a single 'rhythm' card sharing chrome,
  header, and typography rules. Halves still independent so a fresh
  install with no analytics or sessions still hides cleanly.

Hero KPI row CSS bumped to grid-cols-1 sm:2 lg:4 so the new tile
stacks rather than overflowing on narrow viewports.

Tests/build:
- pnpm exec vitest run src/server/dashboard-aggregator.test.ts (12/12)
- pnpm build (passes)
- /api/dashboard/overview shape unchanged \u2014 this is UI-only.
2026-05-03 14:55:41 -04:00
Aurora release bot
aaf482152e feat(dashboard): cost trust labels, skills bar chart, token mix, hour-of-day
Iteration 004. UI-only polish + new charts based on the Hermes Agent
data audit. No new backend endpoints required.

Aggregator:
- Cost honesty: new `analytics.costLabel` with values `precise` /
  `partial` / `included` / `unknown`. Computed from per-model
  session counts split between priced providers and known
  subscription-included ones (codex / anthropic-oauth / minimax /
  ollama / lmstudio / pc1-* / pc2-*). Stops the dashboard from
  showing '$0.052 for 247M tokens' as if that were precise.
- Insights tightened:
  - Capped at 3 (was 4).
  - Drops 'no active runs' line when activity peaked today (was
    contradicting the peak callout in the same card).
  - Skill names in callouts now strip the `namespace:` prefix.
  - Model ids in callouts use the trailing slash segment instead of
    raw provider/model strings.
- New presentational helpers `shortSkillName` / `shortModelName`
  inside the aggregator so insight text is render-ready.

UI:
- HeroMetrics cost tile: reads `costLabel` and renders 'Included' /
  'partial \u00b7 some included' / dollar figure / em-dash accordingly.
  Hides delta + sparkline for non-precise variants.
- New `SkillsUsageCard`: replaces the lonely '60' tile. Top-5 used
  skills as a horizontal bar chart with name (last segment), use
  count, and percentage. Fades to '\u003cN\u003e installed' fallback when
  no usage in the window.
- New `TokenMixCard`: stacked horizontal bar of input / output /
  cache / reasoning split with hover tooltips and an out/in ratio
  chip.
- New `HourOfDayCard`: 24-bucket activity strip computed
  client-side from session `startedAt`. Highlights the peak hour
  and labels the timeline at 12a / 6a / 12p / 6p.
- OpsStrip: appended `pulse Xm ago` microcopy from the new
  `status.lastHeartbeatAt` field.
- SessionsIntelligenceCard:
  - Better kind icon resolution. New `sessionGlyph` helper checks
    `kind`, `source`, and the `cron_<jobId>_*` key heuristic so
    cron sessions actually show a clock instead of a chat bubble.
  - Adds api_server / signal / imessage / matrix / local mappings.
- New `formatSkillName` helper in lib/formatters.ts (strips colon
  and slash namespaces).

Tests/build:
- pnpm exec vitest run src/server/dashboard-aggregator.test.ts (12/12)
- pnpm build (passes)
- Live: costLabel='partial' for our usage (codex + opus mix), insights
  count = 3 (was 4), gpt-5.4 in callouts instead of openai/gpt-5.4.
2026-05-03 14:55:41 -04:00
Aurora release bot
5504f93b23 feat(dashboard): sessions intelligence + canonical active_agents + server-side insights
Iteration 003. Drop the legacy 14d Activity chart; reclaim the space
with a sessions intelligence card. Adopts every Hermes Agent answer
on the source-of-truth fields.

Aggregator:
- /api/dashboard/overview now also probes /health/detailed via the new
  gatewayFetch helper. status.activeAgents reads canonical
  active_agents from the gateway runtime; status.activeSessions
  preserves the /api/status heuristic for separate display.
- status.lastHeartbeatAt added (alias of gateway_updated_at).
- cron now exposes failed count + recentFailures[] (id, name,
  last_error, last_run_at).
- skillsUsage section parses analytics 'skills' object: totalLoads,
  totalEdits, totalActions, distinctSkills, topSkills[] with
  percentage. (Hermes Agent confirmed schema.)
- New top-level insights[] computed server-side: peak day driver,
  cache delta vs prior period, ops pulse (failed/stale crons +
  no-active-runs + restart-pending), top-skill heat. UI no longer
  recomputes.
- New top-level incidents[] aggregates cron failures + stale cron +
  paused cron + platform errors + config drift + restart-pending +
  log-tail errors into one triage list with hrefs.

UI:
- Drop ActivityChart and the legacy Recent Sessions list.
- New SessionsIntelligenceCard:
  - Real human title from derivedTitle (falls back to short slug).
  - Kind/source icon (chat / cron / cli / telegram / discord / api).
  - Badges: hot (active <5m), tool-heavy (>=20 tools), high-token
    (>=50k), error, stale (>7d).
  - Hot row gets accent border so the operator sees what's running.
  - Hierarchy: model chip · msgs · tools · tokens · recency.
  - Click row -> /chat/<sessionKey>.
- AttentionCard now consumes overview.incidents directly. Source icon
  per item. Click items navigate to /jobs / /settings.
- Skills tile: real 'top: <skill>' from skillsUsage.topSkills[0]; row
  reads '<enabled> enabled · <distinct> used · top: <skill>'.
- AnalyticsChartCard switches to overview.insights so the UI is dumb.

Tests/build:
- pnpm exec vitest run src/server/dashboard-aggregator.test.ts (12/12,
  +3 new: failed-cron incidents, /health/detailed active_agents,
  skills usage parsing)
- pnpm build (passes)
- Live: 31 distinct skills used in 30d, top
  'autonomous-ai-agents:hermes-agent' (12 uses), 3 incidents surfaced
  (stale cron, paused cron, 6 config diffs).
2026-05-03 14:55:41 -04:00
Aurora release bot
6deb16bc10 feat(dashboard): attention card, period switch, split analytics, action hierarchy
Iteration 002 - addresses the Hermes Agent product review.

New widgets:
- AttentionCard: prioritized 'what to look at right now' list. Pulls
  stale cron, log errors/warns, config drift, restart-pending, and
  platform error states from the existing overview payload. Shows an
  explicit 'all clear' state when nothing needs eyes. Replaces the
  scattered warning chips and gives the operator a single command line.
- AnalyticsChartCard: daily trend chart + 2-3 client-side insight
  callouts ('Usage peaked Apr 17, driven by GPT-5.4', 'Cache reads up
  X% vs prior period', 'no active runs · restart pending'). Period
  switch (7d / 14d / 30d) at top-right; selection persists to
  localStorage and feeds the same window into Hero KPIs and the rest of
  the overview.
- TopModelsCard: standalone right-rail card so the model breakdown is
  no longer cramped inside the analytics hero. Shows tokens bar plus
  '% of calls' (proxy for routing share) and sessions per model.
- ModelInfoCard now adds an operational microcopy line
  ('66% of calls · 113 sessions · 30d') and a click-through 'Inventory'
  modal that lists every model from /api/models grouped by provider,
  with active-model highlight and live filter.

UX/microcopy fixes:
- OpsStrip: '0 ACTIVE' -> '0 active runs', 'CONFIG +6' -> '6 config
  diffs', stale-cron pill now visually warns (warning border + tinted
  background) instead of muted text only.
- Action row: New Chat is now a primary gradient button, Terminal +
  Skills are secondary monochrome buttons, Settings is icon-only. Top
  visual weight cut ~30%.
- SkillsWidget: replaced the 6-row mini list with a summary tile
  ('42 installed · 39 enabled · top: Airtable'). Click-through opens
  /skills.
- Analytics card period-aware loading state: shows ' · refreshing…'
  microcopy in the header while the period switch is in flight.

Plumbing:
- /api/dashboard/overview now respects ?days=N from the client; query
  key includes period so React Query refetches when the operator
  switches windows.
- Period default 30d preserved; valid options 7 / 14 / 30 only.

Tests/build:
- pnpm exec vitest run src/server/dashboard-aggregator.test.ts (9/9)
- pnpm build (passes)
- Live: /api/dashboard/overview?days=7 returns 7-day window with
  56.5M tokens, top models gpt-5.4 + claude-opus-4-7.
2026-05-03 14:55:41 -04:00
Aurora release bot
b5671a2115 feat(dashboard): hero metrics, analytics+logs widgets, native shape parsing
Phase 2 iteration 001. The Workspace dashboard was already aggregating
`/api/analytics/usage` but parsing it for the wrong shape (legacy
`top_models`/`total_tokens`), so the analytics card stayed hidden
even though the gateway returned 247M tokens / 788 sessions.

Aggregator changes:
- normalizeAnalytics now reads native Hermes `{ totals, by_model, daily,
  period_days }` and falls back to the legacy shape when present. New
  fields exposed: inputTokens, outputTokens, cacheReadTokens,
  reasoningTokens, totalSessions, totalApiCalls, daily[], plus a
  `source` discriminator (`analytics` | `unavailable`).
- New logs section: pulls `/api/logs?lines=N`, classifies error/warn
  counts, returns last N lines for tail UIs.
- Default analytics window bumped 7d → 30d to match native dashboard.

UI changes:
- HeroMetrics: 4 big tiles (Sessions, Tokens, API Calls, Cost) with
  inline SVG sparklines + period-over-period delta chips. Replaces the
  legacy 4 small MetricTile row.
- AnalyticsHeroCard: large daily area chart + top-5 models breakdown +
  totals line. Click 'Expand' opens a modal with a stacked
  input/output/reasoning bar chart and full per-model breakdown
  (sessions / calls / cost).
- LogsTailCard: rolling tail with error/warn pulse and Expand modal.
  Modal auto-refreshes every 3s and supports all/errors/warns filter.

Tests/build:
- pnpm exec vitest run src/server/dashboard-aggregator.test.ts (9/9)
- pnpm build (passes)
- Live smoke: /api/dashboard/overview now returns analytics.source=
  analytics with 247M totalTokens, 29 daily entries, 5 top models, and
  log tail with 24 lines.
2026-05-03 14:55:41 -04:00
Aurora release bot
14f1c057ff fix(chat): tighten composer and agent view polish 2026-05-03 14:50:14 -04:00
Aurora release bot
5ad773753a fix(chat): restore clean layout with docked agent view 2026-05-03 14:27:44 -04:00
Eric
a4d83886a0 docs(readme): comprehensive cleanup after recent shipping wave (#274)
The README's feature list and roadmap were stale: missing the new MCP
page (#231), Operations dashboard, Agent View panel, Conductor screen,
and capability-gate work landed in the past 24-48h. Roadmap still
listed shipped features as 'In Development'.

Changes:
- Replace short 'Features' bullet list with a comprehensive 'What's
  inside' that mentions every major surface, including the explicit
  Conductor caveat with a link to #262.
- Roadmap restructured into Shipped / In progress / Coming sections.
  Conductor gets its own 'in progress' row with the upstream-plugin
  caveat. Native Desktop App moved to 'in progress' per spec status.
  Multi-provider support called out explicitly so users know what
  works on day one.
- Drop the in-house 'Agent W Managed Companion' subsection from public
  README \u2014 it's internal-team-only deployment notes that don't apply
  to anyone running upstream.
- Collapse the duplicate '## Features' section near the bottom into the
  consolidated security section. Avoids saying the same thing twice.
- Tighten security headings: now '## Security & deployment env vars'
  with two clean subsections: 'Built-in safeguards' and 'Env vars for
  remote / Docker deployments'.

Build clean. No code changes.

Co-authored-by: Aurora release bot <release@outsourc-e.com>
2026-05-03 13:36:08 -04:00
Eric
af50de716e chore: rename leftover CLAUDE_* env vars to HERMES_* (back-compat preserved) (#273)
The 2026-05-01 codename rename swept most of the repo from 'Claude' to
'Hermes' but left a few env vars and README copy that still referenced
the old name. Users on fresh installs were configuring HERMES_PASSWORD
based on the docs but the auth middleware only read CLAUDE_PASSWORD,
silently bypassing the guard.

Changes:
- src/server/auth-middleware.ts: read HERMES_PASSWORD first, fall back
  to CLAUDE_PASSWORD for back-compat with pre-rename setups.
- server-entry.js: same fallback for HERMES_PASSWORD +
  HERMES_ALLOW_INSECURE_REMOTE. Error messages updated to point at the
  new names.
- README.md:
  - Drop the misleading 'v2 zero-fork = full feature parity' framing;
    call out that Conductor specifically requires an upstream dashboard
    plugin not yet shipped (per #262), with a link.
  - Theme list updated to current names (Hermes / Nous / Bronze /
    Slate / Mono \u2014 the v2.1 rename) instead of the old 'Official /
    Classic' labels.
  - Replace CLAUDE_PASSWORD / CLAUDE_ALLOW_INSECURE_REMOTE references
    with HERMES_* primary names + back-compat note.
  - Add HERMES_API_TOKEN to the security env-var list (previously
    undocumented despite being honored by the gateway-capabilities probe).
  - Inline note on the avatar PNG asset name being retained for cache
    stability \u2014 the 'claude-avatar' filename is intentional.
- .env.example: HERMES_PASSWORD + HERMES_ALLOW_INSECURE_REMOTE primary
  names with legacy notes.
- docker-compose.yml: HERMES_PASSWORD: ${HERMES_PASSWORD:-${CLAUDE_PASSWORD:-}}
  so existing compose files that set CLAUDE_PASSWORD keep working while
  new files use HERMES_PASSWORD.

Build + auth-middleware tests pass.

No public-facing breaking change: every previous CLAUDE_* env var still
resolves correctly through the back-compat fallbacks.

Co-authored-by: Aurora release bot <release@outsourc-e.com>
2026-05-03 13:15:37 -04:00
Eric
0c28accf69 fix(operations): show 'Needs setup' state when agent has no model configured (#270) (#272)
Agents seeded by seedAgentPresets() (Sage, Trader, Builder, Scribe, Ops)
get system prompts on first load, but the profile dir's config.yaml has
no model configured by default. Dispatching into one hangs because
hermes-agent has nothing to call.

Add OperationsAgent.needsSetup boolean (true when agent.model is empty)
and surface it on OperationsAgentCard:
- Amber 'Needs setup — click to configure' button below the description
- Play button turns amber and routes to onOpenSettings instead of running
- Tooltip explains why on hover

Click either path \u2014 needs-setup banner OR amber play button \u2014 to open
the settings modal where the user can pick a model. Once a model is set,
needsSetup flips to false and the agent dispatches normally.

Refs #270.

Co-authored-by: Aurora release bot <release@outsourc-e.com>
2026-05-03 13:06:20 -04:00
Eric
b5fa6ef8b4 feat: capability gates + clearer swarm tmux errors (#271)
Conductor (#262):
- Add 'conductor' capability to gateway probe (probeConductor probes
  /api/conductor/missions on the dashboard).
- Surface caps.conductor in /api/connection-status response.
- New <FeatureNotReady> component for graceful 'upstream not ready'
  placeholders when a feature requires endpoints the agent doesn't have.
- New useFeatureCapability(key) hook polling /api/connection-status.
- /conductor route now wraps Conductor in a capability check: shows the
  placeholder when the dashboard /api/conductor/missions endpoint isn't
  there, instead of letting the UI 500 mid-action.

Swarm (#244):
- Toast error when /api/swarm-tmux-start returns 'tmux not installed'
  with install instructions instead of silent console.error.
- Upgraded the in-screen 'tmux not installed' banner from grey muted text
  to amber alert with the brew/apt install commands inline. Explains that
  workers can't dispatch tasks without tmux ('can't find pane: swarm-X'
  errors come from this missing dependency).

Build clean. Refs #244, #262.

Co-authored-by: Aurora release bot <release@outsourc-e.com>
2026-05-03 13:02:51 -04:00
Interstellar-code
c021ef5fcc feat(mcp): replace /settings/mcp with full-featured /mcp page (catalog + marketplace + sources) (#231)
* feat(mcp): MCP server management page (Phase 1)

Implements the MCP management plan (.omc/plans/mcp-management.md) Phase 1
end-to-end on a single feature branch (PR1+PR2+PR3+PR4 collapsed):

- New `/mcp` route with capability gate + BackendUnavailableState fallback.
- New `/api/mcp` (GET list, POST create), `/api/mcp/test` (POST connection
  probe), `/api/mcp/discover` (POST tool discovery for a draft config),
  `/api/mcp/configure` (PUT enable/toolMode/include/exclude), and
  `/api/mcp/$name` (DELETE).
- Strict `mcp` capability probe in gateway-capabilities: hits `GET /api/mcp`
  directly and validates the body parses through `normalizeMcpList` —
  dashboard-up-but-route-missing returns false (resolves Open Question #4).
- Type split: read shapes in `src/types/mcp.ts` (client+server), write
  shapes in `src/types/mcp-input.ts` (server-only; secrets contained here).
- Runtime normalization layer `src/server/mcp-normalize.ts` mirrors the
  Skills `asRecord`/`readString`/`normalizeSkill` defense — strips
  unknown fields, coerces enums, masks secrets via `MASK_SENTINEL`,
  re-applies via `maskSecretsInPlace` before every `json(...)`.
- All write endpoints CSRF-checked via `requireJsonContentType`.
- Capability-off responses use `createCapabilityUnavailablePayload('mcp')`
  with `{ servers: [], total: 0, categories }` for GET (200) and 503 for
  writes — feature gates fall open without throwing.
- Static preset catalog (`src/screens/mcp/presets.ts`) with GitHub,
  Filesystem, Postgres, Slack, Linear; Catalog tab installs prefilled
  drafts through the same dialog flow.
- Screens: `McpScreen` (Installed/Catalog/All tabs + search + category
  filter), `McpServerCard` (status badge + Test/Edit/Delete + enable
  toggle), `McpServerDialog` (HTTP/stdio + auth + Discover + Save with
  bearer-token clear-on-submit).
- TanStack Query hooks (`useMcpServers`, `useTestMcpServer`,
  `useDiscoverMcpTools`, `useUpsertMcpServer`, `useConfigureMcpServer`,
  `useDeleteMcpServer`).

Tests (vitest):
- `src/server/mcp-normalize.test.ts` — 13 tests covering enum coercion,
  list-shape variants, malformed-entry drop, presence flags without
  echo, env/header masking by key hint, idempotency, test-result
  normalization, payload-string scanner.
- `src/routes/api/-mcp.test.ts` — 8 tests covering input validation,
  capability fall-open shape, CSRF gate (415 on non-JSON POST, pass on
  JSON, pass on GET), and the **secret echo guard**: a worst-case agent
  that echoes a submitted bearer token in body/env/headers must never
  surface the original string in the workspace response.

Build, lint, and the new test files are clean. Pre-existing unrelated
test failures on `local` (router-route-resolution, context-usage,
markdown math, slash-command-menu, chat-message-list, gateway-capabilities
env-source) are unchanged by this PR.

Worked with Interstellar Code

* fix(mcp): strip secret fields from client-safe McpClientInput

Architect review flagged that `McpClientInput` in `src/types/mcp.ts` (the
file explicitly designated for client+server read shapes with no secrets)
contained `bearerToken` and `oauth.clientSecret`, allowing the browser
bundle to import a secret-bearing type via the dialog component.

Resolves the type-split violation:
- `src/types/mcp.ts`: drop `bearerToken` and `oauth` from `McpClientInput`.
  Now strictly the browser-safe form payload, no secret fields.
- `src/screens/mcp/components/mcp-server-dialog.tsx`: hold `bearerToken`
  in ephemeral component-local `useState<string>` typed inline. Cleared
  on submit and on dialog open. No exported type carries the field.
- `src/screens/mcp/hooks/use-mcp-mutations.ts`: `useUpsertMcpServer`
  accepts `McpClientInput & { bearerToken?: string }` inline at the
  call-site, again with no exported secret-bearing type. Server route
  `parseMcpServerInput` re-validates and forwards to the agent.

The full server-side write shape (`McpServerInput` with secrets) remains
in `src/types/mcp-input.ts`, server-only.

Worked with Interstellar Code

* fix(mcp): block client imports of server-only mcp-input types

Add no-restricted-imports rule scoped to src/screens/** and
src/components/** that blocks importing @/types/mcp-input. That
file may carry unmasked secrets and is server-only — clients should
import McpClientInput from @/types/mcp instead.

Worked with Interstellar Code

* feat(mcp): wire /mcp into all sidebar/nav surfaces

Mirror the existing /skills registration across every nav and
command surface so the MCP screen is reachable from the dashboard
overflow grid, command palette, mobile hamburger drawer, mobile tab
bar, slash menu, search modal quick actions, and workspace shell
(active-tab tracking + mobile page title). Inspector panel gets a
parallel MCP tab that lists configured servers via /api/mcp.

Worked with Interstellar Code

* feat(mcp): catalog tab search, category badges, and nav coverage tests

Catalog tab now reuses the screen's search state to filter presets
by name/description, surfaces an empty-state when no presets match,
and renders each preset as a card with an Official Presets category
badge styled to match the skills-screen design vocabulary.

Tests:
- src/components/-mcp-nav.test.tsx: each modified nav file references
  the /mcp route (or registers an mcp tab id for inspector-panel)
- src/screens/mcp/-presets.test.ts: filtering MCP_PRESETS by query
  narrows results by name and description, returns full catalog for
  empty queries, and returns nothing for unknown queries

Worked with Interstellar Code

* feat(mcp): add MCP entry to chat-sidebar Knowledge group

The primary visible left rail (`chat-sidebar.tsx`) was missed by the
prior nav-coverage commit. Slot MCP between Skills and Profiles in
`knowledgeItems`, mirroring the McpServerIcon used elsewhere.

Worked with Interstellar Code

* feat(mcp): Phase 3 — live tool refresh, OAuth reauth, per-server SSE logs

- `useMcpServers`: enable refetchOnWindowFocus for live state.
- `McpServerCard`: per-card Refresh button (re-runs Test, updates
  discoveredToolsCount), Reauth button when authType === 'oauth' (uses
  new useMcpOAuth hook), Logs button (opens McpLogsDrawer).
- `use-mcp-oauth.ts`: opens auth URL in new tab, polls /api/mcp/test
  every 2s until status === 'connected' or 60s timeout. Returns
  mutation-style { start, isPending, isError, error, data }.
- `mcp-logs-drawer.tsx`: fixed-right slide-in drawer subscribing via
  EventSource to /api/mcp/<name>/logs. Newest-first, max 500 lines,
  auto-scroll, tear down on close (no zombie EventSource).
- `routes/api/mcp/$name.logs.ts`: SSE proxy with auth + capability
  gates. Capability-off → 503. Pattern follows chat-events.ts.
- Tests: 3 new for logs route (input validation, capability-off,
  auth gate); smoke test for useMcpOAuth shape.

Total tests: 24 passing (13 normalize + 8 mcp + 3 logs). Build clean.

Worked with Interstellar Code

* feat(mcp): localhost-only config-fallback transport (Phase 1.5)

Adds an `mcpFallback` capability that lets the workspace perform CRUD on
`config.mcp_servers` via the existing dashboard `/api/config` route when
the agent does not yet expose the new `/api/mcp*` runtime endpoints.

Gated to loopback-only deployments by `isLocalhostDeployment()` (both
URLs loopback AND HOST unset/loopback). Test/Discover/Logs return a
structured "not yet available" payload in fallback mode; the MCP screen
renders an amber banner so the limitation is visible.

Worked with Interstellar Code

* feat(mcp): full catalog + marketplace + sources manager (Phase 2-3.2)

Workspace-only end-to-end MCP catalog + marketplace replacing the static
presets.ts and the upstream /settings/mcp surfaces.

Phase 2 — File-backed catalog:
- assets/mcp-presets.seed.json + ~/.hermes/mcp-presets.json (atomic
  bootstrap via tmp+linkSync, mtime+ino+ctime+size cache, malformed-file
  preservation, schema validation: id regex, transport-specific fields,
  env key regex, https URLs, category allowlist, duplicate-id rejection,
  unknown-field warnings)
- src/server/mcp-input-validate.ts: shared parseMcpServerInput returning
  per-field {path, message} errors; promoted from inline definition
- src/routes/api/mcp/presets.ts GET handler

Phase 3.0 — Federated marketplace:
- src/server/mcp-hub/{cache,trust,index,types}.ts + sources/{mcp-get,
  local-file}.ts: Smithery registry adapter (replaces speculative
  registry.mcp.run NXDOMAIN), ETag/If-Modified-Since with 304 reuse,
  rate-limit handling, parallel Promise.allSettled across sources with
  8s per-source timeout, dedupe by source+id+name, fallback to
  local-file when remote degraded
- Trust hardening: shell metachar reject, transport allowlist, env-key
  regex, control-char + absolute-path attack defenses, inline-exec
  flag detection (-c, -lc, -e for sh/bash/python/node/perl/ruby)
- src/screens/mcp/components/install-confirmation-dialog.tsx: 2-click
  commit with full template preview (command/args/env masked) and
  AbortController on dismiss
- Disk persistence for tool-discovery cache (mcp-tools-cache.ts) +
  hermes-mcp CLI bridge (mcp-cli-bridge.ts) for live test/tool
  enumeration in fallback mode

Phase 3.2 — User-configurable sources:
- ~/.hermes/mcp-hub-sources.json schema (built-ins always present,
  protected from mutation; user can add HTTPS-only generic-json
  sources with trust+format)
- src/routes/api/mcp/hub-sources{,.$id}.ts CRUD with per-process mutex
  (read-modify-write race protection)
- generic-json adapter: SSRF guard (private/loopback/link-local/IPv6
  ULA all rejected after DNS resolution, redirects disabled), 5MB
  response-size cap (streaming read), trust hard-cap at 'community'
  for user-source entries, source field 'user:<id>' for dedupe
- src/screens/mcp/components/sources-manager-dialog.tsx UI

Polish:
- Placeholder detection at install confirmation (inline fill form
  blocks commit until /path/to/, <your-...>, empty *_TOKEN/_KEY/etc
  resolved)
- Test result UX hints when stdio Connection closed + placeholder args
  or http fetch failed + placeholder url
- Env-ref preserved in normalize (${VAR_NAME} no longer masked) +
  Edit dialog diagnostic

UI: Skills-pattern parity for /mcp screen (Tabs + Marketplace tab,
Switch primitive, Button primitives, DialogRoot/Content, primary-*
Tailwind classes matching skills-screen.tsx). Single-row toolbar
(tabs + search + filter). Removed All + Catalog tabs, kept Installed +
Marketplace.

Backend:
- gateway-capabilities probeMcp uses authenticated dashboardFetch
  (Codex MAJOR fix); probeMcpConfigKey + isLocalhostDeployment for
  mcpFallback capability
- routes/mcp.tsx route gate accepts mcp || mcpFallback
- mcp-normalize.ts headers.Authorization + env *_TOKEN/_KEY/_SECRET
  /_AUTH/_APIKEY auth detection upgrades authType to 'bearer'

Removed (replaced by /mcp):
- src/screens/settings/mcp-settings-screen.tsx (759 LOC)
- src/routes/settings/mcp.tsx
- src/routes/api/mcp/{servers,reload}.ts (orphaned endpoints; reload
  posted to gateway 404s)
- src/screens/mcp/presets.ts (static array, replaced by file-backed)
- settings-sidebar MCP nav entries (replaced by main /mcp route)

Tests: 263+ passing across 19+ MCP suites — input-validate, presets-
store, hub-cache/trust/unified-search, sources/{mcp-get,local-file,
generic-json}, hub-sources-store, mcp-tools-cache, ssrf-guard,
marketplace-install-confirmation, marketplace-placeholder-detection,
hub-search/-presets/-hub-sources route tests. Pre-existing 2
gateway-capabilities env-resolution failures unrelated.

Reviewers: Codex critic 4 passes (Phase 2 REJECTED → 8 fixes applied,
Phase 3.0 APPROVED-WITH-CHANGES → 4 fixes, Phase 3.2 REJECTED → 6
fixes including SSRF guard + response-size cap + concurrent-CRUD
mutex + trust cap). Architect approved final pass.

Worked with Interstellar Code
2026-05-03 12:49:28 -04:00
Interstellar-code
b72b47544d fix(composer): prevent ~/.hermes from masquerading as workspace context (#243)
The composer workspace selector and file browser now resolve through one
profile-local workspace catalog, so ~/.hermes remains runtime state instead of
becoming the project root.

Changes:
- Rework /api/workspace into a profile-local workspace catalog
- Prevent ~/.hermes, Hermes profile dirs, and system roots from being
  selected as workspaces
- Make /api/files use the same workspace catalog as the composer
- Update the composer workspace selector to list/switch backend workspaces
- Invalidate workspace state after profile switches
- Change the workspace menu action to open the in-place files sidebar
  instead of navigating to /files
- Remove the "Add path manually..." button (used window.prompt; not suitable
  for remote workspaces)
- Composer bottom-row layout: profile, workspace, reasoning, model, context
  ring near send button; mic/attachment ordering aligned
- Add focused regression coverage for workspace resolution and composer
  control wiring

Constraint: Hermes Web UI treats workspaces/spaces separately from
profile state.
Rejected: Keep localStorage saved workspace paths | stale saved paths could
keep showing ~/.hermes.
Rejected: Keep /api/files fallback to HERMES_HOME | files sidebar would still
browse runtime state.
Confidence: high
Scope-risk: moderate
Directive: Do not reintroduce HERMES_HOME/CLAUDE_HOME as project workspace
fallbacks; use /api/workspace catalog instead.
Tested: targeted vitest for workspace/files/composer controls; targeted
eslint; LSP diagnostics on key changed files; pnpm build green.
Not-tested: dedicated Spaces manager UI remains follow-up.

Worked with Interstellar Code
2026-05-03 12:49:25 -04:00
henry li
acaa4e5081 feat(i18n): add zh-TW Traditional Chinese locale with Taiwan terminology (#248)
- Add zh-TW locale with Taiwan-specific terms (檔案, 終端機, 記憶體, 市集, 設定, 儲存, 搜尋, 載入中 etc.)
- Update getLocale() to match full navigator.language before splitting, ensuring zh-TW users are not routed to Simplified Chinese
- Rename zh label to 中文(简体)for clarity

Co-authored-by: 你的名字 <你的電子信箱>
2026-05-03 09:56:54 -04:00
Interstellar-code
0d4b39a860 feat(skills): origin badges, filter, source path, toolbar redesign (#245)
* feat(skills): origin detection, source path, and category alias map in API

- Cross-reference .bundled_manifest + SKILL.md frontmatter to classify
  each installed skill as built-in, agent-created, or marketplace
- Populate sourcePath with resolved local path (~/.hermes/skills/<cat>/<id>)
  for installed skills (was previously empty)
- Add category alias map: gateway returns lowercase dir names (research,
  productivity); map these to display labels for filter compatibility

Worked with Interstellar Code

* feat(skills): origin badges, filter, card layout, and security popup fix

- Add origin badge on each skill card (Built-in / Agent-created / Marketplace)
  alongside existing Installed badge
- Add origin badge in skill detail dialog beside Source path field
- Add origin filter dropdown in Installed tab to filter by origin
- Fix card layout: skill icon now inline with name, author line hidden when empty
- Fix security popup transparency: stacking-context conflict with neighboring
  cards resolved via inline background var + z-index promotion on card hover

Worked with Interstellar Code

* feat(mcp): align toolbar tab layout with Skills; frosted-glass tooltip

- MCP page toolbar: move Installed/Marketplace tabs to right side,
  matching the Skills page toolbar layout for consistency
- Tooltip: translucent themed background with backdrop-blur for
  frosted-glass appearance (affects chat sidebar icons, security
  popups, and all tooltip usages globally)

Worked with Interstellar Code

* feat(chat): widen default content column to 1125px (25% wider)

Bumps `--chat-content-max-width` from 900px to 1125px so the
default ('comfortable') chat column is roomier without forcing
users to switch to the 'wide' or 'full' settings.

Worked with Interstellar Code
2026-05-03 09:56:28 -04:00
Interstellar-code
18a68d04be fix: recovery buffer for vanishing last message after refresh (#241)
- Persist final assistant message to sessionStorage on 'done' event
- Merge recovery message into history if backend hasn't caught up yet
- Clear buffer once server history confirms the message
- Refactor: replace (msg as any) casts with type-safe bracket access
- Refactor: extractMsgId() helper in use-chat-history.ts
- Refactor: getMessageTimestamp() uses Array<unknown> + bracket access
2026-05-03 09:56:14 -04:00
Eric
da12206145 fix(probe): tighten enhancedChat capability detection (#261) (#269)
The previous probe used a generic GET to /api/sessions/__probe__/chat/stream
and treated any non-404/403 status as 'available'. Vanilla hermes-agent
serves a router-level handler at sessions paths but doesn't actually
expose the streaming POST endpoint there, so it was returning 405 (or
similar) on the GET, which the probe interpreted as 'enhanced chat is
available'. Workspace then routed sends through streamChat() which
posts to /api/sessions/{id}/chat/stream, which 404s on vanilla agent,
and the bundle's error mapper surfaced it as a generic 'Authentication
error' to the user.

Replace the generic probe with probeEnhancedChatStream() that:
- POSTs (the real method) instead of GET
- Treats 404 (path missing) and 405 (path mismatch) as not-available
- Treats any other status as available (4xx structured errors / 401
  auth gates / streaming start all imply the path is registered)

Result: getChatMode() correctly returns 'portable' on vanilla agent,
send-stream.ts takes the openaiChat path, chat works without patches.

Refs #261.

Co-authored-by: Aurora release bot <release@outsourc-e.com>
2026-05-03 09:55:48 -04:00
Eric
e500143c1f feat: Agent View phase 1 + theme polish (Nous/Hermes/Bronze rename) (#266)
* feat(agent-view): port AgentViewPanel from controlsuite into Workspace chat

- Copy 9 agent-view components + use-agent-spawn hook
- Copy use-agent-view zustand store + use-orchestrator-state hook
- Copy orchestrator-avatar (1490 LOC, 9 SVG avatars + animations)
- Copy agent-personas lib (Roger/Sally/Bill/Ada/Max/Luna/Kai/Nova)
- Rename storage keys controlsuite-* -> hermes-workspace-*
- Mount <AgentViewPanel /> in chat-screen.tsx after main, before terminal
- Demo data wired via createDemoActiveAgents in use-agent-view

Iteration 001 of goal 2026-05-02-agent-view-port-from-controlsuite.
All deps already present in Workspace. Greek persona swap + Hermes avatar art = next iter.

* feat(agent-view): demo fallback when live gateway returns zero agents

Hermes Workspace session status vocabulary (idle/active/etc) doesn't yet
map onto ControlSuite's running/queued/completed brackets, so live mode
was returning empty arrays and hiding the panel content.

Phase 1: fall through to demo data when classifier produces 0 agents,
so we can see the full UI visually before Phase 2 status mapping work.

* feat(agent-view): default avatar owl, drop lobster as first-run default

Per Eric: ditch lobster, default to owl. Lobster stays available in picker,
just no longer the first-run choice. Cleared user must clear localStorage
key 'hermes-workspace-orchestrator-avatar' to see new default if they had
old default cached.

* feat(agent-view): add Hermes PNG avatar as default

- Add 'hermes' to AvatarStyle union with PNG renderer (HermesPNG)
- HermesPNG: <img src='/avatars/hermes.png'> with state animations
  (breathing scale loop on idle/responding, dotted ring on thinking)
- Avatar renders circular via objectFit cover + border-radius 50%
- Default first-run avatar = hermes (was owl, was lobster originally)
- New PNG asset at public/avatars/hermes.png (1.6MB anime portrait)
- Picker modal now shows Hermes as first tile

Phase 1.5 of agent-view port. 8 more Greek god PNGs to land same way.

* feat(agent-view): bump main agent avatar 52 -> 88 for more presence

Per Eric: avatar felt small relative to panel width and whitespace.
Now occupies ~30% panel width which gives the character actual presence
in the top-of-rail slot.

* feat(agent-view): two-tier avatar picker with 9 Greek god PNGs

- Add 8 Greek god PNG avatars: athena, apollo, artemis, iris, nike, eros, pan, chronos
- Refactor PNG renderer into makeGreekPNG factory (one fn per god)
- AvatarPicker now has two tiers:
  * Standard tier: 9 emoji-styled SVG avatars (smaller tiles, default view)
  * Greek tier: 9 anime PNG portraits (premium, opens via 'More ->' button)
- Picker remembers tier based on currently selected avatar
- Greek tile shows actual PNG thumb at 56x56, emoji tile shows 2xl emoji
- 'More ->' / '<- Standard' toggle button switches tiers in-place
- Default avatar remains 'hermes' (god of messengers, fits the brand)

* fix(agent-view): wire chat-activity-store so avatar reflects real state

Previously chat-screen.tsx had a no-op stub for setLocalActivity, so
the OrchestratorAvatar in the agent view never actually changed state
during user sends, thinking, tool calls, or responses.

Hook the actual zustand store so:
- send -> 'reading'
- waiting for first token -> 'thinking'
- streaming -> 'responding'
- tool call running -> 'tool-use'
- done -> 'idle'

Now the avatar's animation states (breathing, dotted ring, bob, glow)
react to live chat activity instead of staying frozen on idle.

* fix(agent-view): cleaner enterprise chrome, drop noisy stats line

Per Eric: panel had visible outline border + chatty '1 active · 0 queued · $0.000' line that felt noisy and unenterprise.

- Drop the left border on the outer aside, switch bg to --theme-sidebar
  for cohesion with the rest of the dark surface
- Drop the bottom border on the header row
- Drop the inline stats line entirely (info available per-card)
- Soften the Agents section bg from /35 to /15, drop its border ring

Result: panel reads as a continuous dark surface flush with the chrome,
not a bordered floating widget.

* feat(agent-view): smooth slide+fade in/out for desktop panel

Was: instant render with no entrance animation (initial={false})
Now: panel slides in from right edge with opacity fade, custom cubic
bezier eases (0.32, 0.72, 0.24, 1) for a natural deceleration that
feels native and enterprise rather than CSS-default.

Open: 320ms slide, 220ms opacity fade-in
Close: same easing, panel slides off-canvas right

* feat(agent-view): port usage meter — real provider quota tracking

Port from controlsuite:
- src/server/provider-usage.ts (1026 LOC) — Claude OAuth, Codex JWT
  decode, OpenAI billing, OpenRouter, Anthropic API key probes
- src/routes/api/provider-usage.ts — JSON endpoint
- src/components/usage-meter/* (5 files) — full meter, compact meter,
  details modal, context alert, index re-exports

AgentViewPanel.OrchestratorCard already had the poller wired to
/api/provider-usage from the prior agent-view port; route was just
missing. Now live:
- Codex: Plus plan, session/weekly windows, JWT-decoded plan tier
- Claude: OAuth credentials read from ~/.claude or keychain, refresh
  on demand, session+weekly+sonnet bars
- OpenAI: billing endpoint via API key
- OpenRouter, Anthropic API key paths included

Verified at /api/provider-usage returns real percentages.

* fix(agent-view): unify enterprise chrome — drop borders on inner sections, queue, history, agent cards

Per Eric: bottom subagent sections still had the old border-primary-300/70
outline ring + heavier bg/35. Top header was already cleaned but the rest
of the panel didn't match.

- Drop border on Agents/Queue/History sections (3 spots), bg /35 -> /15
- Section header pills (Agents/Queue/History): drop border + shadow,
  use uppercase tracking-wider primary-500 for hierarchy without lines
- Drop border on individual agent card containers, swap gradient bg
  for flat /15 to match parent section

Result: panel reads as one continuous dark surface from header to
bottom, no nested ring-on-ring outlines. Hierarchy comes from
typography + bg shading not strokes.

* fix(agent-view): kill demo data fallback — show real state only

Demo agents (Roger/Sally/Bill) were appearing as if they were real
spawned workers because we wired a fallback during Phase 1 visual
port. Per Eric: 'I noticed there's a few agents running but we didn't
spawn'.

Now:
- Empty results -> empty panel (no fake agents)
- Gateway error -> empty panel + error state, not fake agents
- Only real /api/sessions data populates Active/Queue/History sections

createDemoActiveAgents / createDemoQueue / createDemoHistory functions
remain in code for future test fixtures, just not invoked.

* feat(agent-view): swap lobster for wolf in emoji avatar tier

Per Eric: ditch lobster, add wolf instead.

- New WolfSVG component matching the same animation API as Fox/Owl/etc
- Gray palette (gray-200/400/500), yellow eyes with vertical pupils
- Sharp triangular ears, longer muzzle, faint snarl on orchestrating state
- Speech dots on responding, dotted ring on thinking
- AvatarStyle union: 'lobster' -> 'wolf'
- Picker tile: '🐺 Wolf' replaces '🦞 Lobster'
- Renderer map: wolf -> WolfSVG

LobsterSVG component left in code (unused) so no behaviour delta if
storage has stale 'lobster' key (will fall back to default 'hermes').

* fix(themes): rename theme labels — drop 'Claude' prefix, use Nous/Hermes/Bronze

Per Eric: theme picker showed 'Claude Nous', 'Claude Official', 'Claude Classic'
which was odd branding for Hermes Workspace. Themes are referenced by id
('claude-nous' etc) for backward storage compat, but the user-facing label
now reads cleanly:

- Claude Nous       -> Nous
- Claude Nous Light -> Nous Light
- Claude Official   -> Hermes        (was the flagship navy/indigo)
- Claude Official L -> Hermes Light
- Claude Classic    -> Bronze        (bronze accents description)
- Classic Light     -> Bronze Light
- Slate             -> Slate         (no change)
- Slate Light       -> Slate Light   (no change)

Storage keys + ThemeId union unchanged; no migration needed.

* fix(theme): darken sidebar so it reads as solid against dashboard bg

Eric reported the chat sidebar 'looks transparent.'  Root cause: in
`claude-official` the sidebar (#0d1220) and main bg (#0a0e1a) were
nearly identical lightness, so the divide between them faded out
entirely.

Bumped `--theme-sidebar` to #060914 \u2014 deeper than the bg \u2014 so the
sidebar column reads as its own clearly-defined surface without
needing extra borders or backdrop blur.

Only the active theme is touched here; other themes already have
sufficient contrast between sidebar and bg.

Tests/build:
- 12/12 aggregator tests
- pnpm build clean

---------

Co-authored-by: Aurora release bot <release@outsourc-e.com>
2026-05-03 09:55:45 -04:00
Aman
2e5859ff98 fix: include symlinked profiles in workspace discovery (#256) 2026-05-03 09:55:30 -04:00
charithasjayakody-wq
171229c78f fix: clean Hermes Workspace setup and branding copy (#252)
Co-authored-by: Aurora release bot <release@outsourc-e.com>
2026-05-03 09:55:15 -04:00
chopperas1977
c2b852931e fix: show all crew members on Swarm page, not just swarm\d+ IDs (#247)
The sortSwarmMembers function in swarm2-screen.tsx filtered crew members
with regex /^swarm\d+$/i that only matched IDs like swarm1, swarm2, etc.
Agent IDs like rocky, scotty, huyang, and margaret were silently filtered
out, causing the Swarm and Operations pages to show "No swarm workers
discovered from crew status yet." despite the /api/crew-status endpoint
returning all agents correctly.

Fix: change filter to accept any non-empty ID instead of requiring
the /^swarm\d+$/i pattern.
2026-05-03 09:54:50 -04:00
patrick
898413430f fix(update-system): only flag updates when fast-forward is possible (#254)
Co-authored-by: patrickrinoh0910 <patrickrinoh0910@gmail.com>
2026-05-03 09:54:34 -04:00
Sanjay Santhanam
e943c65560 fix(slash-menu): add /plugins to autocomplete and export DEFAULT_SLASH_COMMANDS (#251)
The slash-command autocomplete in the chat composer was missing the
/plugins command, even though there's a real /api/plugins backend route
and the existing test suite (src/components/slash-command-menu.test.tsx)
already specs the expected behavior:

  import { DEFAULT_SLASH_COMMANDS } from './slash-command-menu'
  describe('DEFAULT_SLASH_COMMANDS', () => {
    it('includes /plugins in the slash autocomplete list', () => { ... })
  })

That spec was failing because:
  1. The local list was named SLASH_COMMANDS (not DEFAULT_SLASH_COMMANDS)
     and was not exported.
  2. The /plugins entry was simply missing.

Fixes:
  - Rename SLASH_COMMANDS -> DEFAULT_SLASH_COMMANDS and export it (so
    the test and any downstream code can introspect the list).
  - Add { command: '/plugins', description: 'List installed plugins and
    their status' }.
  - Export the SlashCommandDefinition / SlashCommandMenuProps /
    SlashCommandMenuHandle types that chat-composer.tsx already imports
    (the import was working only because TypeScript exposes types
    structurally even when not explicitly exported, but making them
    public types is cleaner).

Extends the existing test with three additional regression specs:
  - The list contains every core command (/new, /clear, /model, /save,
    /skills, /plugins, /skin, /help).
  - Every entry has a non-empty description and starts with '/'.
  - No command label is duplicated.

Verification:
  pnpm vitest run src/components/slash-command-menu.test.tsx
   ✓ 4 tests passed
  Full suite: 19 failed -> 18 failed (the slash-menu test now passes;
  remaining failures are pre-existing and unrelated).
2026-05-03 09:54:12 -04:00
Sanjay Santhanam
ffddc356f9 fix(connection-errors): show distinct gateway-auth-rejected message and stop misrouting benign 'token' errors (#250)
Two related bugs in src/lib/connection-errors.ts caused the wrong UI
message when the gateway refused a device's auth token (issue #239,
'Hermes Agent rejected the connection token').

1) getConnectionErrorMessage had unreachable code: the
   'gateway_auth_rejected' case fell through to a duplicated
   'clawsuite_auth_required' label, which is already handled above.
   That meant gateway-auth rejection always returned the ClawSuite
   'Claude Login Required / Enter your password' UI, which is wrong
   when the actual problem is the gateway refusing the device token.

   Fix: give 'gateway_auth_rejected' its own message that points the
   user at re-pairing the device or checking the gateway token, not
   at typing a password.

2) classifyConnectionError matched on lower.includes('token'), which
   misrouted benign network errors like
   'failed to fetch token from /api/foo' to gateway_auth_rejected.

   Fix: require 'token' to co-occur with an auth-failure marker
   (unauthorized / forbidden / rejected / invalid / expired / 401 /
   403 / etc.) before classifying as auth-rejected. Bare 'token'
   strings now fall through to the appropriate network/unknown bucket.

Adds full unit-test coverage for the classifier and the message
table, including regression tests for both bugs.

Fixes #239
2026-05-03 09:53:58 -04:00
Eric
87cb85d908 fix: mount UsageMeter at root so search-modal Usage tile works (#258) (#268)
The Usage quick-action tile in the search modal (Ctrl/Cmd+K) emits an
'OPEN_USAGE' window event. UsageMeter listens for that event but was
never rendered in the app root, so the event fired into the void.

Mount it next to SearchModal in __root.tsx. UsageMeter renders no UI
when closed (just the listener), so this has no visual side effect.

Fixes #258.

Co-authored-by: Aurora release bot <release@outsourc-e.com>
2026-05-03 09:53:33 -04:00
Eric
a08b9af6a3 fix(docker): re-add python3 to runtime image (regression of #161/#185) (#267)
PR #185 (commit 8b45b632) added python3 to the runtime Dockerfile to
fix issue #161 — terminal broken in Docker because scripts/pty-helper.py
requires Python at runtime.

Commit efcb7d14 (2026-05-01 'migrate legacy Hermes codename bytes to
canonical Claude') reverted the Dockerfile to a pre-#185 state without
python3, regressing #161 silently. Issue #259 reports the regression.

This is a one-line restore. Inline comment added so the next rename
sweep doesn't trip over it again.

Co-authored-by: Aurora release bot <release@outsourc-e.com>
2026-05-03 09:53:29 -04:00
Eric
6a6d9d958b feat(dashboard): aggregate /api/dashboard/overview + Hermes-native cards (#242)
* feat(dashboard): aggregate /api/dashboard/overview + Hermes-native cards

Workspace dashboard now mirrors what the native Hermes Agent dashboard at
:9120 surfaces, on top of the existing sessions analytics, in a single
server-aggregated round trip.

Adds new server endpoint `GET /api/dashboard/overview` that fans out to:
- /api/status         (gateway state, active sessions, platforms)
- /api/cron/jobs      (cron summary)
- /api/plugins/hermes-achievements/recent-unlocks (recent ribbon)
- /api/plugins/hermes-achievements/achievements   (totals)
- /api/model/info     (provider, model, context, capabilities)
- /api/analytics/usage (token totals, top models, optional cost)

Per-section graceful fallbacks: each slice independently resolves to
null on auth failure / missing endpoint / unreachable dashboard, and
the corresponding card hides itself. Vanilla installs without the
achievements plugin or analytics auth still get a usable dashboard.

Adds 5 new dashboard cards:

- SystemStatusStrip: one-line gateway + active-agents pill at top,
  warning chip when restart_requested.
- PlatformsCard: connected platforms with per-platform state pills
  (api_server, telegram, discord, etc.).
- CronSummaryCard: scheduled / paused / running counts + next-run
  countdown, click-through to /jobs.
- AchievementsCard: 3 most recent unlocks with tier badges, plus a
  modal that fetches a wider window (?achievements=12) for the full
  ribbon view.
- AnalyticsSummaryCard: top-3 models by tokens with proportional bars,
  total tokens over the window, real cost from the dashboard (replaces
  the old hardcoded ~$5/M estimate).

Other tweaks:

- Replace the hardcoded cost subline on the Tokens MetricTile with the
  real estimated_cost_usd value from /api/analytics/usage when present.
- New section row between the chart row and Recent Sessions for
  Platforms / Analytics / Achievements.

Tests: +7 for the aggregator covering the empty / mixed / full payload
shapes plus the field-rename quirks (gateway_platforms vs platforms,
active_sessions vs active_agents). All 31 swarm/dashboard tests green.

* fix(dashboard): use existing hugeicons names (Award01Icon, CancelIcon)

* feat(dashboard): consolidate ops strip + native model card polish

Polish pass on PR #242 (Workspace dashboard parity phase 1) before
merge. Tightens layout per the dashboard spec's '10-second status
read' goal.

Layout changes:
- Drop the centered logo hero. New header is a single row with title,
  Hermes Workspace label, and inline QuickActions.
- Collapse the three stacked status rows (SystemStatusStrip,
  CronSummaryCard, PlatformsCard) into one OpsStrip that surfaces
  gateway state, version, active agents, restart-pending, config
  drift, platform pills, and cron pulse in a single horizontal bar.
- Re-flow main content as 8/4 split: Activity + Analytics on the left,
  Model + Skills + Achievements as a side rail.

Data parity:
- Aggregator now exposes status.version, releaseDate, configVersion,
  latestConfigVersion, hermesHome from /api/status. OpsStrip uses these
  for the version chip and config drift warning.
- New ModelInfoCard reads overview.modelInfo (i.e. /api/model/info, the
  active model the gateway is using) instead of /api/claude-config
  defaults. Surfaces context length and tools/vision/reasoning chips.

UX:
- AnalyticsSummaryCard now renders a stable 'No usage in last Nd'
  empty state instead of disappearing, so layout doesn't reflow on
  fresh installs.
- Cron stale next-run (>7 days overdue) downgrades to muted 'stale'
  label so March overdue jobs don't look alarming.

Cleanup:
- Remove orphaned SystemStatusStrip, CronSummaryCard, PlatformsCard
  components. Drop legacy ModelCard + dead SystemGlance helper from
  dashboard-screen.tsx (-179 lines net).

Tests/build:
- pnpm exec vitest run src/server/dashboard-aggregator.test.ts (7/7)
- pnpm build (passes)

---------

Co-authored-by: Aurora release bot <release@outsourc-e.com>
2026-05-03 09:45:01 -04:00
Eric
371ffb4b32 fix: vanilla-agent onboarding — auth cast, available-models 404 fallback (#265)
* fix(api): replace broken 'authResult as unknown as Response' cast with proper 401

isAuthenticated() returns boolean. The previous pattern:

  const authResult = isAuthenticated(request)
  if (authResult !== true) return authResult as unknown as Response

silenced the TypeScript error but threw HTTPError -> 500 at runtime
because the framework received `false` instead of a Response. This
broke /api/connection-status entirely on protected setups (causing
ONBOARDING_KEY to never persist on fresh installs) and would have
broken the just-merged /api/system-metrics in the same way.

Replace with the canonical pattern used by every other API route:

  if (!isAuthenticated(request)) {
    return json({ error: 'Unauthorized' }, { status: 401 })
  }

Refs #261 (which spotted the pattern in connection-status), #246
(which copied the broken pattern into system-metrics).

* fix(claude-proxy): fall back to /v1/models for /api/available-models on vanilla agent

Vanilla hermes-agent (any version through 2026-05) does not expose
`/api/available-models` \u2014 that endpoint is legacy fork-only. The chat
composer + settings dialog hit `/api/claude-proxy/api/available-models`
expecting it to work, get 404, and fall through to broken UI states
where the model picker is empty.

Fix: when proxying GET /api/available-models and the upstream returns
404, synthesize a compatible `{ models: [...] }` response from
/v1/models filtered by ?provider= so the picker keeps working.

Also: read the bearer token at request time using the same precedence
as the rest of the codebase (HERMES_API_TOKEN || CLAUDE_API_TOKEN ||
module-level BEARER_TOKEN). PR #234 fixed this in openai-compat-api.ts;
this catches the proxy path that was missed.

Refs #261.

---------

Co-authored-by: Aurora release bot <release@outsourc-e.com>
2026-05-03 09:44:52 -04:00
Alex Souza
2b81b6d434 fix(server): read gateway bearer token at request time and honor HERMES_API_TOKEN (#234)
src/server/openai-compat-api.ts has two issues that combine to break
the chat surface for any deployment whose Hermes Agent gateway has
`API_SERVER_KEY` set (i.e. anything that isn't an open loopback
gateway):

1. The local `BEARER_TOKEN` const reads only `CLAUDE_API_TOKEN`,
   ignoring the documented `HERMES_API_TOKEN` env var that the README
   tells users to set. Looks like a leftover from the Claude → Hermes
   rename: the const in src/server/gateway-capabilities.ts:222 honors
   both names, but this local one was never updated.

2. The const is evaluated at module-load time. Under vite-node SSR
   (`pnpm dev`), the module can be loaded in a worker context where
   `process.env` doesn't yet contain the values that systemd /
   EnvironmentFile / .env populated for the parent node process.
   That freezes the constant to '' permanently, even though the env
   is correctly populated by the time `openaiChat` actually runs.

Symptom: chat UI loads, sessions/skills/memory all work, but every
message produces a run with `status: error` and:

  errorMessage: "OpenAI-compatible chat: 401 {\"error\": {...,
  \"code\": \"invalid_api_key\"}}"

…in `<HERMES_HOME>/webui-mvp/runs/<session>/<run>.json`. The error
format matches what the gateway returns for missing Authorization, not
an upstream provider error. Confirmed via instrumentation:

  [DEBUG-AUTH] BEARER_TOKEN length: 0
               env.HERMES_API_TOKEN length: 16

Fix: replace the const with a small `getBearerToken()` helper that
reads the env at call time and honors HERMES_API_TOKEN with a fallback
to CLAUDE_API_TOKEN. Three call sites updated (`getDefaultModel`,
`openaiChat` Authorization header, and the session-id guard).

No behavior change for setups that already worked (open loopback
gateway with no API_SERVER_KEY, or production builds where
process.env is fully populated before module load).
2026-05-03 09:36:29 -04:00
dontcallmejames
0ce4dc5266 feat: restore system metrics footer (#246)
* feat: restore system metrics footer

* fix: stabilize system metrics footer layout

* style: refine system metrics footer chrome

---------

Co-authored-by: dontcallmejames <dontcallmejames@users.noreply.github.com>
2026-05-03 09:34:57 -04:00
dontcallmejames
b5f4dc93ff fix: load kanban task assignees from Hermes profiles (#249)
Co-authored-by: dontcallmejames <dontcallmejames@users.noreply.github.com>
2026-05-03 09:34:49 -04:00
dontcallmejames
8e4cbb2271 docs(swarm): remove dead reference to non-existent aurora-rotate-worker.sh (#257)
* fix: load kanban task assignees from Hermes profiles

* docs(swarm): remove dead reference to non-existent aurora-rotate-worker.sh

Fixes #255. Option A referenced a personal helper script that has never
existed in the repo. The Add Swarm dialog (former Option B) remains as
the single supported path for spawning tmux-backed workers.

---------

Co-authored-by: dontcallmejames <dontcallmejames@users.noreply.github.com>
2026-05-03 09:34:40 -04:00
Aurora release bot
6c82bfd56f fix(playground): GLB probe must inspect content-type or world crashes
TanStack Start's catch-all '$.tsx' route SSRs index.html for any missing
static file, returning 200 OK with content-type: text/html. The naive
r.ok check marked all GLB URLs 'present', then useGLTF tried to parse
HTML as a binary glTF and threw inside Suspense, killing the entire
3D world and forcing the lite fallback to render — exactly what Eric saw
('Athena · Agent' overlay text instead of the 3D scene).

Fix:
- Probe checks content-type explicitly and rejects text/html.
- Wrap GlbInner in a class-based GlbErrorBoundary so even a wrong probe
  no longer crashes the world: errored GLBs cache as 'missing' and the
  voxel body shows.

Verified on dev server: HEAD /avatars-3d/athena.glb returns 200 +
text/html, now correctly classified as missing.
2026-05-03 09:20:25 -04:00
Aurora release bot
fc8db5efd2 feat(playground): optional GLB body swap for named NPCs
- Add PlaygroundNpcGlb component using @react-three/drei useGLTF.
  Loads /avatars-3d/<npcId>.glb on demand, with materials frozen and
  raycasting disabled (parent group handles clicks).
- HEAD-probe per id with module-level cache so a missing GLB never
  triggers GLTF parse errors. Voxel body is the seamless fallback.
- Wire useGlbAvailable() into the NPC component: when GLB present,
  render it instead of the voxel torso/limbs/head; nameplate chip stays.
- public/avatars-3d/README.md with Meshy.ai prompts per Greek god.

Drop a GLB at public/avatars-3d/athena.glb and Athena upgrades. No code
change needed. Mix-and-match is supported (only some NPCs upgraded).
2026-05-03 09:11:27 -04:00
Aurora release bot
a7f1f60f29 feat(playground): LLM-driven NPC dialog + README live-URL update
- New /api/playground-npc route: POST { npcId, playerMessage, history? } ->
  persona-wrapped chat completion via gateway. 8 personas (Athena, Apollo,
  Iris, Nike, Pan, Chronos, Hermes, Eros) with vibe + lore prompts. Caps
  history to 8 turns, message to 800 chars. Falls back gracefully if
  gateway unreachable.
- PlaygroundDialog upgraded with free-form chat input ('Ask {name}
  anything...'). Renders user/assistant turns inline above the existing
  quest-choice buttons. Aborts in-flight requests on NPC switch / unmount.
  e.stopPropagation on input keydown so WASD doesn't move the player while
  typing.
- README: hosted CF Worker URL is now the default cross-device path.
  Added 'multiplayer' + 'LLM dialog' rows to feature table.

Build clean. /api/playground-npc respects HERMES_DEFAULT_MODEL.
2026-05-03 09:08:43 -04:00
Aurora release bot
01affa551b perf(playground): multiplayer optimization sweep (5Hz + skip-still + world-scope + push-counts)
Client (use-playground-multiplayer.ts):
- Drop presence to 5 Hz (200ms, was 100ms). Halves bandwidth, identical look.
- Skip-send when player static (POS_EPSILON 0.04, YAW_EPSILON ~1.4°).
- Avatar config sent only on signature change, not every tick.
- Position-delta gate before re-render (RENDER_POS_EPSILON 0.03).
- World-scoped local rendering (visibleRemotes) — never see remotes from
  other worlds.
- Connection state ('offline' | 'broadcast' | 'ws' | 'both') exposed for HUD.
- Server-pushed count consumed via 'count' wire kind (no /stats polling).

Worker (playground-ws-worker/src/worker.ts) v1:
- World-scoped fan-out: only broadcast to recipients in same world.
- Push 'count' messages on every join/leave (zero poll cost on clients).
- Per-socket token bucket rate limit: 30 msgs/sec.
- 50ms presence dedupe per player (drops floods).
- Stale prune at 5s (matches client).
- Send live count baseline on connect handshake.

HUD (playground-online-chip.tsx):
- Consume server-pushed 'hermes-playground-count' CustomEvent.
- /stats fetch only as 3s fallback if no push arrives.
- Connection-state dot: green (live), yellow (local-only), red (offline).
- Tooltip shows peak + per-world breakdown.
- Pulsing animation when fully connected.

World-3d:
- Coalesced position poll to 200ms (matches presence cadence).
- Surface transport + serverCount on window for HUD chip.

Verified: pnpm build clean, worker deploy clean, WS handshake + count
push + byWorld breakdown all working against the live worker.
2026-05-03 09:02:06 -04:00
Aurora release bot
8a5971c97d feat(playground): nameplate portrait chips + CF Worker WS hub + online HUD
- Replace floating PNG portraits with portrait-chip nameplates above heads
  (NPCs, player, bots, remote players). Voxel body becomes the character;
  PNG identifies them at a glance without the chimera-y face hover.
- Add Cloudflare Workers + Durable Objects port of scripts/playground-ws.mjs
  in playground-ws-worker/ — same wire protocol so client connects unchanged.
  Includes /stats endpoint with online + byWorld + peakToday.
- Add PlaygroundOnlineChip HUD component that polls /stats and renders a
  live 'N agents online' badge. Hidden when VITE_PLAYGROUND_STATS_URL unset.
- Drop unused Billboard / useTexture imports from world-3d.

Build: pnpm build clean. No new deps. Worker deps install separately.
2026-05-03 08:45:46 -04:00
Aurora release bot
a4e5593e9e chore(playground): demote /agora — remove from sidebar/mobile nav, lower sparkles count for perf 2026-05-03 08:17:20 -04:00
Aurora release bot
0d759577c6 feat(playground): UI declutter pass — collapsible help, slim multiplayer chip, utility dock for capture/customize 2026-05-03 08:14:57 -04:00
Aurora release bot
7eb1c6d89d feat(playground): broadcast avatar config to remote players — your custom look shows up in other tabs 2026-05-03 08:08:58 -04:00
Aurora release bot
1a612da6b5 feat(playground): avatar customizer — Sims-style builder for skin/hair/eyes/outfit/cape/helmet/weapon, live SVG preview, persisted, C key to open 2026-05-03 08:07:52 -04:00