Adds a new Matrix theme family (dark + light) to the workspace:
- Black glass terminal surface with phosphor green (#00FF41) signal glow
- Light variant: white terminal paper with green accents (#008F2D)
- Full CSS token set covering bg, sidebar, panel, card, chat, composer,
tool cards, code blocks, and input surfaces
- Registered in ThemeId, THEMES array, LIGHT/DARK_THEME_MAP, LIGHT_THEMES
- Added to ENTERPRISE_THEME_FAMILIES with color preview swatches
Co-authored-by: Worked with Interstellar Code <noreply@interstellarcode.dev>
The provider card dot was rendering green for OAuth providers and local
providers regardless of actual auth/online state. Result: misleading
status (Custom card showed both green AND red simultaneously when
selected; OpenAI Codex / Nous Portal always showed green even without
OAuth login).
Now:
- Single-dot precedence: active > missing-key > verified > none.
- Local providers (authType: 'none') only show green when localDiscovery
reports them online.
- OAuth providers (authType: 'oauth') stay neutral until an actual
session check is wired — no auto-green on declarative authType alone.
- API-key providers unchanged: green when key is set, red when missing.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
NODE_ENV=production enables the Secure flag on session cookies. Browsers
silently drop Secure cookies over plain HTTP, causing login to fail with
no visible error when HOST=0.0.0.0 is used on a LAN without HTTPS.
- Add startup warning in server-entry.js when non-loopback host +
production + COOKIE_SECURE not explicitly disabled
- Document COOKIE_SECURE=0 in .env.example alongside the existing =1 case
- Add COOKIE_SECURE entry to README env-vars table
Closes#149
Worked with Interstellar Code
Replace `ja: EN` fallback with proper Japanese translations for all
existing TranslationKey entries. Also adds a regression test mirroring
the existing Russian/Simplified Chinese tests.
The Japanese locale was already exposed in LOCALE_LABELS and the
language picker, but selecting it had no effect because the LOCALES
map pointed to the English bundle. This change wires up the actual
strings so users who pick 日本語 see translated nav, settings,
tasks, jobs, skills, profiles, and common labels.
No behavior change for other locales.
The file lives in src/routes/api/ but is a helper module, not a route.
TanStack Router was logging 'does not export a Route' on every route
generation pass, contributing to dev-mode log noise.
Renaming to '-send-stream-live-tools.ts' uses the configured ignore
prefix so the route generator skips it cleanly.
Note: the real cause of the 3002 loading loop was multiple concurrent
vite dev servers running against the same hermes-workspace tree, all
writing to src/routeTree.gen.ts and triggering perpetual HMR reloads.
This rename does not fix that root cause but does silence the noise.
Co-authored-by: Aurora <aurora@hermes>
- Page title now reads HermesWorld
- Mobile hamburger nav label/icon updated to HermesWorld + castle
- Side panel label updated from Playground Menu to HermesWorld Menu
- Include session handoff note for continuation
Reverts the sidebar auto-hide on /playground so users keep normal workspace navigation and don't feel trapped inside HermesWorld. The right-side ChatPanel remains hidden on the route because HermesWorld already has its own in-game chat and that panel genuinely competes with the HUD.
Sidebar:
- HermesWorld is now a dedicated promoted link directly under Search \u2192 New Session, before the MAIN section. Stands out because it's outside the dense nav list.
- Icon swapped from Rocket01 (shared with Conductor) to Castle02 in gold (#facc15) \u2014 unique, on-brand.
- Gold gradient NEW badge with subtle glow.
- Removed the duplicate Playground entry from the MAIN section so there's only one HermesWorld link in the sidebar.
Workspace shell:
- /playground route now auto-hides the desktop chat sidebar (same mechanism as chat focus mode). Game canvas and HUD overlays are full-bleed; no more sidebar overlap with fixed-position UI.
- Floating ChatPanel + ChatPanelToggle are also hidden on /playground (HermesWorld has its own in-game chat).
- /playground sidebar item now reads 'HermesWorld'
- Gold gradient NEW badge with subtle glow draws the eye to the flagship destination
- Badge styling is opt-in via item.badge === 'NEW' so other badges still use the default chip styling
Avatar customizer presets:
- Added Chronos (gray hair, gold eyes, dark cloak), Artemis (silver, forest green, bow), Eros (rose pink, violet eyes, bow). Customizer now has 9 presets total.
Cinematic camera (Tab key):
- Cycles 6 preset angles: Isometric / Behind-back / Front-face / Top-down / Cinematic-low / Wide-establish.
- Toast at top-center shows the preset name briefly so you know what just changed. Perfect for filming b-roll.
Loading screen during world transitions:
- Replaces the simple radial fade with a full Cinzel-serif HermesWorld card showing the destination world name, an animated golden progress bar, and a rotating Hermes lore quote (10 lines, randomly picked per transition).
ASCII trailer kit:
- scripts/ascii-trailer.sh takes a screen recording, samples 1 frame/sec via ffmpeg, converts each to ASCII via Pillow + a 10-char ramp, and bundles into ascii-trailer.md ready to paste into Discord. No chafa dependency.
- Title screen hero canvas: gold-warm palette to match HermesWorld branding, plus a 6-node 'agent network' graph layered over the orbiters \u2014 every agent connects to every other through the center, slowly rotating. Reads as 'multi-agent orchestration'.
- Inner orb shifted to warm gold (was cyan).
- Generated ASCII portraits for the 5 building-keeper roles (trainer, recruiter, banker, tavernkeeper, shopkeeper) so dialog cards work for them too.
ASCII art (pyfiglet via skill):
- Generated ASCII portraits for 9 NPCs (athena, hermes, pan, iris, nike, chronos, apollo, artemis, eros) into public/ascii-portraits/.
- Dialog header now shows the NPC's ASCII name in their accent color next to the avatar portrait. Distinctive look, hand-crafted feel.
- Title screen got a HermesWorld ASCII signature under the gold serif heading.
Perf:
- Canvas DPR clamped to min(1.5, devicePixelRatio) so we don't oversample on standard displays.
- powerPreference: 'high-performance' on the WebGL context.
- Disabled stencil buffer (we don't use it) to save framebuffer memory.
- performance.min: 0.5 lets R3F drop quality automatically when frames slow down.
Utility dock (bottom-right, now 7 buttons):
- Screenshot world (PNG, downloads instantly with timestamp)
- Fullscreen toggle
- Copy share link
- Replay narration / mute narration / mute audio / customize avatar (existing)
Help overlay updated with all current shortcuts including 4 Summon and F focus.
- Exit trigger radius 1.05 -> 1.6 so you don't have to land on the exact center.
- Exit pad now clickable (pointer-down anywhere on the ring exits immediately).
- Glowing pillar + point light at the exit so it's visible from anywhere in the room.
- New InteriorExitButton: always-visible 'Leave Building' button at the top of the screen, fixed position. If the floor trigger fails or you can't navigate to the door, click and you're out.
Three coplanar rings (stone-tile plaza, statue inscription, accent ring)
at nearly the same y caused flicker by the central statue. Spread the
heights, made the upper rings transparent with polygonOffset to reliably
sort above the plaza.
UI:
- BUILDERS NEARBY card moved under the player card (top-left, same x-offset as the card).
NPCs:
- Per-NPC ambient lines: each named NPC (Athena, Iris, Pan, Nike, Hermes, Chronos, etc.) has Hermes-themed lore lines that pop as speech bubbles every 12-22s.
Remote players:
- Full knight armor parity: cuirass + glowing sigil + tasset (4 strips) + gauntlets + greaves. Other players in your view now look as good as you do.
Ground textures:
- Procedural stone-tile plaza (canvas texture) under the central HermesStatue in Training Grounds + Agora. Warm sandstone with per-tile color jitter, mortar gaps, subtle highlights and cracks.
World density:
- Forge: HermesStatue centerpiece (cyan-tinted) + 40 floating data motes (Sparkles).
- Grove: HermesStatue (forest-tinted) + 50 bioluminescent fireflies + entrance banners.
- Oracle: HermesStatue (violet-tinted) + 4 incense braziers + 35 floating runes.
Quests \u2014 covers all 6 Hermes skills now:
- agora-diplomacy: meet another live builder + chat with them nearby. Auto-fires when a remote enters your world; rewards 'Diplomat of the Realm' title + 80 diplomacy XP.
- forge-summon: enter the Forge and use action bar key '4' to summon a familiar. Rewards 'Summoner of the Forge' title + 80 summoning XP.
Action bar:
- New 'Summon' ability (key 4): 60-second glowing familiar that orbits you, point-light included, costs 20 MP, 30s cooldown. Maps to Hermes Summoning skill.
- Speech bubble appears over the local player's head when they send a chat (fades after 5.5s).
- Speech bubble appears over remote players' heads when their chat arrives via HTTP polling (lastChat / lastChatAt now updated on chat fan-out).
- addChatMessage dedupes by (authorId, body, ts within 2s) so the same message can't appear twice in the chat panel even if multiple transports deliver it.
- handleIncomingChat rejects messages whose name matches our display name (defense-in-depth against echo from server chat ring entries from older selfIds).
WebSockets were never actually connecting from Eric's browser (CF logs
showed zero WebSocket Upgrade requests during testing). Even when they
do connect, they're unreliable: CF DO hibernation, bg-tab throttling,
dev bundle env issues, network blips all kill them.
HTTP polling: dead simple, works everywhere, survives bg tabs, no
hibernation issue, no env-var dependency.
Server (deployed):
- POST /presence body: {id, name, color, world, x, y, z, yaw, ...}
Returns: {presences (others in world), chats (since lastChatTs), online, byWorld, peakToday, ts}
- POST /chat body: {id, name, color, world, text, ts}
- POST /leave body: {id} (called via navigator.sendBeacon on unload)
Client:
- New 1Hz polling loop in use-playground-multiplayer that POSTs presence
and processes the snapshot response. Hardcoded fallback URL so it works
even with stale dev bundles.
- sendChat now fans out to BroadcastChannel + WS + HTTP for redundancy.
- WS still attempted for low-latency presence updates between polls,
but no longer required for MP to work.
- navigator.sendBeacon on beforeunload/pagehide so explicit leaves
propagate even when the tab is closing.
Logs show no WebSocket Upgrade requests reaching the Cloudflare hub during
Eric's testing \u2014 only HTTP /stats from CLI probes. That means his browser
was loading a bundle that didn't have VITE_PLAYGROUND_WS_URL inlined, so
the hook returned early and never opened a WS. With the public hub URL
hardcoded as fallback, MP works even with stale dev bundles or fresh
clones without an .env file.
Also added a console.log of the actual WS URL on connect attempt for
faster debugging.
The actual root cause: the DO worker was hibernating after ~10s of
inactivity (CF default), which silently killed every live WebSocket.
Clients reconnected but presence map was reset, count went to 0, all
avatars disappeared from each other's screens.
Fix: state.acceptWebSocket() lets the DO hibernate WITHOUT killing the
WebSockets. Messages route to webSocketMessage/Close/Error class methods.
Presence + chat ring are now persisted to storage so they survive
hibernation cleanly.
Server build is now 'hermes.playground.cf-worker.v2-hibernation'.
Deployed: version fc4a2e58-c7e2-44f5-8629-a266da423c7b.
- Local STALE_AFTER_MS bumped to 30s so bg-tab throttling never causes a remote prune. Server prune handles real disconnects after 12s + alarm grace.
- Chat header now shows the actual WS transport state inline ('WS', 'local-only (no hub)', 'offline', 'connecting') in a small chip next to the player count. This lets us see at a glance whether the WS hub is actually reachable without opening DevTools.
This eliminates the 'avatar disappeared then came back' flicker that was
happening every time a tab momentarily lost the WebSocket (CF DO hibernation,
bg-tab throttling, network blip). Now socket close just ages the presence;
if the client reconnects within STALE_AFTER_MS (12s) the avatar persists
seamlessly. Explicit 'leave' messages from the client (sent on beforeunload)
still propagate immediately as before.
Branding:
- Title rebranded from 'Hermes Playground' to 'HermesWorld' with serif gold/cream gradient text and 'the agent MMO' tagline.
- Premium hero with starfield backdrop, vignette, gold-accented identification card.
- Cinzel/Trajan-style serif heading with gold gradient + glow.
- Primary 'Enter the Realm' CTA in molten gold instead of cyan.
- 3-column premium feature cards (Six Worlds / Live Multiplayer / Hermes Skills).
- Numbered 'Your Path' card on the right.
MP debugging:
- selfId now includes Date.now() so even duplicated tabs (which share sessionStorage) get unique ids.
- Console-logs selfId on creation, leave events received, and WS close codes so we can pinpoint what's actually causing the disappearing-avatar bug.
ROOT CAUSE of disappearing avatar: localStorage shared selfId across browser
tabs in the same browser. Both tabs used the same id, so the WS hub stored
only one presence record. When one tab throttled, the other's avatar got
pruned for both sides. Switched to sessionStorage so each tab gets a
unique id.
Narration:
- Built playground-narration.ts on top of the Web Speech API (no API key
needed). Per-world scripts: Training Grounds, Agora Commons, Forge,
Grove, Oracle, Arena.
- Auto-plays once per session per world on entry.
- 4 buttons in the utility dock: replay narration, mute narration,
audio toggle (existing), avatar customizer (existing).
- sessionStorage persists 'already played' flags; localStorage persists
the user's mute preference.
- Cancels previous narration on world change so they don't overlap.
Worker (deployed):
- STALE_AFTER_MS 5000 -> 12000 to forgive bg-tab throttling.
Player avatar:
- Muscled cuirass chest plate + glowing winged Hermes sigil disc on the chest
- Tasset (4 armored skirt strips) at the pelvis
- Steel gauntlets on the forearms
- Greaves (shin armor) on each leg
- Shoulder pauldron stud kept; helmet/cape/weapon variants unchanged
Result: avatars now read as actual knights, not blobs.
Multiplayer:
- KEEPALIVE_MS 1500 -> 1000 so we send at least once per second.
- STALE_AFTER_MS 5000 -> 6500 locally (server is still 5000) so we hold remotes a bit longer than the server prunes.
- Send presence packet immediately on document.visibilitychange + window.focus so backgrounded tabs don't stay pruned for the few seconds after refocusing. This is the main reason 'one of my characters disappeared' happens — Chromium throttles bg setInterval and the server prunes us after 5s.
UI:
- Player card + chat moved a bit further LEFT (left: min(120px, 9vw)) so they sit under the small left rail not on top of it.
- Focus eyeball moved down 20px so it doesn't crowd the minimap.
- Minimap 'M for full' and quest tracker 'J for journal' both replaced with compact [M] / [J] keycap chips.
Multiplayer:
- Send presence WebSocket frame immediately on open instead of waiting for the next 200ms tick (server only counts clients after they send presence; this is why 'players online' was stuck at 0).
- Chat now seeds online count + transport from window globals on mount so it doesn't miss the first dispatch.
UI layout:
- Player card moved right (left: min(180px, 14vw)) so it doesn't overlap with the nearby-builders chip on the left rail.
- Chat dock moved right (same offset, with maxWidth that shrinks to leave room for the right-rail panels).
- Player card now shows the avatar portrait (PNG from /avatars/<portrait>.png) with a level badge in the corner.
- Current Objective card moved to the top-center with a directional arrow that rotates toward the active objective's world position (10 Hz update via setInterval, smooth CSS transition).
- Combined HUD: avatar circle (level) + display name + title + XP-to-next + HP/MP/SP/XP orbs all in ONE card top-left.
- Removed standalone PlaygroundOnlineChip; chat header now shows live player count from the WS hub (transport-aware) plus an NPC subcount.
- Chat: NPC messages get a purple 'NPC' tag; bot authorIds prefixed with 'bot:'.
- Quest tracker pushed down 50px on desktop so it doesn't crowd the minimap area.
- Focus mode button: eyeball icon only (no text label), sits in the gap below the minimap.
- Chat dock moved to bottom-left (out of the way of action bar + sidebar)
- Online chip moved to top-LEFT (was overlapping minimap on top-right)
- Builders Nearby chip moved to top-LEFT under online chip (was overlapping side panel)
- Focus mode (F or button under minimap): hides Quest Tracker + Inventory + Builders chip so the world is visible while playing or recording
- Auto-engages focus mode on first WASD/arrow press
- Esc exits focus mode and closes panels
- Online chip shows '—' before WS connects and 'connecting…' as the status label so 0 doesn't look like 'global is empty'
Fixes CLI routing regression: provider key must be nested under model.provider
not at top-level config since _get_model_config() only reads the model block.
Renames providers.custom → providers.manifest (custom is a reserved type name
in hermes-agent and _get_named_custom_provider returns None for it).
Adds key_env: CUSTOM_API_KEY so the API key stays in .env, not embedded in YAML.
Co-Authored-By: Worked with Interstellar Code <noreply@interstellarconsulting.com>
Replace the static "No custom providers configured." placeholder with
editable SettingsRow entries for CUSTOM_API_KEY and providers.custom.base_url,
matching the existing API Keys section pattern with inline edit/save/cancel.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add envKey: 'CUSTOM_API_KEY' to custom provider card so the API Keys
section auto-shows a Custom row that saves to ~/.hermes/.env
- Remove API Key row from Custom Endpoint section (moved to API Keys)
- Save custom base_url without api_key in config payload
- Update PROVIDERS in claude-config.ts: envKeys: [] → ['CUSTOM_API_KEY']
- Remove unused savingCustom / setSavingCustom state variables
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds base_url + api_key input fields when Custom provider is selected.
Saves to providers.custom in config.yaml via the existing claude-config
PATCH endpoint — no more direct YAML edits for custom endpoint setup.
Also migrates existing manifest provider config to use provider: custom.