Commit Graph

352 Commits

Author SHA1 Message Date
teknium1 d1dbb0ac9e feat(gateway): multiplexer hot-serves profiles created while it runs, unroutes deleted ones
A `gateway.multiplex_profiles` gateway enumerated `profiles/` once at boot, so a profile
created afterwards (CLI, dashboard, Desktop, TUI) was never served until `hermes gateway
restart`; Desktop and the dashboard gave no reminder, so a new profile's bot simply never
connected.

The served set is now reconciled at runtime (`gateway/run_profile_reconcile.py`):
- `hermes_cli/profiles.py` create/delete ping the multiplexer over its control socket
  (new `rescan-profiles` verb); a supervised watcher rescans every 30s as the safety net.
- A new profile gets its adapters under its own runtime scope from its config/.env
  (`_start_one_profile_adapters`, same duplicate-credential guard as boot, now seeded
  with the LIVE secondaries' claims), `served_profiles` in gateway_state.json is
  updated, MCP discovery + log routing run for it. Other profiles' adapters are never
  touched.
- A served profile whose config.yaml/.env changed is re-scanned so a token added after
  create builds the adapter; already-live/queued platforms are skipped (no second poller).
- A deleted profile (tombstone) has its reconnects cancelled, adapters torn down,
  pairing/busy bookkeeping and cached agents dropped, and this process's SQLite /
  memory-store handles released so the deleter's rmtree succeeds.
- The in-process cron ticker takes a live enumerator so new profiles' jobs fire.
- PUT /api/messaging/platforms/<id>?profile=X returns `hot_served` when a live
  multiplexer rebuilt X's adapters; Desktop/dashboard skip the restart banner then.
- `hermes profile create` confirms hot-serve; the restart reminder stays for a gateway
  that did not pick the profile up (older build / signal failed).
2026-09-12 08:49:16 -07:00
teknium1 1df51cb4ee fix(dashboard,website): widen sidebar fallback to chat side panel; trim CSS comments
The chat page's mobile side panel reads the same --component-sidebar-background
skin var as App.tsx and renders transparent under skins that do not define it,
so it gets the same var(--background-base) fallback. Trim the two salvaged CSS
comments to the WHY.
2026-09-12 08:28:35 -07:00
Ivan Marynych ff97415c91 fix: opaque fallback for mobile header/sidebar background
--component-header-background and --component-sidebar-background
resolve to an empty string in some themes. An unresolved CSS custom
property makes the whole background declaration invalid, so the
browser falls back to transparent -- the mobile hamburger menu then
renders with a see-through sidebar and page text bleeds through
behind the nav items, even though the click-to-close backdrop overlay
itself is fully opaque and working correctly.

Add --background-base as an explicit fallback in both var() calls so
the sidebar and mobile header always paint an opaque background, with
themes still free to override via the component tokens.
2026-09-12 08:28:35 -07:00
lawcheck 21ec19dc02 fix(cli): let cron job badges wrap so the job title survives narrow widths
The job header was a single non-wrapping flex row carrying the title and up
to seven badges. On a phone the badges overflowed the card, collided with
the action buttons, and squeezed the title to zero width, so the scheduled
jobs showed no names at all.

flex-wrap lets the badges fall to the next line, and min-w-0 on the title
lets its existing truncate take effect instead of being ignored inside a
flex row.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-12 08:28:35 -07:00
Teknium 65fd3a2b9c feat(status): show each served profile's shared-listener callback URLs
hermes -p <name> gateway status, hermes gateway status and hermes status (under
Serves:) list the /p/<profile>/<path> URL per inbound-port platform the live
multiplexer serves, read from the <profile>:<platform> ingress_url in the default
home's gateway_state.json (hermes_cli/gateway_multiplex_served.py). The dashboard's
messaging payload carries the same ingress_url and the Channels page renders it.
The dashboard's 409 guard now covers only api_server/webhook (the mirrored pair):
enabling Twilio/LINE/Teams/... on a secondary is allowed because the gateway serves
it.
2026-09-12 01:53:15 -07:00
Teknium df95f378a6 feat(dashboard): "Migrate to a single multiplexed gateway" on the System page
GET /api/gateway/migrate/plan returns the CLI plan JSON; POST
/api/gateway/migrate spawns `hermes gateway migrate --multiplex --yes`
detached (action log gateway-migrate.log). The Gateway card shows the
button only for a multi-profile install that is not yet multiplexed, and
disables it while listing the blockers.
2026-09-12 01:49:28 -07:00
Teknium 0dcadf6f41 revert: remove Collective Wisdom V1 (#94266)
Reverts the in-tree org skill-marketplace: hermes_wisdom package, three
model tools, CLI/gateway/desktop/dashboard/Telegram/Slack surfaces.

Later non-Wisdom work on shared files (guest onboarding i18n, dashboard
startup schema, Slack adapter, tui_gateway) is kept; Wisdom-only call
sites and config were stripped from those files.
2026-09-11 11:54:49 -07:00
shannonsands a6ee31f55a feat(wisdom): add Hermes Collective Wisdom Agent V1 (#94266)
* feat(wisdom): add trusted publish and install foundation

* feat(wisdom): add private contribution loop

* feat(wisdom): add managed consumption workflows

* fix(wisdom): close cross-repository safety gaps

* fix(wisdom): align local package and lifecycle policy

* fix(wisdom): require explicit profile setup

* docs(wisdom): repin reconciled gateway head

* fix(wisdom): fence content downloads and approval receipts

* docs(wisdom): record generation-fenced downloads

* docs(wisdom): record unified delivery PR

* fix(ci): stop passing invalid classifier inputs

* docs(wisdom): remove internal requirements ledger

* feat(wisdom): localize dashboard and desktop copy

* feat(wisdom): complete local contribution and consumption UX

* style(wisdom): satisfy desktop lint

* chore(wisdom): refresh requirements pin

* test(dashboard): allow formatted profile copy

* test(wisdom): stabilize desktop interaction coverage

* fix(wisdom): surface dashboard action failures

* fix(wisdom): add repeatable Portal demo login

* feat(wisdom): add actionable skill notifications

* feat(wisdom): add notification install and update actions

* fix(wisdom): make Telegram skill alerts actionable

* fix(wisdom): always refresh demo Agent login

* feat(wisdom): embed Telegram notification actions

* fix(wisdom): preserve Telegram notifications after actions

* fix(wisdom): keep Telegram notification cards readable

* feat(wisdom): add Telegram candidate approval flow

* feat(wisdom): explain Telegram qualification reasons

* fix(wisdom): reconcile cross-surface candidate actions

* feat(telegram): add Collective Wisdom management command

* chore(wisdom): refresh Gateway contract pin

* chore(wisdom): advance Gateway contract pin

* feat(wisdom): align command UX across clients

* feat(slack): add Collective Wisdom management parity

* feat(wisdom): add security and professionalism reviews

* feat(wisdom): add first-time qualification guidance

* feat(wisdom): simplify qualification sharing choices

* feat(skills): add optional editorial metadata

* feat(wisdom): enrich legacy skill presentation

* fix(wisdom): harden review and update boundaries

* fix(wisdom): emit canonical review timestamps

* fix(wisdom): align with merged gateway and main

* wisdom: add agent-led sharing core (policy, evidence, schemas, templates, delivery, weekly job, share/install flows)

- hermes_wisdom/agent_led/: policy resolution (server > local > defaults),
  7-day evidence builder that excludes bundled/hub/managed skills and
  dismissed/handled/recently-suggested content hashes, strict pydantic
  schemas for agent output with repair-or-reject, fixed copy templates
  (Share / Teammate / Published / Update / Mute), idempotent retried
  delivery ledger with stale-action resolution, weekly review job,
  resumable Share and Install flows.
- prompts/: candidate review, recipient recommendation, share packaging.
- tests/wisdom/test_agent_led.py: 30 tests.

* wisdom: agent-led renderers and button action dispatcher

- render.py: Telegram HTML, Slack blocks, Desktop payload; editorial name
  is the emphasized line, product label stays separate.
- actions.py: resolve opaque wa:<action>:<dedup> targets via the delivery
  ledger; Not now -> dismissal, Mute -> fixed options, Share -> resumable
  packaging flow, Install/Update -> plan command. Never publishes/installs.

* wisdom: CLI verbs, agent_led config default, conversational catalog skill

- hermes wisdom browse/review-week/act/share/dismiss/mute (all --json).
- wisdom.agent_led config block, default enabled.
- SKILL.md rewritten so natural-language catalog questions map to the CLI
  verbs, share/install flows and fixed notification templates.

* wisdom: wire agent-led weekly review into gateway tick and Telegram buttons

- gateway housekeeping tick calls maybe_run_weekly_review with a home
  channel sender when a Telegram adapter is available.
- Telegram: wa: callbacks resolved through the ledger (stale-safe), mute
  duration keyboard, send_wisdom_agent_recommendation rich card + fallback.

* fix(wisdom): integrate local mediation and harden model and setup boundaries

* fix(wisdom): honor authoritative recommendation policy and defer on failure

* fix(wisdom): synchronize opaque suppression and recheck delivery preferences

* feat(wisdom): route weekly selection through the session-owned assessment queue

* fix(wisdom): prepare and submit the reviewed generated share package

* feat(wisdom): separate native Share preparation from publication consent

* feat(wisdom): sync native mute choices through a leased preference outbox

* feat(wisdom): bind native mute controls to durable preference choices

* feat(wisdom): add scoped desktop and dashboard notification settings

* fix(wisdom): revalidate feed recommendations before assessment and delivery

* fix(wisdom): persist validated delivery receipts before completing notices

* feat(wisdom): add private notification claim and receipt client

* Persist Wisdom send reservations and recover delivery acknowledgements

* Route legacy Wisdom controls through current native review

* Add typed private Wisdom operation outcome client

* fix(wisdom): make agent-led advice usable in the local demo

* fix(wisdom): keep requested consent outside proactive limits

* fix(wisdom): distinguish unavailable assessments and preserve digest text

* fix(wisdom): assess ongoing usefulness beyond the current task

* fix(wisdom): restore immediate qualification sharing controls

* fix(wisdom): separate qualification review from installation advice

* fix(wisdom): collapse review checklists and simplify sharing copy

* fix(wisdom): show compact sharing progress and publication receipts

* fix(wisdom): require credential prefixes rather than matching skill names

* fix(wisdom): finish package checks before presenting sharing consent

* fix(wisdom): scan local skills before qualification cards

* fix(wisdom): update moderation results on existing sharing cards

* fix(wisdom): keep sharing review accessible from receipt cards

* fix(wisdom): align mediated review cards and collapsible checks

* fix(wisdom): clarify clean security summary wording

* fix(wisdom): normalize consent plans and add explicit recheck

* fix(wisdom): keep install and update receipts concise

* fix(wisdom): collapse assessments and deduplicate operation cards

* fix(wisdom): restore private Portal review from native cards

* fix(wisdom): sync Portal publication to original consent card

* fix(wisdom): show local skill version on sharing cards

* fix(wisdom): skip agent recommendations for self-published versions

* fix(wisdom): simplify candidate notices and local-edit recovery copy

* feat(wisdom): submit locally reviewed packages with one confirmation

* feat(wisdom): expose safe receipt and outcome sync recovery

* wisdom: onboarding notice says detect and share, names the user's own skill

Copy review from the product owner on the first and returning
qualification notices (fixed delivery mode):
- the feature blurb now says the org enabled detection *and sharing*
- both notices say the detected skill is one the user created
- both close with an exclamation mark

Applied identically to hermes_wisdom.notice, the desktop and web i18n
strings, and the tests that assert the sentences.

* wisdom: one opener, no approval line, ask to share after the skill is shown

Product owner review of the candidate card.

- The Hermes written card now opens with the same sentence as the fixed card
  ("Your organisation has enabled Collective Wisdom, a feature designed to
  automatically detect and share useful skills across all team members.")
  instead of its own blurb, so there is one first time message.
- "Nothing is shared without your approval." removed from Telegram, Slack
  and Desktop. The buttons already make the permission explicit.
- "Would you like to share?" no longer appears before the skill is named.
  It is now the last line, after the skill name, description, why suggested
  and the checks, and reads "Would you like to share it?" (matching the
  agent led template wording).

Tests updated for the new order; proposalNotice removed from all desktop locales.

* wisdom: American spelling, organization

Product owner decision: user facing copy uses American spelling.
Changes "Your organisation" to "Your organization" in the chat notice,
the Hermes written card opener, the desktop and web strings, and the
tests that assert them. Identifiers such as nas_organisation:* and the
German and French locales are untouched.

* wisdom: candidate card copy round 4 (owner review)

Apply the product owner's round 4 copy decisions to the Hermes Collective
Wisdom candidate card on Telegram, Slack, Desktop and the shared views:

1. Hermes-written cards are titled "Hermes Collective Wisdom" instead of
   the bare "Collective Wisdom".
2. The "Reusable skill ready to review" line is gone from the candidate
   card (Telegram rich card and plain fallback, legacy agent-led share
   template).
3. The skill name and description are labelled: "Skill name: <name>" and
   "What it does: <description>" (Telegram, Slack, Desktop).
4. "Why suggested:" is now "Why others might benefit:".
5. A passing professionalism review reads "Safe to share at work ✓ (no
   inappropriate content found)" with no per-check bullets and no "Pass";
   a failed review reads "Needs a look before sharing at work (possible
   inappropriate content)" and lists only the checks that flagged
   something. Pending/unavailable wording is unchanged.
6. Telegram button toasts: "Will ask later...", "Preparing more
   details...", "Sharing...".
7. Qualification reasons: "You used this skill consistently across many
   days." and "You've really refined this skill."
8. prompts/wisdom_candidate_review.md asks for a compelling
   editorial_name, a simple one_line_description and a compelling
   why_coworkers_benefit under 300 characters; "Be concise and
   convincing." becomes "Be concise and compelling: the goal is that the
   user wants to share it."

Tests updated for the new strings; review_text() gains direct coverage.

* wisdom: re-apply owner copy after rebase

- Native share cards (advice_view/interaction_view): drop the approval line, ask "Would you like to share it?" as the last line after the checks
- Hermes-written completion card titled "Hermes Collective Wisdom"
- Qualification reasons use the owner wording (consistently across many days / really refined)
- American spelling (organization) in remaining English copy
- Desktop test asserts the current Share button; web test matches the returning notice

* fix(wisdom): pin reconciled Gateway and verify Unicode hash vectors

Pin Gateway 60cd2d6b613ae3cd4a6e65155d1142006d907e78 and byte-identical producer artifacts. Verify every content-order case and package-manifest binding. Validation: 186 focused Python tests, Ruff and contract verifier.

* fix(wisdom): reconcile optional SDK tests and frontend lint

* fix(wisdom): default to agent-written notification summaries

* fix(wisdom): restore deferred install review and browse controls

* feat(wisdom): inspect installed setup with exact package provenance

* feat(wisdom): run native-approved installed setup steps with durable evidence

* fix(wisdom): recover interrupted setup with explicit native consent

* feat(wisdom): hand native installs into guided setup review

* fix(wisdom): continue requested setup with fixed notification copy

* fix(wisdom): preserve setup while waiting for a session model

* fix(wisdom): expose canonical setup review controls on desktop

* fix(wisdom): resume setup after recorded automatic updates

* fix(wisdom): make missing setup prerequisites recheckable

* chore(wisdom): align Agent with verified Gateway contract

* fix(wisdom): stop guessing team slugs in portal links

* fix(wisdom): retire pending advice on account sign-out

* fix(wisdom): cancel advice after terminal account revocation

* fix(wisdom): fence feed responses across account sign-out

* fix(wisdom): checkpoint signed-out feed before reactivation

* fix(wisdom): link proactive advice to scoped notification settings

* fix(wisdom): coalesce queued publication recommendations by version

* fix(wisdom): keep package review navigation local and deferable

* fix(wisdom): reflect installed state in discovery controls

* fix(wisdom): show exact checks before command confirmation

* chore(wisdom): pin bounded analytics privacy contract

* chore(wisdom): pin retired legacy notification contract

* feat(wisdom): review publisher usage with exact sharing copy

* fix(wisdom): align discovery and review check summaries

* fix(wisdom): show expired consent before confirmation

* fix(wisdom): require fresh review for legacy install controls

* fix(wisdom): preserve review expiry across check toggles

* fix(wisdom): retain update policy in native install reviews

* fix(wisdom): surface failed native card edits

* fix(wisdom): persist local command approval reviews

* fix(wisdom): use saved approvals for messaging commands

* test(wisdom): provide scan result in setup handoff fixture

* test(wisdom): exercise Telegram approvals with saved review state

* fix(wisdom): retain suppression policy for offline deferral

* fix(wisdom): reconsider candidates after deferred suppression expires

* fix(wisdom): bind review checks and report verified readiness separately

* fix(wisdom): persist accepted publication intent and recover exact outcomes

* fix(sync): pin UTF-8 tree ordering across writers

* chore(wisdom): pin organisation-scoped Gateway authorization

* fix(wisdom): restrict consent delivery to user-facing sessions

* chore(wisdom): refresh reviewed Gateway contract pin

* fix(wisdom): preserve kept tools in Blank Slate exclusions

* test(auth): reset anonymous fixture with a profile-scoped cache

* fix(wisdom): gate local surfaces and work on current profile entitlement

* fix(wisdom): invalidate quiet tool cache on entitlement changes

* test(wisdom): authorize local consent gateway fixtures

* fix(wisdom): keep entitlement decoding free of native crypto imports

* test(wisdom): provide local entitlement to demo CLI subprocess

* ci: leave upstream workflow unchanged in Wisdom PR

* fix(wisdom): ship package and contracts in Nix wheels

---------

Co-authored-by: hbizi <36184542+hbizi@users.noreply.github.com>
2026-09-11 19:04:06 +10:00
teknium1 87cc4de430 fix(dashboard): place the deactivation ref reset after the callbacks that also write those refs
PR #106445's effect was inserted above `reconnectPty` / `startFreshPty` /
`startFreshDashboardChat`. React Compiler's `react-hooks/immutability`
rule then treats `ptyInputLineRef` and `mobileReplacementInputUntilRef`
as effect-owned values and flags the six pre-existing assignments in
those callbacks as errors ("This value cannot be modified"), which is
what turned the `JS & TS checks` job red. Declaring the effect after the
callbacks keeps the rule quiet; eslint on the file is back to main's
0 errors / 3 warnings.

Adds one pure-function vitest on `normalizePtyMobileInput` proving the
symptom: a stale tracked line ("hel") makes an in-window re-emission of
"hello" come out as 3×DEL + "hello", which over a composer already
holding "hello" leaves "hehello"; with the tracker reset the same input
passes through untouched.
2026-09-09 10:48:49 -07:00
Konstantin Khlopkov cbd1018ea6 fix(dashboard): clear input refs on tab deactivation (#106403)
(cherry picked from commit cb6fd96ef0521ed887bb136e12dadf2937053c3c)
2026-09-09 10:48:49 -07:00
Teknium d47adec28f Merge origin/main into feat/plugin-catalog
Python plugin CLI/loader/web/tui files taken from main wholesale; the
catalog layer is re-ported onto main's decomposed shapes in the
following commits. plugin_index.py removed (catalog is the sole
discovery system).
2026-09-09 04:15:27 -07:00
Teknium fd3565deec fix: remove dedicated user-facing output cap controls 2026-09-07 06:15:43 -07:00
Austin Pickett 04fd0172cd fix(dashboard): stop the embedded TUI repainting on every OS app-switch (#103165)
* feat(pty_bridge): mark the dashboard-spawned TUI with HERMES_PTY_HOST

Ink needs to know when its emulator is the dashboard's xterm.js rather
than a native terminal, so it can drop hidden-tab recovery work that only
makes sense for emulators that coalesce output.

Co-authored-by: Raymond <supere989@users.noreply.github.com>

* fix(tui): skip the focus-in erase+repaint under the dashboard PTY

Ink answers a DECSET 1004 focus-in with a full clear+repaint to heal rows
a native emulator may have dropped while the tab was hidden. xterm.js fed
by the dashboard WebSocket never drops frames, and it reports focus on
every OS window blur/focus, so under the dashboard that repaint was a
visible "session reloaded" flash on every alt-tab. Keep the mode
re-assert and keep delivering the focus report to TerminalFocusProvider
(the composer hides its cursor on blur); only the repaint is skipped.

Co-authored-by: Raymond <supere989@users.noreply.github.com>

* fix(web): restore terminal focus after an OS app-switch

Alt-tabbing away and back lands browser focus on <body>, so Ctrl+V never
reached the composer. Pull focus back into xterm on window focus under
the same ownership rule tab activation already uses, extracted into
shouldRestoreTerminalFocus so both paths share it.

Co-authored-by: Raymond <supere989@users.noreply.github.com>

---------

Co-authored-by: Raymond <supere989@users.noreply.github.com>
2026-09-04 20:24:02 -04:00
liuhao1024 74b00d7a97 fix(dashboard): route every per-row session request at the row's owning profile (salvage #99387)
The Sessions page listed rows stamped with their owning profile but sent
delete/bulk-delete to the global management profile, which stays "" while
the sticky active profile equals the dashboard process's own — so the
request opened the process store, missed, and returned a false
`already_absent` success while the row survived in profiles/<p>/state.db.

Same class at three sibling sites the PR didn't touch: renameSession,
exportSessionUrl and the expanded-row getSessionMessages read. One
`rowProfile(id)` owner now feeds all four (+ bulk delete); unstamped rows
(search results) fall back to the management profile as before.

Test trimmed to one jsdom scenario driving all four row actions.

Co-authored-by: Teknium <teknium@nousresearch.com>
2026-09-02 06:47:55 -07:00
Teknium df4b3733ba fix(cron): every last_status consumer renders delivery_failed explicitly (dashboard badge, Desktop inspector, /cron list, docs)
Audit of every last_status reader outside the scheduler (rg last_status across
web/, apps/desktop/, hermes_cli/, tui_gateway/, tools/, scripts/, website/):

- web dashboard CronPage: last_status was never rendered at all — a
  delivery_failed job showed a green 'scheduled' badge and only a small red
  'delivery: ...' line. New pure cronLastResult() helper maps the closed
  literal set to tones (ok=success, delivery_failed/blocked_config=warning,
  error/unknown=destructive) and the card now shows an amber
  'delivery_failed' badge (title = last_delivery_error).
- Desktop hermes-bots routine inspector: 'Last result' printed the raw
  literal; routineLastResult() spells out each one ('Ran, but delivery
  failed', 'Blocked by configuration (not run)', ...), unknown passes through.
- /cron list (cli_commands_mixin): 'Last run: <ts> (delivery_failed)' now
  appends the delivery reason, since last_error is None for those runs.
- hermes cron list/doctor and the cronjob tool already handled the literal
  on this branch; no consumer compared == 'ok' for success apart from the
  cronjob manual-run path, which the branch already fixed.
- developer-guide/cron-internals.md: table of last_status literals + which
  detail field carries the reason.

Live repro (real 'hermes dashboard' on a temp HERMES_HOME with a
delivery_failed job, CronPage rendered against the live /api/cron/jobs):
before — badges [scheduled, default, telegram:123]; after — badges
[scheduled, delivery_failed (warning tone, title 'telegram: 502 Bad
Gateway'), default, telegram:123].
2026-09-02 00:52:58 -07:00
Hermes Agent 3e56911edb fix(i18n): add sessionExpiredNoError to all fully-typed web locales
The salvaged commit added the key to web/src/i18n/types.ts and en.ts
only; the 15 web locales typed as full Translations (fr/de/es/it/pt/
ru/tr/uk/ko/ja/zh/zh-hant/af/ga/hu) then failed 'tsc -b' (the real
build path) with TS2741. web/ar.ts uses defineLocale and falls back
to en, so it needs nothing. Follow-up for salvaged PR #98716.
2026-09-01 09:28:17 -07:00
kokhlo 55181a7d79 fix(oauth): surface actionable guidance when sign-in windows lapse
The local expires_in countdown killed OAuth sessions with a bare
"Session expired" before the backend poller's enriched message (Portal
sign-in stalled in the opened tab, retry/API-key fallback) could reach
the UI, and the desktop onboarding poller had no local expiry at all —
a dead session polled forever. Both surfaces now lapse with guidance
naming the common cause, prefer the backend error_message when it has
one, and keep polling when the backend still reports pending (clock
skew).
2026-09-01 09:28:17 -07:00
Ryan Currah c8a7c6c36b fix(dashboard): remove deleted custom env keys 2026-08-31 03:37:43 -07:00
joaomarcos 0099f250c2 fix(auth): close Anthropic OAuth review gaps 2026-08-29 18:34:35 -07:00
Teknium 7e17ff0ab3 Merge origin/main into feat/plugin-catalog — reconcile with landed index/manifest-v2 tracks 2026-08-27 21:29:42 -07:00
Teknium a75ea37dc5 feat: browser snapshots drop LLM summarization — truncate-and-store like web_extract; auxiliary.web_extract slot removed
web_extract stopped using an auxiliary LLM long ago (deterministic
truncate-and-store), but browser snapshots still routed oversized
accessibility trees through the auxiliary web_extract model, keeping a
dead-looking aux slot alive across every config/picker surface.

- tools/browser_tool.py: remove _extract_relevant_content and
  _get_extraction_model; oversized snapshots always truncate at line
  boundaries, store the full tree to cache/web, and append a read_file
  pointer (element refs beyond the cut live in the file)
- tools/browser_camofox.py: same — no LLM path
- Remove auxiliary.web_extract slot: config_defaults (removal note, same
  pattern as session_search/PR #27590), cli.py defaults + env bridge,
  gateway/run.py bridged keys, hermes config display, hermes model picker,
  dashboard REST slots, desktop + web AUX_TASKS, i18n labels (en/zh/
  zh-hant/ja/ar)
- Docs: env-vars, configuration, fallback-providers, browser + zh-Hans
  mirrors (web-search zh-Hans was stale on the old LLM pipeline — synced
  to truncate-and-store truth)
- Tests updated: aux bridge uses approval slot, browser tests assert the
  LLM path is gone and stored files are secret-redacted
2026-08-24 20:11:18 -07:00
chelsealong 9c013eaaf8 fix(dashboard): follow scroll on implicit active-session resume (#93518)
pty_ws already fell back to the per-channel active-session file when a
/chat WS connects with no ?resume= param, replaying the whole session
into the PTY, but the frontend only pinned xterm's viewport to the
bottom when resumeParam came from the URL (#59591). The implicit path
had no way to learn a replay was happening, so the viewport stayed at
the top of the scrollback.

pty_ws now sends a one-off JSON control frame naming the session id it
resolved from the active-session file, before any PTY bytes; PTY
output itself always arrives as binary frames, so this is unambiguous
on the wire. ChatPage tracks an `effectiveResume` value seeded from
resumeParam and updated when this control frame arrives, and the
existing follow-scroll/sanitizer/hydration logic keys off it instead
of the URL param alone.

Fixes #93518.
2026-08-24 03:21:49 -07:00
Teknium 65c58651b0 feat: review slot appears in every aux-model picker (desktop, dashboard, CLI)
Follow-up to #93339: the auxiliary.review slot existed in config but was
missing from every model-picker surface, so users could only set the
review model by hand-editing config.yaml.

- hermes_cli/web_server.py: review in _AUX_TASK_SLOTS (REST allowlist,
  stale-aux warning sweep)
- hermes_cli/main.py: review in _AUX_TASKS (hermes model aux picker)
- apps/desktop model-settings.tsx + all 5 i18n locales (en/ja/zh/
  zh-hant/ar): review slot with label/hint
- web/src/pages/ModelsPage.tsx: review row in dashboard Models page
- tests: registry-sync test pinning review across DEFAULT_CONFIG,
  _AUX_TASKS, and _AUX_TASK_SLOTS (curator pattern)
- docs: aux-task table in fallback-providers.md (en) + zh-Hans mirrors
  of fallback-providers and the delegation /review section missed in
  #93339
2026-08-23 18:22:39 -07:00
Teknium 1bf8bd2c7d feat(models): 'ox alpha' now finds x-preview-f-free in every model picker
The OpenCode Zen wire slug for the Ox Alpha stealth model is opaque
(x-preview-f-free); users searching the picker for 'ox' or 'ox-alpha'
found nothing. Adds the search alias across all four synced alias
tables (CLI, desktop, web, TUI) plus tests. Wire id is unchanged and
still what renders and gets sent to the provider, matching the k3 →
kimi-k3 precedent. No canonical-dedup collision with opencode-go's
keyed ox-alpha-free slug.
2026-08-21 14:38:19 -07:00
Teknium a1682376ca feat(profiles): rename any agent — the default profile gets a display name (#45624)
`hermes profile rename default <name>` (and the Desktop/dashboard rename
flows) now set a presentation-only `display_name` in profile.yaml instead
of erroring. The canonical id stays "default"; resolution, comparison,
and spawn paths are untouched. Named profiles keep real renames and their
display_name survives the move.

Surfaces: profile list/show/status, /profile (text only — data.profile
stays canonical), dashboard ProfilesPage, TUI-gateway profiles.list, and
Desktop (rail, switcher, Manage page, and the Bot Mode roster via a
displayName fallback so a renamed default shows its name, not "default").

Slimmer redo of the direction in PR #87760 by @yxssxn — thanks; see PR
body for what changed vs that approach.
2026-08-18 02:27:18 -07:00
Teknium cb1b1da219 fix: surface missed cron fires as last_fire_error on the job record
On hosted deployments a scheduled fire that cannot be forwarded to the
gateway api_server (dead 8642 listener, gateway down) was invisible
outside gui.log: no execution row is created because the claim never
happens, so `cronjob list` showed a healthy job that silently missed
days of scheduled runs (4 consecutive nightly misses in the field,
diagnosed only by log grep).

Changes:
- cron/jobs.py: note_fire_forward_failure() durably stamps
  last_fire_error ({at, detail}) on the job record; mark_job_run clears
  it on the next successful run so it always describes current
  auto-fire health (mirrors preflight_alerted/drift_alerted).
- hermes_cli/web_routers/cron.py: the dashboard fire webhook stamps the
  job on the gateway-unreachable path, best-effort (never disturbs the
  503/Retry-After retry contract or the OOF-266 intentional-stop drop).
- tools/cronjob_tools.py: _format_job carries last_fire_error so the
  agent-facing cronjob list surfaces it.
- hermes_cli/cron.py: `hermes cron list` prints a red
  "Missed scheduled fire" line.
- web/: dashboard CronPage renders the miss; api.ts type updated.
- gateway/run.py: one-time startup warning when an external cron
  provider is active but the api_server adapter is not running (the
  fire path is dead-on-arrival; most common cause is API_SERVER_KEY
  missing from an unsupervised gateway relaunch).
- website/docs: cron doc section on missed fires.
2026-08-17 11:29:10 -07:00
Teknium 3ef63373f8 Port from paperclipai/paperclip#10875: route all dashboard copy actions through the HTTP-safe clipboard helper
Self-hosted dashboards served over plain HTTP on a LAN have no
navigator.clipboard (insecure context), so every direct writeText call
silently failed. web/src/lib/clipboard.ts already ships the HTTP-safe
copyTextToClipboard fallback but only OAuthLoginModal used it; ChatPage
(OSC 52 + Ctrl/Cmd+Shift+C), ProfilesPage, SystemPage, and WebhooksPage
all bypassed it. Route them through the helper and add a source-level
regression test that rejects any new direct clipboard write outside
lib/clipboard.ts (clipboard reads are exempt: no legacy fallback exists).

Sabotage-verified: the guard test fails when a direct write is introduced.
2026-08-16 22:10:29 -07:00
Teknium ea29702749 feat(cron): --continuity / --no-continuity flags on hermes cron create/edit
CLI parity for the continuity toggle:

- subcommands/cron.py: --continuity on create; --continuity / --no-continuity
  tri-state pair on edit (same store_const pattern as --no-agent/--agent)
- cron.py: forwarded to the cronjob tool; created/edited job summaries print
  a "Continuity: on" line
- cronjob_tools._format_job: reports continuity as an explicit boolean and
  strips the reserved 'self' entry from the reported context_from list
- cron-job.ts: form reader accepts both shapes (raw store record with 'self'
  inside context_from, or formatted record with the explicit flag)
- docs: CLI flag examples in the continuity section

E2E (real argparse -> cron_create/cron_edit -> jobs.json in temp HERMES_HOME):
create --continuity stores ['self']; edit --no-continuity clears; edit
--continuity restores; default-off unchanged. 91 cron/tool tests + 16 CLI
cron tests + vitest 10/10 pass.
2026-08-16 22:09:28 -07:00
Teknium 0b13cafffa feat(cron): continuity toggle across dashboard, Bot Mode routines, and TUI cron RPC
Wire the continuity flag through every cron-creation surface, not just the
model tool:

- dashboard (web/): checkbox in the cron job editor; form state round-trips
  the stored reserved 'self' entry into the toggle and strips it from the
  context_from textarea; web_server dashboard validator skips 'self'
  (create precedes the job's existence)
- Bot Mode Routines tab (hermes-bots plugin): Continuity checkbox in the
  New Cronjob dialog, forwarded through cron.manage
- tui_gateway cron.manage RPC: optional continuity param on action=add

vitest cron-job suite 10/10 (4 new), tsc app project clean, py_compile clean.
2026-08-16 22:09:28 -07:00
Teknium ad02425470 fix(web): gate keyboard-inset scroll pin on chat page visibility (salvage follow-up for #74579)
ChatPage stays mounted (hidden) on every dashboard route so the PTY
survives tab switches. With the visualViewport listeners attached
unconditionally in the PTY effect, the NS-434 scroll pin
(window.scrollTo(0, 0)) fired whenever a soft keyboard opened on ANY
page — fighting iOS Safari's own scroll-into-view for focused inputs on
Settings, Sessions, etc.

Move listener attachment into an isActive-gated effect: attach on
chat-tab activation, detach on deactivation (clean lifecycle, no
if-check inside the hot handler). The handler reads through refs
populated by the PTY effect, so the two lifecycles stay independent.
Deactivation also clears any applied inset padding so a keyboard left
open during navigation can't strand stale bottom padding on the hidden
terminal wrapper.

Adds a component-level test asserting listeners attach only while
isActive and detach on deactivation.
2026-08-16 22:06:16 -07:00
Shannon Sands 67710548ec fix(web): keep the chat terminal input line above the mobile soft keyboard (NS-434)
On mobile the on-screen keyboard overlays the layout viewport instead of
resizing it (iOS Safari always; Android Chrome under its default
interactive-widget=resizes-visual). The dashboard shell is a fixed h-dvh
column, so the xterm host's bounding box never changed when the keyboard
opened: fit() computed identical (cols, rows), no RESIZE reached the PTY,
and the Ink input line — drawn at the bottom of the grid — stayed hidden
under the keyboard.

Fix, in three parts:

1. Keyboard-inset handling (new web/src/lib/keyboard-inset.ts).
   computeKeyboardInset() measures the layout-viewport region obscured by
   the keyboard via window.visualViewport
   (innerHeight - vv.height - vv.offsetTop, with an 80px floor so
   collapsing URL-bar chrome doesn't thrash the grid). ChatPage applies
   it as bottom padding on the terminal wrapper, which shrinks the host →
   the existing ResizeObserver/fit path recomputes rows and sends RESIZE →
   Ink redraws the input line above the keyboard. Listens on both vv
   resize and scroll (offsetTop changes arrive as scroll events on iOS).

2. interactive-widget=resizes-content in the viewport meta. Android
   Chrome 108+ then resizes the layout viewport natively and the JS inset
   computes ~0 (harmless no-op); iOS ignores the directive and takes the
   JS path.

3. Scroll pinning. iOS auto-scrolls the page to reveal xterm's hidden
   textarea on focus, which drags the fixed shell offscreen. While a
   keyboard inset is active we pin window/scrollingElement scroll back to
   0 and term.scrollToBottom() so the freshly-resized input line stays in
   view.

Unit tests cover the inset math (thresholds, offsetTop, rotation races,
fractional geometry, non-finite guards). Grid-level behavior needs a real
device pass — DevTools emulation doesn't model keyboard insets.
2026-08-16 22:06:16 -07:00
fangliquanflq 0b8a09759c fix(sessions): address prune skip review notes 2026-08-16 01:55:25 -07:00
fangliquanflq 29dfbf2d6a fix(sessions): surface open sessions skipped by prune 2026-08-16 01:55:25 -07:00
Teknium fbe4d73051 test: type the WebSocket mock to satisfy docker-build tsc strictness 2026-08-15 01:05:39 -07:00
null-runner 4e6d863d0e fix(dashboard): gate shortcut sends on PTY state
The word-delete branches sent ^W / ESC d whenever the socket was OPEN,
bypassing the shouldBlockPtyInput gate that term.onData applies to
every other keystroke, so a reconnecting/closed session could still
receive shortcut bytes. Route both branches through a shared guarded
sender that applies the identical socket + PTY state check, and cover
the non-open PTY states in the lib tests.
2026-08-15 01:05:39 -07:00
null-runner be250390db fix(tui): restore copy and cut shortcuts 2026-08-15 01:05:39 -07:00
null-runner 2108d4bfda fix(dashboard): paste on Ctrl+V and word-delete on Ctrl+Backspace/Delete
The dashboard chat is an xterm.js terminal in a browser tab, so several
editing keys never reached the input:

- Bare Ctrl+V fell through to the TUI, whose server-side clipboard read
  can't see the browser/OS clipboard, so it reported "No image found in
  clipboard". Route Ctrl+V through the same navigator.clipboard path as
  Ctrl+Shift+V (image-or-text). Fixes #24860.
- Ctrl+Backspace / Ctrl+Delete never sent a word-delete: xterm.js emits
  bare DEL regardless of modifier. Send ^W (0x17) and Alt+d (ESC d) to the
  PTY so readline / prompt_toolkit delete the previous / next word.

Ctrl+W itself stays unavailable in a browser tab: it is a reserved
shortcut (close tab) that preventDefault cannot suppress. Ctrl+Backspace
covers word-delete there; the Electron desktop app can bind Ctrl+W.
2026-08-15 01:05:39 -07:00
Eros 505f289d37 feat(web): add collapse toggle for the chat side panel
The right-hand chat panel (model picker + session list) is a fixed 240px
column on desktop with no way to hide it. Add a collapse button (X) in the
panel header and a floating 'panel' button over the terminal to reopen it,
mirroring the collapsible app sidebar. The choice is persisted in
localStorage (hermes-chat-panel-collapsed) so it survives reloads.

(cherry picked from commit 854f1325a5490bcc968723a82746c939118f6b67)
2026-08-15 00:34:57 -07:00
Denis H 00516e6e8e test(dashboard): cover delayed IME fallback after unrelated input 2026-08-15 00:33:49 -07:00
Denis H 0d40955fd1 fix(dashboard): handle chunked IME composition input 2026-08-15 00:33:49 -07:00
Denis H 434f1e954c fix(dashboard): retain composition after unrelated input 2026-08-15 00:33:49 -07:00
Denis H a428531ef6 fix(dashboard): preserve consecutive IME composition input 2026-08-15 00:33:49 -07:00
Denis H 874ae74a10 fix(dashboard): keep mouse input out of IME fallback 2026-08-15 00:33:49 -07:00
Denis H 3d9fdb2e2b fix(dashboard): avoid duplicate IME fallback input 2026-08-15 00:33:49 -07:00
Denis H c066b5a1d3 test(dashboard): cover IME fallback lifecycle 2026-08-15 00:33:49 -07:00
Denis H 0924e33369 fix(dashboard): defer IME fallback until xterm input settles 2026-08-15 00:33:49 -07:00
Denis H 6ece2da738 fix(dashboard): forward committed dead-key input to PTY 2026-08-15 00:33:49 -07:00
Denis H 278c6ebcb7 test(dashboard): reproduce dropped dead-key composition input 2026-08-15 00:33:49 -07:00
liuhao1024 2af4da45ec fix(dashboard): prevent React from dropping first keystroke during IME composition
React 18's root-level event delegation intercepts keydown events with
keyCode 229 (the "composition in progress" signal sent by the browser
during non-Latin IME input) and synthesises an onCompositionStart event.
That synthetic path sets internal composing state that interferes with
xterm.js's own IME handling on its hidden textarea, causing the first
keystroke of each composition chunk to be silently dropped.

The fix adds a capture-phase keydown listener on the terminal host div
that stops propagation of keyCode-229 events before they reach React's
delegation layer.  xterm.js relies on native compositionstart/
compositionend on its internal textarea — not on keydown — so blocking
the propagation is safe.

Fixes #52111
2026-08-15 00:33:49 -07:00
Teknium 6fb6123f3b test: replace any-casts with typed globalThis narrowing in plugin SDK test 2026-08-15 00:33:32 -07:00