* chore: preserve pnpm approved builds
* fix(router): support runtime basepath override for reverse-proxy hosting
The TanStack router was created without a `basepath` option, so the same
built bundle could not be hosted under a path prefix (e.g. behind a
reverse proxy that mounts the app at `/workspaces/<id>/`). Hard refreshes
appeared to work because SSR runs at the proxy-stripped path, but
client-side navigation to dynamic routes such as `/chat/$sessionKey`
silently fell through to the catch-all `/$` route — rendering the
"404 — Not Found" page from inside the SPA.
Read an optional `window.__HERMES_WORKSPACE_BASEPATH__` global and pass
it through to `createRouter`. When unset, behavior is unchanged
(`basepath: '/'`). The value is normalized so callers can pass either
`/workspaces/abc`, `workspaces/abc`, or `/workspaces/abc/` without
upsetting TanStack's pathname matching.
This lets hosting layers inject a tiny inline script before the bundle
loads to mount the app at any path, without rebuilding.
* fix(chat): prevent stale thinking state after page refresh (closes#449)
Root cause: sessionStorage 'waiting' flags persisted across page refreshes
even for completed conversations. The Zustand store restored these stale
entries on mount, and the active-run API check cleared them async —
but there was a visible render window where the UI showed 'thinking'.
Fix:
1. Added activeRunCheckDone state that gates the waitingForResponse memo.
While the active-run API check is pending, stale restored state is
not trusted — the thinking indicator stays hidden until verification.
2. Added onCheckComplete callback to useActiveRunCheck hook that fires
after the API check finishes (success or error), unblocking the gate.
3. Added a useEffect that detects restored stale waiting state and sets
pendingVerifySessionKeyRef so the gate only applies to the key that
needs verification — not to genuine active streams.
Test: e2e/chat-thinking-state.spec.ts injects a stale sessionStorage
entry before page load, then verifies no thinking indicator appears
and the stale entry is cleaned up by the API check.
* fix(chat): eliminate duplicate messages flicker on stream completion (closes#441)
Root cause: onDone handler used queryClient.invalidateQueries() which
triggers an async refetch. During the refetch window, mergeHistoryMessages
ran with stale cache data + realtime buffer, producing visible duplicates
(extra user message + blank line) for 1-2 seconds until refetch completed.
Fix: Directly merge realtime buffer into history cache via setQueryData(),
then clear buffer synchronously. Background refetch runs after for
consistency but doesn't block rendering.
* fix: restore hermes-config and config-patch API routes
The Aurora rename migration (efcb7d14) renamed hermes-config.ts to
claude-config.ts, but the frontend and routeTree.gen.ts still reference
the original paths. This caused all /api/hermes-config and /api/config-patch
requests to fall through to the SPA HTML fallback, breaking config saves
from the settings dialog and provider wizard with 'Failed to save' errors.
Restored by creating thin route files that delegate to the existing
handleHermesConfigGet/handleHermesConfigPatch handlers from
src/server/hermes-config-route.ts.
Fixes the settings dialog (hermes-config GET/PATCH) and provider wizard
(config-patch POST) config save flows.
* fix(server): add essential env vars to terminal session
* fix(swarm): worker card shows stale state after task completes
deriveWorkerState derived the badge from currentTask title substring
matching and markCheckpointResult never cleared currentTask on terminal
checkpoints, so a finished worker's card stayed permanently 'working'.
- swarm-dispatch.ts: clear currentTask on terminal checkpoint
(checkpointStatus !== 'in_progress'), matching conductor-stop's reset
- operational-worker-card.tsx: deriveWorkerState reads authoritative
checkpointStatus/state first, title heuristic only while in_progress
- swarm2-screen.tsx: pass checkpointStatus/state into the card
* fix(portable-history): replay authenticated portable chat history
* fix(config): keep legacy claude-config shim on shared handlers
* fix: harden splash hydration and docker uid mapping
* fix: keep seen update notes dismissed
* feat: consolidate workspace state under configurable state directory (closes#439)
Adds HERMES_WORKSPACE_STATE_DIR env var support, consolidating 5
scattered state files under a single configurable directory.
Changes:
- New src/server/workspace-state-dir.ts with getStateDir() utility
honoring HERMES_WORKSPACE_STATE_DIR → HERMES_HOME/workspace →
CLAUDE_HOME/workspace → ~/.hermes/workspace (fallback chain)
- Updated gateway-capabilities.ts (workspace-overrides.json)
- Updated mcp-presets-store.ts (mcp-presets.json)
- Updated mcp-hub-sources-store.ts (mcp-hub-sources.json)
- Updated mcp-tools-cache.ts (cache/mcp-tools.json)
- Updated knowledge-config.ts (knowledge-config.json)
- Removed 5 duplicated hermesHome() functions, replaced with shared
getStateDir() import
Test: 6 vitest unit tests covering all env var priority combinations
(cherry picked from commit d6bebe0614b0c7b9015bac5e35d315a8450ac146)
* fix(conductor): surface native-swarm progress and harden worker startup
* feat(chat): safely render HTML message markup
* fix(chat): surface installed skills in slash autocomplete
* fix: add swarm runtime reset endpoint
* fix(conductor): mobile rendering — add overflow-y-auto, mobile bottom padding, OfficeView responsive height, tabbar fix
* fix(send-stream): preserve runs on client disconnect
* fix(profiles): skip profiles/default duplicate card
* fix: accept HERMES_AGENT_PATH override
* fix(profiles): allow disabling sticky active_profile writes
* fix: preserve workspace chat session routing
* fix(portable-history): skip replay when gateway session continuity is available
---------
Co-authored-by: Hermes Agent <hermes-agent@local.invalid>
Co-authored-by: jack <jack@hijak.dev>
Co-authored-by: Waylon Kenning <waylonkenning@Waylons-MacBook-Pro.local>
Co-authored-by: Michael Rodriguez <michael@rivercity-industries.com>
Co-authored-by: Vu Tran <baysao@gmail.com>
Co-authored-by: iltaek <iltaekkwon@gmail.com>
Co-authored-by: Aurora release bot <release@outsourc-e.com>
Co-authored-by: jonathanmalkin <jonathan.d.malkin@gmail.com>
Co-authored-by: KT-Hermes <ktadmin@kt-bot2.tekeis.net>
The vite dev server intercepted /api/connection-status with a slim
inline shortcut handler that returned only {ok, mode, backend} \u2014
silently overriding the real route at src/routes/api/connection-status.ts
which returns the full ConnectionStatus payload.
Downstream feature gates (useFeatureCapability, useFeatureAvailable)
read .capabilities from the response. With the slim body, every
capability evaluated to undefined, so dev users got UI states that
looked like 'feature not available' even when the gateway was healthy
and exposing the API.
Fix: drop the inline shortcut entirely. The real route file already
caches via ensureGatewayProbed() (PROBE_TTL_MS), so the cost in dev
is negligible and the payload is correct everywhere.
Closes#285. Also drops the now-unused dashboard URL / token / cache
locals in vite.config.ts that were only used by the deleted handler;
the per-PR work that introduced them (#288, #289) was the
right diagnosis but the wrong fix \u2014 the real route was already
doing it correctly.
Combines two community contributions and one local correction:
* PR #289 (Sanjays2402): probe /api/sessions on the dashboard URL
(default :9119) instead of the agent URL (:8642). The agent does not
serve /api/sessions, which produced a 404 every 15s. Adds a small
cache so success/failure responses don't refetch every poll. Closes#276.
* PR #288 (Interstellar-code): send Authorization: Bearer
$HERMES_API_TOKEN on the connection-status probes so a gateway
secured with API_SERVER_KEY is detected correctly. Adds watch.ignored
for .runtime/.tanstack/.omc/.omx/coverage/dist/etc. so these noisy
internal paths don't fire spurious dev reloads.
* Local correction: did NOT add '**/routeTree.gen.ts' to watch.ignored.
An existing regression test (src/router-route-resolution.test.ts)
forbids that — ignoring the generated route tree breaks route HMR.
The real cause of routeTree.gen.ts thrash is multiple concurrent
vite dev servers writing to the same file (fixed earlier today).
Co-authored-by: Sanjays2402 <Sanjays2402@users.noreply.github.com>
Co-authored-by: Interstellar-code <Interstellar-code@users.noreply.github.com>
* 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
Five distinct issues were causing chat SSE streams to disconnect during
multi-minute silent processing windows on slow upstream providers (e.g.
Z.AI coding plan with rate limits and frequent connection drops).
Fixes in this commit:
- chat-screen.tsx active-run poller no longer false-positives on null
run / queued / pending statuses; only fires streamFinish() on
definitively terminal statuses (completed|failed|cancelled|error).
- vite.config.ts disables Node's default 5-minute requestTimeout for the
dev server only (gated on command === 'serve'). Heartbeats handle
keep-alive at the app layer; production servers keep their defaults to
avoid slowloris exposure. Was killing any HTTP request older than 5min
including healthy long-running SSE responses.
- use-streaming-message.ts AbortError path now clears hook state and
invokes a new optional onAbort() callback instead of returning
silently. Prevents UI flags (sending / waitingForResponse /
pendingGeneration) from getting stuck when a stream is aborted by
cross-session navigation, HMR unmount, or dev-server auto-restart.
- chat-screen.tsx wires onAbort to reset all sending/waiting flags so
the composer unblocks immediately.
- models.ts now exposes streamAcceptedTimeoutMs / streamHandoffTimeoutMs
in its response, sourced from workspace.stream_accepted_timeout and
workspace.stream_handoff_timeout in ~/.hermes/config.yaml (env vars
STREAM_ACCEPTED_TIMEOUT_MS / STREAM_HANDOFF_TIMEOUT_MS still
override). Replaces the abandoned /api/stream-config route.
- chat-screen.tsx feeds those timeouts into useStreamingMessage so users
can tune the no-activity windows via config.yaml without rebuilding.
- Removes orphan /api/stream-config route file (was returning HTML
instead of JSON in dev because TanStack Start hadn't picked it up
without a server restart; functionality moved to /api/models).
Refs upstream issue #195 for full root cause analysis.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
(cherry picked from commit 941f8efa2b8881986c82205ebcb61b58187c4a08)
The 'dev' and 'start:dev' scripts hardcoded --port 3000 on the CLI,
which collided with Baileys/WhatsApp bridges and other services that
also default to 3000. Users couldn't relocate the workspace without
editing package.json.
Changes:
- Drop --port from the CLI scripts; vite.config.ts now owns the port
- vite.config.ts reads process.env.PORT (default 3000) so users can run:
PORT=4000 pnpm dev
- README snippet updated to mention the override
- Default stays at 3000 so every existing install, docker-compose file,
and reverse-proxy rule keeps working
Fixes#96
Co-authored-by: Eric <eric@outsourc-e.com>
User reported on X:
'error: hermes agent not found clone it as a sibling directory or set
hermes agent path in .env'
after running the fixed install.sh. The root cause is that Nous's installer
puts hermes-agent at ~/.hermes/hermes-agent/ (or installs a `hermes` binary
to ~/.hermes/bin/), but the workspace auto-start logic only looked at:
- HERMES_AGENT_PATH env var
- ../hermes-agent (sibling)
- ../../hermes-agent (monorepo)
None of which match Nous's actual install layout. So every user installing
via the one-liner hit this error.
Changes in both vite.config.ts and src/server/hermes-agent.ts:
- Add ~/.hermes/hermes-agent and ~/hermes-agent to the candidate list
- Add a resolveHermesBinary() that finds ~/.hermes/bin/hermes or
~/.local/bin/hermes (Nous installer + fallback)
- Prefer launching `hermes gateway run` via the binary (canonical
entrypoint) over reconstructing uvicorn against the source tree
- Fall back to uvicorn-from-source when only a directory is available
(dev / cloned-in-place setups)
- Update the 'not found' error message to point at the installer URL
instead of asking users to clone a sibling dir
- PATH for the spawned child now includes ~/.hermes/bin and ~/.local/bin
- Probe localhost:11434 (Ollama) and localhost:1337 (Atomic Chat) on startup
- Auto-merge discovered models into /api/models response
- Auto-write custom_providers to config.yaml when new local backend detected
- New /api/local-providers endpoint for frontend discovery status
- 30s probe TTL with deduplication
- Logs provider online/offline state changes
- 5-column kanban board: Backlog → Todo → In Progress → Review → Done
- Drag-and-drop, create/edit tasks, priority, assignees, tags
- Tasks link in sidebar nav
- Kanban proxy route in vite.config.ts for /api/hermes-tasks
- Render ContextBar in chat screen (was imported but never placed)
- Pass sessionId to context-usage API for accurate token data
- Count all tokens (cached + uncached) for real context window usage
- Model-aware max tokens (200k for Claude, 128k for GPT)
- Return model name in context-usage response
- Remove header border-b (context bar replaces separator)
- Remove context bar own border-b for clean look
- Change dev server port from 3000 to 3002
Co-authored-by: outsourc-e <eric@outsourc.e>
Replaces static isFeatureAvailable() with useFeatureAvailable() React hook that fetches from /api/gateway-status. Fixes enhanced features always showing 'unavailable' after client hydration. Credit: @ClintMoody
- vite.config.ts: resolveHermesAgentDir() finds hermes-agent via HERMES_AGENT_PATH env,
../hermes-agent sibling (standard README clone), or ../../hermes-agent fallback
- startHermesAgent() spawns uvicorn on pnpm dev start, reuses if already running,
logs output to vite console, stops when dev server stops
- start-hermes.ts: same path resolution logic, polls health 10s instead of 2s,
returns clear error + clone hint if hermes-agent not found, removed Eric-specific path
- connection-startup-screen.tsx: full rewrite — single useEffect, no stale closures,
no settledRef vs useCallback dependency bugs. Simple: try once, succeed = dismiss,
fail = show screen + poll every 3s. Start Server button kicks poll loop on success.
- webapi/app.py: CORS now covers localhost:3000-3010 by default + HERMES_CORS_ORIGINS env override
* feat: mobile UX polish — context bar, session titles, composer spacing, image preview
- Context bar: slim 3px bar with tap-to-reveal inline label (auto-hides 3s)
- Session titles: auto-friendly names (agent:main:main → Main Chat, heartbeat prompts → Main Chat)
- Title truncated to 20 chars with word boundary breaks, max-width 45vw
- Composer: tighter toolbar padding/gaps, model selector compact on mobile
- Attachment ✕ button always visible on mobile (hover-only on desktop)
- Tap-to-preview fullscreen image overlay (portaled to body)
- Message spacing: tighter gaps between messages on mobile
- User bubbles: compact padding on mobile
- Dashboard NowCard: friendly agent names
- Tasks widget: hides empty columns on mobile, wrapped in useMemo
- Logo tip: only shows on mobile
- PWA icons optimized + apple-touch-icon added
* fix: onboarding SSR hydration + allow empty gateway token
- onboarding-tour.tsx: added mounted state guard, return null during SSR
prevents Joyride from accessing localStorage/DOM server-side
- use-onboarding-tour.ts: lazy localStorage read via useEffect
eliminates server/client state divergence on hydration
- gateway.ts: remove hard requirement for token/password
allows connecting to gateways without auth (e.g. nanobot on port 18790)
device identity signature handles authentication instead
- gateway-setup-wizard.tsx: mark token as optional, add nanobot port hint
Reported-by: @absol_89 (Tharshan)
* fix: remove pull-to-refresh (buggy on mobile) + gateway save timeout 5s→15s
- Remove pull-to-refresh from dashboard and chat message list
- Increase gateway config save timeout from 5s to 15s
- Show 'Config saved. Reconnecting...' instead of timeout error
- Remove onPullOffsetChange prop from chat-screen
- Add onRefresh prop to chat-message-list for manual refresh
- Add mobile viewport detection for responsive padding
- Clean up unused streaming state variables
* feat: mobile agent hub sheet + tab bar improvements + chat header polish
- Add slide-up mobile sheet for agent view panel (replaces desktop sidebar)
- Tab bar: swipe-up gesture to reveal when hidden by keyboard
- Chat header: agent model + connection status display
- Chat empty state: responsive padding for mobile
- Orchestrator avatar: accept className prop
- Onboarding tour steps: minor cleanup
* chore: fix all 40 pre-existing TypeScript errors (0 remaining)
- Remove unused imports/variables across 15 files (TS6133/TS6192)
- Fix update-notifier type narrowing for 'done' state (TS2367)
- Fix route type assignments with string casts for /chat/main (TS2322)
- Fix gateway.ts global declaration syntax (TS2488)
- Remove invalid tooltipClose Joyride style property (TS2353)
- Remove stale skills-screen 'featured' tab comparison (TS2367)
- Remove unused @ts-expect-error in vite.config.ts (TS2578)
- Clean up dead code: getMessageTimestamp, normalizeStatusPayload, etc.
No functionality changes. All fixes verified with npx tsc --noEmit.
* fix: auto-refresh model list cache every 30s
Previously, configured models were cached forever in memory, requiring
a server restart to pick up new models added to openclaw.json config.
Now uses a 30s TTL so model list updates are reflected automatically.
Exports invalidateCache() for manual refresh if needed.
* feat: show all user-configured models in model switcher
Models defined in openclaw.json config but not auto-discovered by the
gateway's models.list RPC now appear in the model switcher. This ensures
newly added models (like claude-sonnet-4-6) show up without waiting for
gateway discovery.
Also exports getConfiguredModelsFromConfig() for direct config reads.
* fix: new chat creates unique thread ID instead of routing to main
* feat: telegram-style slash command autocomplete in chat composer
* feat(dashboard): add WidgetShell component + wiggle/shimmer CSS
- WidgetShell: unified iOS-style widget container with size variants
(small/medium/large), glass background, press states, edit mode jiggle,
loading skeleton, and error state
- WIDGET_SIZE_SPAN export for use in widget-grid CSS grid layout
- Added animate-shimmer utility (skeleton loading)
- Added animate-wiggle keyframe (iOS jiggle edit mode)
- tsc --noEmit: 0 errors
---------
Co-authored-by: Aurora <aurora@MacBookPro.lan>
Root cause: Vite's 'define' config replaced ALL process.env references
with empty objects in both client AND server bundles. TanStack Start's
environment-based builds don't use the old isSsrBuild flag, so the
client-only guard never worked.
Fix:
- Removed blanket process.env replacement from top-level define
- Added client-only transform plugin that replaces process.env only
in client bundles (browser has no process.env)
- Server bundles now keep real process.env for Docker runtime env vars
- Gateway config save no longer fails on read-only containers (EACCES)
Tested: Docker container reads CLAWDBOT_GATEWAY_URL and TOKEN from
runtime env vars, /api/ping returns {ok:true}