Commit graph

9211 commits

Author SHA1 Message Date
teknium1
95b7ea5e5d feat(cli): /init — generate or update AGENTS.md from a project scan
Cross-surface slash command (CLI, gateway, TUI) following the /learn prompt-injection pattern. Port of Codex /init.
2026-07-26 17:46:29 -07:00
teknium1
40dc36a842 fix(deps): converge huggingface-hub on one exact version (1.24.0) across lock and lazy pin
hermes update's lazy-refresh pass re-asserts LAZY_DEPS pins whenever the
package is present (active_features() is presence-based). The
tool.trace_upload pin huggingface-hub==1.2.3 sat below transformers'
>=1.5.0,<2 requirement, so every update force-downgraded the shared
package and broke Hindsight local embeddings on daemon startup (#60783).

Keep the exact-pin security posture — no ranges — but move the pin to
1.24.0 (current) and bump uv.lock in lockstep (uv lock --upgrade-package
huggingface-hub: hub 1.4.1->1.24.0, hf-xet 1.3.1->1.5.2, click
8.3.1->8.4.2, drops typer-slim), so the entire tree converges on ONE hub
version. The refresh pass now reports 'current' with zero churn.

Invariant tests (not snapshots): the lazy pin must equal the uv.lock
resolved version, and must sit inside transformers' accepted window.
HfApi surface used by trace upload (whoami/create_repo/upload_file)
verified present with identical kwargs on 1.24.0 in a live venv.
2026-07-26 17:24:31 -07:00
izumi0uu
35a9b3a7c2 fix(docker): ship a WAL-reset-safe SQLite runtime
Compile and checksum-pin SQLite 3.53.4 in the published image, preserve Hermes' required SQLite features, and assert the final Python linkage plus FTS5 trigram behavior during image builds.\n\nMake doctor remediation install-aware so Docker users pull and recreate every Hermes container instead of running the inapplicable git updater.\n\nFixes #70480
2026-07-26 17:20:31 -07:00
teknium1
c476ad35ae fix(update): refresh stale managed-uv catalog so SQLite runtime repair can succeed
The managed uv is installed with UV_UNMANAGED_INSTALL, which disables
'uv self update' by design — the swallowed failure left its embedded
python-build-standalone catalog frozen at bootstrap age forever.
python-build-standalone re-releases existing patch versions with fixed
SQLite (3.11.15 was re-cut with 3.53.1), so a stale catalog resolves
the same version number to the OLD vulnerable build, the probe rejects
it, and the patch-retry loop cannot recover because the fixed build
carries no newer number to try. Result: 'hermes update' printed a
guaranteed-failure provisioning warning on every run (issue #72093).

- When provisioning fails, re-bootstrap the Hermes-managed uv binary
  via the official installer (the only supported refresh for unmanaged
  installs) and retry provisioning once — only when the binary version
  actually changed, so no wasted download cycles.
- Never touch a caller-supplied uv outside the managed path.
- Soften the failure report from alarming ⚠ to informational ℹ and say
  why it is safe to wait: the WAL gate keeps databases out of WAL on
  vulnerable builds, and the next update retries.

Verified: 56 unit tests green; sabotage run (retry block removed) fails
the 3 new retry tests; live E2E replaced a fake managed uv via the real
astral installer and the refreshed binary resolved the 3.11 catalog.

Fixes #72093
2026-07-26 17:20:27 -07:00
Kevin Haddock
a75ec9278c fix(model): track explicit models: declarations in section 3 so a singular default_model doesn't suppress live discovery
A providers: entry with only a default_model/model (no explicit models:
list) is un-narrowed — the singular field is just the active selection.
Section 3 derived has_explicit_models from the merged models list, so
the lone default_model entry counted as an explicit catalog and
suppressed the /v1/models probe for no-key endpoints, leaving a
one-line /model picker menu for local llama.cpp/Ollama/vLLM servers.

Track explicit models: declarations separately at group-build time
(mirrors section 4's declaration-tracking from #40542 / PR #61928) and
gate the probe on that instead.

Salvaged from PR #68984 by @vigilancetech-com (the probe_custom_providers
gate removal in that PR is not taken — the GUI no-probe gate is
intentional).
2026-07-26 17:17:03 -07:00
teknium1
0f554ce19e test(gateway): drop source-reading guard test from #71671 salvage
Source-regex tests are banned (AGENTS.md 'Never read source code in
tests') — keep only the behavioral regression test.
2026-07-26 17:15:38 -07:00
hereicq
12fdaeaf1a test(gateway): trim docstrings 2026-07-26 17:15:38 -07:00
teknium1
b792bd0529 feat(delegation): structured stall metadata + live per-child status in /agents
Completes #51690 on top of the salvaged #60378 timeout metadata:

- async_delegation: terminal 'stalled' events now carry structured
  stall context (stalled_after_quiet_seconds, stall_threshold_seconds,
  stall_phase idle|in_tool, stall_grace_seconds) on both single and
  batch paths, persisted in the durable row so restart-restored events
  keep it. Mirrors the sync path's timeout_seconds/timed_out_after_
  seconds/timeout_phase from #60378.
- list_async_delegations(): exposes seconds_since_progress and live
  children_activity (per-child api_calls, current_tool,
  seconds_since_activity) sampled from the dispatch's progress_fn
  outside the records lock; private monitor bookkeeping and callables
  never leak.
- /agents (CLI + gateway): background delegations render per-child
  activity rows, quiet-time hints, and the stalling state; gateway
  section is new (previously async delegations were invisible there).
  New locale key gateway.agents.background_delegations in all 17
  catalogs.

Tests: stall-metadata event shape, live-listing projection, gateway
/agents rendering (real registry dispatch, sabotage-verified), sync
timeout metadata fields, non-timeout None contract.
2026-07-26 17:13:52 -07:00
teknium1
136f8dab67 refactor(gateway): promote autonomous silence matcher to shared response_filters helper
Follow-up to the salvaged #71756: instead of webhook importing cron's
private _is_cron_silence_response, the loose autonomous-lane matcher now
lives in gateway/response_filters.py as is_autonomous_silence_response,
sharing LIVE_GATEWAY_SILENT_MARKERS with the interactive exact-marker
rule so the marker sets can never drift. Cron and webhook both delegate
to it. Interactive gateway behavior unchanged.
2026-07-26 16:55:42 -07:00
Gumclaw
55d3272286 fix(webhook): honor [SILENT] when the agent explains its own silence
A webhook route that answered `[SILENT]` still delivered, whenever the model
added a sentence saying why it was staying quiet:

    [SILENT]

    The new inbound was the same email quoted back a second time, on a ticket
    we already answered. Nothing new to reply to, so I closed it.

Webhook subscription prompts tell the agent to answer `[SILENT]` on a tick that
produced no story — a duplicate inbound, a stand-down because a sibling lane
already replied, a routine close. Nobody is waiting on the other end of a
webhook, so a "nothing happened" message has no reader.

Delivery went through the live gateway's `is_intentional_silence_response`,
which requires the response to be EXACTLY a marker. That rule is right for an
interactive chat: swallowing a real answer because it opens with a marker is
much worse than showing a stray marker. It is the wrong trade for an autonomous
lane, where a leaked non-story is a pointless notification on every tick and
models reliably append the explanation that flips the check back to "deliver".
Cron already resolved this the other way — `cron/scheduler.py` treats a marker
on its own first or last line as silence — so the two autonomous lanes
disagreed while the interactive path was fine.

Suppress in `WebhookAdapter.send`, before the deliver-type switch, so every
route (log, github_comment, cross-platform) behaves the same. Reuses cron's
`_is_cron_silence_response` rather than restating the rule, so the two lanes
cannot drift; prose that merely mentions a marker mid-sentence still delivers.
The interactive gateway path is untouched.

Tests: six cases in tests/gateway/test_webhook_adapter.py — bare marker,
marker + trailing prose (the reported shape), marker on the last line, a real
report, a report quoting a marker mid-sentence, and a `log` route. Verified
red-first: with the suppression removed the three silence cases fail
("Expected send to not have been awaited") while the three delivery cases still
pass, so the tests assert the fix rather than the framework.
2026-07-26 16:55:42 -07:00
HexLab98
aff48958d3 fix(deepseek): drop retired models from picker and provider defaults
Stop offering deepseek-chat/reasoner in the static catalog and point
fallback/aux defaults at the permanent v4 IDs. Keep retired aliases in
a detection-only map so /model deepseek-chat still resolves to deepseek.
2026-07-26 16:28:41 -07:00
HexLab98
cc7c418b33 fix(deepseek): remap retired chat/reasoner aliases to v4-flash
DeepSeek cut off deepseek-chat and deepseek-reasoner on 2026-07-24.
Sending those IDs now returns HTTP 400; rewrite them (and fuzzy
reasoner names) to deepseek-v4-flash so saved configs keep working.
2026-07-26 16:28:41 -07:00
trymhaak
148497f6d6 fix(kanban): strip stale session routing from dispatched worker env
A long-lived gateway can have platform routing (HERMES_SESSION_* /
HERMES_CRON_AUTO_DELIVER_*) mirrored in os.environ from a previous turn.
_default_spawn() copied that process environment verbatim into detached
kanban workers, so a worker calling kanban_create treated the inherited
chat/topic as its origin and auto-subscribed the child task — the task's
terminal notification then woke an unrelated chat.

Strip every registered session-context routing key from the worker env
unconditionally (the dispatcher is detached from every conversation);
board, workspace, task, branch, profile, model, and credential
propagation are unchanged.

Salvaged from PR #69181 (both commits squashed; the PR's second commit
fixed the first's engagement-latch assumption).
2026-07-26 16:27:52 -07:00
Salim
c93ed07459 fix(kanban): route active named profile through the active adapter map
A gateway running under a named active profile (e.g. `hermes -p main gateway`)
stamps kanban auto-subscriptions with notifier_profile=main, but
_authorization_adapter() treated any name other than the literal "default"
as a multiplex secondary and consulted only _profile_adapters — empty on
standalone gateway-per-profile deployments. The helper failed closed, the
notifier rewound the claim, and the notification was silently retried
forever (#71340).

Recognize the gateway's own active profile name as primary so its stamped
subscriptions resolve via self.adapters; genuinely secondary profiles keep
the fail-closed lookup.

Salvaged from PR #62380 (the unrelated blocked-reason truncation change is
intentionally not taken).
2026-07-26 16:27:52 -07:00
srojk34
dda6f0f63e fix(kanban): widen notifier pre-filter to secondary-profile platforms
_collect()'s active_platforms pre-filter was derived solely from
self.adapters (the default profile), so a subscription owned by a
secondary profile on a platform the default profile never connected
(e.g. beta owns discord, default has no discord adapter at all) was
skipped before claim_unseen_events_for_sub ever ran. Unlike the
disconnected-adapter path, an unclaimed event is never rewound, so this
was a permanent, silent notification/wake loss — directly contradicting
the point of routing notifications via the owning profile
(c69643026/b225b30d0). Same cross-profile-adapter-lookup bug class the
delivery-side _authorization_adapter chokepoint already guards against,
one gate earlier. The precise per-profile check still runs unchanged at
delivery time, with its existing rewind-on-None safety net.
2026-07-26 16:27:52 -07:00
tymrtn
67826e3068 fix: scope kanban auto-subscriptions to active profile 2026-07-26 16:27:52 -07:00
teknium1
f34a69b1cd test(tui): prove agent build installs the selected profile's secret scope
Sabotage-verified: fails when the set_secret_scope call in
_start_agent_build is removed.
2026-07-26 16:19:52 -07:00
Gille
b93fd077c0 fix(session-search): allow scrolling compacted lineage history 2026-07-26 16:18:31 -07:00
teknium1
5f5afb1eef fix(delegation): count streamed tokens as liveness in the stale monitor
Include last_activity_ts in the progress token sampled from each child.
_touch_activity ticks on every streamed chunk ('receiving stream
response'), every tool transition, and API-call start/completion — so a
child mid-stream on a long response is alive even though api_call_count
only advances when the call completes. Same liveness signal as the
compaction inactivity budget (PR #71508): if tokens are flowing it never
dies; staleness is measured from the last streamed token / tool activity
/ API call.
2026-07-26 16:15:29 -07:00
teknium1
99a381f310 fix(delegation): progress-based stale detection for detached async runners
Replace the wall-clock timeout watchdog (from #60234) with progress-based
staleness detection, on by default with zero config:

- The async registry now accepts a progress_fn per dispatch; delegate_task
  wires a sampler over the batch's child agents (api_call_count +
  current_tool from get_activity_summary()).
- A single monitor thread sweeps running delegations: a child whose
  progress token keeps advancing is never touched, no matter how long it
  runs. A frozen token past the stale threshold (450s idle / 1200s
  in-tool, mirroring the sync-path heartbeat monitor) marks the record
  'stalling' and interrupts the child.
- A stalling child that unwinds within the grace window (120s) finalizes
  through the NORMAL path, preserving its partial results. One that never
  returns is force-finalized with a terminal 'stalled' completion event so
  the owning session hears an outcome and the async slot frees.
- Late runner returns after force-finalization are deduped by the
  begin/push/finish finalization split (kept from #60234).

Why not a timeout: delegation.child_timeout_seconds defaults to 0 by
deliberate design (DEFAULT_CHILD_TIMEOUT rationale) — a timeout-based
watchdog never arms for default configs, leaving the reported silent-
profile symptom (#60203) unfixed, and when armed it kills legitimately
slow heavy subagents mid-task. Progress detection distinguishes 'wedged
at first API call' from 'grinding through a 2h review'.

Builds on izumi0uu's finalization-atomicity work from #60234.
2026-07-26 16:15:29 -07:00
izumi0uu
65420cdecd fix(delegation): timeout stuck async child runners
Async background delegation can leave gateway sessions holding only a dispatched handle when the detached runner wedges before it can return and enqueue a completion. Enforce the configured child timeout in the async registry so the parent observes a terminal timeout event and the async slot is released.

Constraint: Issue #60203 reports long-lived gateway processes with background child delegates that never produce completion events despite child_timeout_seconds being configured.

Rejected: Relying only on _run_single_child timeout handling | it cannot finalize the async registry when the outer runner thread itself never reaches normal completion.

Confidence: high

Scope-risk: narrow

Directive: Keep background delegation completion owned by the async registry whenever detached workers can outlive the caller's immediate control.

Tested: .venv/bin/python -m pytest tests/tools/test_async_delegation.py tests/tools/test_delegate_subagent_timeout_diagnostic.py tests/tools/test_delegate.py -q

Tested: .venv/bin/python -m ruff check tools/async_delegation.py tools/delegate_tool.py tests/tools/test_async_delegation.py

Tested: git diff --check

Not-tested: Multi-day real gateway degradation; covered with deterministic stuck-runner registry tests.
2026-07-26 16:15:29 -07:00
teknium1
a0bc7d5572 fix(kanban): snap notify-sub cursor to current MAX(task_events.id) at creation
Fixes the boot-storm half of issue #29905: kanban_notify_subs.last_event_id
defaulted to 0, so a subscription created on an already-active task replayed
the task's ENTIRE terminal-event backlog on the next notifier tick. With
many stale subs (27 observed in the report) a gateway boot after downtime
burst 100+ notifications in one go.

add_notify_sub now snaps the cursor to COALESCE(MAX(task_events.id), 0) for
the task inside the same INSERT, so new subscriptions start caught up and
only receive events that occur AFTER subscribing. The gateway slash-command
and kanban-tool auto-subscribe paths run at task creation, where the
snapshot is just the 'created' event — behavior there is unchanged.

Stale fixtures that asserted the literal 0 creation cursor now assert
'cursor unchanged/unclaimed' instead, which is what they actually meant.
2026-07-26 16:14:15 -07:00
teknium1
a945364ba2 test(gateway): harden notifier isolation regression + block_loop_detected e2e coverage
Follow-ups from review of salvaged PRs #59278 and #62712:

* test_kanban_notifier_isolates_per_subscription_failure previously
  created the good subscription first; list_notify_subs() has no
  ORDER BY, so the good delivery happened before the bad claim raised
  and the test passed even without the isolation fix. The bad task is
  now created first AND a deterministic-order shim forces the failing
  subscription to be iterated first, so the test fails on the old
  whole-tick-abort behavior.

* New test_notifier_delivers_block_loop_detected_triage_ping: drives a
  block_loop_detected event through one notifier tick end-to-end,
  asserting the triage ping reaches the adapter and the cursor advances
  (the sweeper review of #62712 flagged that only DB-level emission was
  tested).
2026-07-26 16:14:15 -07:00
David Beyer
0b632f772a fix(gateway): zero-sub early exit for kanban notifier board polling
Salvaged from PR #63001 (reduced scope): probe each board with the new
read-only kanban_db.count_notify_subs() before the writable connect(),
so boards with zero subscriptions are never opened writable on the 5s
notifier tick (no schema migration, no WAL/-shm sidecar churn, no
checkpoints).

The PR's machine-global .notifier.lock singleton gate was deliberately
NOT salvaged: a lock-winning default-profile gateway cannot deliver a
secondary profile's subscriptions in standalone-profile deployments
(profile routing fails closed in _authorization_adapter), so the lock
could suppress delivery entirely. The probe captures the per-tick cost
win without that regression.
2026-07-26 16:14:15 -07:00
rhylryan21
4436eacebf fix(gateway): kanban notifier delivery reliability
- honor SendResult(success=False) instead of discarding it, so an adapter
  that REPORTS (not raises) a soft send failure — e.g. the Telegram adapter's
  "Not connected" mid-reconnect — no longer advances the cursor past an
  undelivered event and silently loses the notification. Addresses the
  notifier half of #31901.
- add block_loop_detected to the notifier's TERMINAL_KINDS so a task routed to
  triage for a human decision (re-blocked past the recurrence limit) actually
  pings its subscribers instead of stalling silently.
- raise MAX_SEND_FAILURES 3 -> 12 (~60s at the 5s tick) so a transient
  Telegram/API outage does not permanently unsubscribe a live channel now that
  reported soft-failures also reach this counter.
- route active-profile-stamped subscriptions via the primary adapter on a
  single-profile gateway (self.adapters[platform] when the stamped
  notifier_profile equals the active profile). Related to #56802.

Adds test_kanban_notifier_rewinds_claim_on_reported_send_failure asserting a
reported send failure leaves the event unseen (rewound) rather than consumed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-26 16:14:15 -07:00
AlexFucuson9
f6ccfa6bd3 fix(gateway): isolate per-subscription failures in kanban notifier
The kanban notifier _collect() loop iterates subscriptions without
per-subscription error handling. When claim_unseen_events_for_sub raises
for one subscription (e.g. DB corruption, lock contention), the entire
tick aborts — silently blocking delivery for ALL other subscriptions.

Wrap the per-subscription logic in try/except so one bad subscription
logs a warning and continues to the next, instead of jamming the
entire notifier.

Closes #59269
2026-07-26 16:14:15 -07:00
yuexiong
83dc0b9b85 fix(install): clear stale Windows cua lock 2026-07-26 16:01:54 -07:00
yuexiong
23d0dca832 fix(install): reap Windows cua installer process tree 2026-07-26 16:01:54 -07:00
embwl0x
f174a08c93 fix(gateway): let internal events bypass topic lobby 2026-07-26 16:01:32 -07:00
embwl0x
1bdec6f065 fix(kanban): preserve telegram dm topic metadata 2026-07-26 16:01:32 -07:00
brooklyn!
48bdde1deb
Merge pull request #72230 from NousResearch/bb/bootstrap-marker-triage
fix: make the bootstrap-complete marker consistent across every install path
2026-07-26 17:48:57 -05:00
teknium1
6d1e08b2bc fix(dashboard): QA pass — log colors, nameless channels, config bool, UX gaps
Companion fixes from a full dashboard QA pass (every page dogfooded
live), on top of the cherry-picked #31863 header-slot fix:

- ChatPage: harden the header-slot effect further — useLayoutEffect and
  never write the slot while inactive, so the handoff commentary and
  ownership rule live next to the code.
- LogsPage: level classification used raw substring matching, so INFO
  lines carrying 'parse_errors=0' (or paths like errors.log) rendered
  red. New unit-tested classifier (web/src/lib/log-classify.ts) anchors
  on the hermes_logging level token with a word-boundary fallback.
- Channels API: plugin platforms (irc, ntfy, photon, teams, …) rendered
  as nameless title-cased cards ('Irc', 'Ntfy') with empty descriptions.
  Two root causes: (1) plugin discovery never ran in the dashboard
  server process, so plugin_entries() was empty; (2) Platform enum
  pseudo-members claimed plugin ids before the registry could attach
  labels. The catalog now discovers plugins explicitly and resolves
  plugin metadata first; added descriptions + docs links for bundled
  plugin platforms and the msgraph_webhook / whatsapp_cloud / relay
  enum members. Regression test sabotage-verified against the old
  enum-first ordering.
- Config schema: updates.refresh_cua_driver declared type 'bool'
  (schema vocabulary is 'boolean'), so the switch rendered as a text
  input holding 'true'.
- Page titles: '/mcp' rendered as 'Mcp' via the naive capitalize
  fallback; literal-label table now covers MCP/Files/Channels/Webhooks/
  Pairing/System (unit-tested).
- AuthWidget: skip the guaranteed-401 /api/auth/me probe in loopback
  mode — every dashboard load logged a console error for nothing.
- Model picker: with no filter, providers that actually have models
  float above the wall of '0 models' rows.
- Cron: empty state now carries an actionable Create button.
2026-07-26 15:17:30 -07:00
xd-Neji
7b662c8d72 fix(kanban): inherit notify subs for child tasks
Copy gateway notification subscriptions from parent tasks to child tasks created by create_task(..., parents=...), link_tasks(), and decompose_triage_task().

Inherited subscriptions start at the child's current event cursor, so linking an existing child does not replay pre-link task events.
2026-07-26 14:31:47 -07:00
brooklyn!
79b4a40568
Merge pull request #72220 from NousResearch/bb/tool-row-dedupe
fix(tools): stop the inline tool row stuttering its verb and repeating the command
2026-07-26 16:25:50 -05:00
teknium1
af8d698b41 fix(mcp): propagate profile HERMES_HOME override into shared discovery owner; restore stdio startup + WSTransport tests
Follow-up to @LionGateOS's #72135 salvage:
- Route ensure_mcp_discovery_started through hermes_cli.mcp_startup's
  shared owner instead of a hand-rolled bare thread, keeping the start
  lock, retry-after-zero-connected allowance, and interactive-OAuth
  suppression. The shared owner now captures the caller's context-local
  HERMES_HOME override and re-installs it inside the discovery thread,
  so discovery reads the selected profile's mcp_servers (#67605).
- Restore stdio TUI startup discovery in main() and the
  _mcp_discovery_enabled retry gate in wait_for_mcp_discovery, both
  dropped by the original branch.
- Restore the 3 WSTransport regression tests (send serialization,
  cross-batch ordering, drained-token ordering) deleted by the PR.
- Harden the profile-scoped discovery test against sibling-state leaks.
2026-07-26 14:14:03 -07:00
LionGateOS
a7bb123b4a fix(tui-gateway): scope verification and MCP discovery to active profile 2026-07-26 14:14:03 -07:00
LionGateOS
c6e811e48f fix(tui-gateway): scope MCP discovery to active profile 2026-07-26 14:14:03 -07:00
LionGateOS
830ff5967a fix(tui-gateway): start MCP discovery for websocket sessions 2026-07-26 14:14:03 -07:00
konsisumer
486c5ffc8d fix(cli): live-probe dashboard status and share update cleanup across paths
- hermes dashboard --status now verifies each matched PID is alive AND
  bound to a listening socket before reporting it, so stale PIDs and the
  desktop app's IPC-only 'serve --port 0' backends no longer masquerade
  as running dashboards (#58578).
- The git and Windows ZIP update paths share one
  _finish_dashboard_update_cleanup(), so the ZIP fallback gets the same
  stop/restart reporting.
- _kill_stale_dashboard_processes returns a structured
  {matched, killed, failed, unrecovered} result; the explicit was-stopped
  notice fires only for processes that could NOT be auto-restarted,
  meshing with the auto-respawn from #72192.
2026-07-26 14:11:05 -07:00
Brooklyn Nicholson
2b0b5e4c53 fix(install): stamp the bootstrap-complete marker from install.sh too
install.ps1 wrote the marker on Windows and the Rust installer now writes it,
but install.sh -- the path every Mac and Linux CLI install takes -- never did.
A machine set up with install.sh therefore looked uninstalled to the desktop
app, which re-ran first-run bootstrap on every launch.

Stamp the same schema-v1 payload install.ps1 writes, from both the staged
`complete` stage and monolithic main(). An unresolvable HEAD skips the marker
rather than writing one the desktop validator rejects: absent reads as a clean
"bootstrap needed", malformed reads as a confusing half-state.
2026-07-26 16:06:50 -05:00
Brooklyn Nicholson
f0031abc39 fix(gateway): send a raw arg preview on tool.start, not a phrased label
_tool_ctx switched to build_tool_label in #55166, so every tool.start
carried an already-phrased string ("Running sleep 70 + 2 commands").
Both clients then apply their own verb on top: the TUI renders
Terminal("Running sleep 70 + 2 commands") and the desktop row reads
"Ran Running sleep 70 + 2 commands". The friendly labels stay where they
belong — the CLI spinner and the gateway progress line, which compose
verb + preview at their own call sites.
2026-07-26 16:03:49 -05:00
teknium1
920facdc4c fix(update): never blind-reinstall cua-driver during hermes update
'Refreshing cua-driver (Computer Use)...' could hang for minutes on
Windows: when the driver's native check-update verb returned an
indeterminate result (old driver without the verb, offline, GitHub
rate-limited, or the probe timing out), install_cua_driver(upgrade=True)
fell through to the full upstream installer — a silent, output-captured
run with a 660s ceiling, plus install.ps1's 600s concurrency-lock wait
on Windows on top. Every 'hermes update' paid that cost.

Two changes:

- install_cua_driver() grows require_confirmed_update: with it set, an
  indeterminate check keeps the installed version and returns fast,
  printing the force path (hermes computer-use install --upgrade).
  'hermes update' passes it; the explicit --upgrade CLI keeps the old
  fall-through so a force refresh still works when the check can't
  answer.
- cua_driver_update_check() default timeout is now 25s on Windows
  (8s unchanged on POSIX): first-spawn of the exe under Defender /
  SmartScreen routinely exceeds 8s, and a false timeout is exactly the
  indeterminate result that used to trigger the multi-minute reinstall.
2026-07-26 13:47:35 -07:00
teknium1
bd03960071 test(update): cover supervised-unit restart and manual argv respawn
11 new tests: owning-unit restart + dedupe + failure hint (#68934),
argv capture/respawn + --no-open + failure fallback (#40449),
/proc and ps cmdline capture, --stop never restarts.
All fail without the fix; 26 pre-existing tests unchanged.
2026-07-26 13:42:47 -07:00
teknium1
2a2ae3bca1 test(kanban): assert DM wake resumes the creator's real DM session key
Strengthen the salvaged regression test to prove the end-to-end claim in
#56580/#68874: a DM-created task's terminal wake must build the creator's
':dm:<chat_id>' session key via build_session_key(), not a group-scoped
key that forks a fresh session. Sabotage-verified: reverting the watcher
to the hardcoded chat_type='group' fails this test.
2026-07-26 13:42:33 -07:00
lijun
a417c6e08d fix(gateway): preserve kanban notifier chat type 2026-07-26 13:42:33 -07:00
MaxFreedomPollard
aa636c6fca fix(config): merge duplicate kanban block so auto_subscribe_on_create default survives
DEFAULT_CONFIG declared "kanban" twice. Python keeps only the last
literal for a duplicate key, so the first kanban block was silently
dropped and its "auto_subscribe_on_create": True default never made it
into DEFAULT_CONFIG. The consumer in tools/kanban_tools.py masks the
miss with cfg_get(..., default=True), but the documented default was
absent from DEFAULT_CONFIG (so config templates / 'hermes config show'
omit it), and the duplicate key is a standing hazard: any future key
added to the first block would also vanish.

Merge auto_subscribe_on_create into the single canonical kanban block.

Adds a regression test asserting both default sets survive and a guard
against any duplicate top-level DEFAULT_CONFIG key.
2026-07-26 13:42:22 -07:00
teknium1
13590ce685 fix(gateway): GIS extensions, spaced MEDIA paths, and code-block-safe display strip
Follow-up wave to #72170 resolving the remaining open MEDIA-delivery gaps:

- #24032: add .kmz/.kml/.geojson/.gpx to MEDIA_DELIVERY_EXTS, and recover
  unknown-extension paths containing spaces via _match_extensionless_path —
  validation-gated forward extension across single spaces (bounded at 8
  tokens, stops at newline / next MEDIA: keyword). The regex itself stays
  non-greedy and whitespace-bounded so the #68773 absorption bug class
  cannot return; the on-disk file check is the oracle.

- #16434 (streaming half): _strip_media_tag_directives now uses the same
  mask-as-locator pattern as extract_media, so MEDIA tags inside fenced
  code blocks, inline-code examples, and JSON string values survive in
  streamed display text instead of being mangled. Display and delivery
  now agree on every protected-span rule.

- Updated three stream_consumer display expectations that pinned the old
  inconsistent behavior (backtick/double-quote tags stripped from display
  while delivery never attempted them).
2026-07-26 13:41:53 -07:00
Siddharth Balyan
59529afee0
feat(billing): carry the payment-method union through to clients (#71542)
* feat(billing): add payment_method union to the billing-state wire type

* feat(billing): carry the payment-method union through the gateway

NAS now sends a typed `paymentMethod` union on /api/billing/state alongside
the legacy `card` field. The gateway parses payloads field-by-field, so the
new field was dropped on the floor before reaching TUI/Desktop.

Parse it into PaymentMethodInfo and re-emit it as snake_case `payment_method`,
matching the translation the rest of this payload already does. The payment
method id is deliberately not carried through — clients have no use for it.

No client rendering changes: `card` stays populated for cards, so every
existing consumer behaves exactly as before and the new field is inert until
a surface opts into reading it.

* fix(billing): send only the fields each payment-method kind declares

The serializer emitted every key for every kind, so a Link method went out
carrying brand, last4 and wallet set to null. That contradicts the shared
type, where each kind declares its own fields: a client testing `'brand' in
pm` would read every Link method as a card, and one trusting the declared
non-null `brand` could crash on it.

Send each kind's own fields, and forward an unrecognized kind by name alone
so a client that predates it can still say something honest. The shared type
gains the matching fallback arm its own comment already promised.

Tests now follow a payload from the server response through to the client
wire for each kind, rather than checking parsing and serializing separately —
which is why the old expectation locked in the wrong shape without noticing.

* fix(billing): keep the payment-method kind narrowable

Typing the fallback arm's kind as `string & {}` borrowed a trick that only
works on unions of plain strings. On a union of objects it makes the
discriminant non-literal, so TypeScript stops narrowing on every arm — even
`if (pm.kind === 'card')` no longer gives you `brand`. The first client to
use this would have hit a compile error and reached for a cast.

An unrecognised kind now arrives as `unknown`, carrying the real name
alongside it, so every arm has a literal discriminant. A type-level test
pins this: it fails to compile if the discriminant stops narrowing.

The parser settles which kind it is, the way the card parser already does,
so the record cannot hold fields that do not belong to its kind and the
serializer no longer re-checks. The type comment also stops claiming `card`
is a safe signal — it is null for Link, so `!card` does not mean "nothing on
file".
2026-07-27 01:49:43 +05:30
teknium1
588b7059a8 test(tui): prove kanban poller reads the shared board under a profile override
The sweeper review on #66435 flagged that the collector doesn't bind
session["profile_home"]. That binding is intentionally unnecessary: the
kanban board is shared across profiles by design — kanban_home() anchors
on get_default_hermes_root(), which resolves the process env and ignores
context-local profile overrides (see the kanban_db.py module docstring).
Add a regression test that claims a subscription while a foreign-profile
set_hermes_home_override() is active, proving delivery still works for
non-launch-profile Desktop sessions.
2026-07-26 12:54:51 -07:00
falkoro
6247712c3f test(tui): drive a real subscription through _notification_poller_loop
Covers the poller wiring above _collect_kanban_notifications, per the
hermes-sweeper review: status.update emission, agent-turn dispatch via
_run_prompt_submit when the session is idle, and the busy-session
pending buffer that flushes once the session goes idle.
2026-07-26 12:54:51 -07:00