Commit Graph

28243 Commits

Author SHA1 Message Date
Teknium afee35700e fix(delegation): suppress subagent-owned process notifications in parent chat by default
Background processes started by subagents (task_id sa-*) route their
notify_on_complete / watch_pattern notifications to the parent
conversation (b95ec1cb5) because anything outliving the child needs a
durable consumer. In practice these 'npm ci finished' walls are noise
mid-conversation — the child's consolidated delegation result is the
deliverable.

- New config key delegation.surface_child_process_notifications
  (default false = suppress). Flag true restores the previous behavior
  exactly (delivery with subagent attribution line).
- drain_notifications drops (never requeues) completion/watch_match/
  watch_disabled events whose task_id starts with 'sa-' when the flag
  is false, logging at debug with session_id+task_id for diagnosis.
  Requeueing would pin them forever — children never drain notifies.
- async_delegation events are NEVER suppressed (they ARE the result).
- watch_disabled emitters now carry task_id so sa- sessions' safety
  events follow the same suppression as their other events.
- Config read errors fall back to the default (suppress) and never
  crash the drain loop.
- Docs: delegation.md + configuration.md.
2026-08-25 21:59:04 -07:00
Brooklyn Nicholson b742be711a fix(desktop): skip Tip delay when moving between adjacent chrome
First hover still waits 200ms so a sweep does not flash a trail. After
a tip has opened, the next trigger within 300ms opens instantly.
2026-08-25 22:16:24 -05:00
Teknium f751a8c546 fix(update): also defer the missing-binary CUA install on Windows
Follow-up to the salvaged #94296: the two guards covered the repair and
confirmed-update branches, but when cua-driver is enabled yet not
installed at all, control still reached _run_cua_driver_installer() and
an automatic 'hermes update' would launch the interactive install.ps1
anyway. Add the same defer before the installer run, keep POSIX
behavior unchanged, and give the confirmed-update message a natural
fallback when latest_version is unknown.
2026-08-25 16:53:01 -07:00
Royalaid 0c23bf19af fix(update): defer interactive CUA installs on Windows 2026-08-25 16:53:01 -07:00
Teknium a935154378 test(tool-search): make salvage-seam tests order- and stem-robust
Two interaction seams between the #92693 salvage (merged as #95050) and
this branch: the source-label indexing test now compares in token space
(the stemmer shortens 'catalogsource' to 'catalogsourc'), and the
unregistered-core-name describe test forces the unregistered condition
via monkeypatch instead of depending on which sibling test file imported
model_tools first.
2026-08-25 16:39:12 -07:00
alt-glitch 00305aec61 test(tool-search): kill the shared-stemmer mutant with cache-missing input
The parallel determinism test warms _stem's lru_cache after ~11 distinct
stems, so almost no iterations reach the underlying stemmer and a shared
(non-thread-local) instance survives it. New test bypasses the cache with
per-iteration unique tokens via _stem.__wrapped__, so thousands of stems
run concurrently: a shared stemmer's mutable parse state fails it within
2,000 calls (verified — the mutant dies 8/8 runs; healthy runs stay green).
2026-08-25 16:39:12 -07:00
alt-glitch 6d37f2b78a test(tool-search): exercise stemmer in parallel 2026-08-25 16:39:12 -07:00
alt-glitch a667ab60d9 fix(livetest): render multi-query bridge calls 2026-08-25 16:39:12 -07:00
alt-glitch 3b065745ca refactor(tool-search): keep batch caps internal 2026-08-25 16:39:12 -07:00
alt-glitch e35a7bddee perf(tool-search): cache stems and bound result metadata 2026-08-25 16:39:12 -07:00
alt-glitch b09f617722 fix(tool-describe): separate missing and direct names 2026-08-25 16:39:12 -07:00
alt-glitch 1e86a263ed fix(tool-search): preserve exact and per-query ranking semantics 2026-08-25 16:39:12 -07:00
alt-glitch e455e4afd0 feat(tool-search): multi-query search, batched describe, Snowball stemming
tool_search now takes queries: string[] (searched independently against
the same catalog, limit applies per query, default 5 / max 25) and
returns the split shape: per-query groups carry tool names only, one
shared tools map holds each matched tool's source, description (400-char
cap) and required parameter names once. When some queries miss, a single
top-level available_sources + hint block replaces the old per-response
fallback.

tool_describe now takes names: string[] and returns a map keyed by name;
unknown names collect in not_found (with the refresh hint) and
non-deferrable names keep their per-name spelling-check error in errors,
so one bad name no longer fails the whole call. Duplicates dedupe
silently.

The shared tokenizer now applies Snowball stemming (english, exact-pinned
snowballstemmer) at both index and query time, closing the measured
plural/singular miss where 'issues' failed to return create_issue. The
inline BM25 is unchanged. Stemmer instances are thread-local (they carry
mutable parse state and bridge dispatch can run on parallel tool-call
threads).

New config knobs under tools.tool_search: max_queries / max_describe_names
(default 10 each, floor 1, no upper clamp) bound the per-call array
inputs; over-cap calls error so the model repairs in one round-trip.

No backward compatibility with the single query/name shapes, by decision.
scripts/analyze_livetest.py renders both shapes since transcripts on disk
may predate this change.
2026-08-25 16:39:12 -07:00
BrunoBza 250faa69fb fix(desktop): keep the Cronjobs pane subscribed to roster hydration
RoutinesPane resolved its cron owner from a bare $lastRoster.get()
snapshot. BotsHomeView owns the roster fetch, so whenever the pane
mounted before that fetch landed (fresh boot ordering, renderer reload
resetting the atoms) it captured an empty roster forever: the pane
stayed pinned on "Cronjobs are unavailable until this agent appears in
the roster." and Create Cronjob silently no-oped until some unrelated
atom happened to re-render it (#94483).

Subscribe via useValue($lastRoster) instead, matching every other
consumer of the shared roster. Scoping intent is unchanged: a complete
focused owner without an exact roster row still fails closed rather
than routing cron reads/mutations through a stale selection or an
unscoped profile name (contracts in routines-selected-bot.test.mjs).

The source contract in focused-bot-highlight.test.mjs pinned the bare
.get() shape; it now pins the subscription form while keeping the
socket-home-atom prohibition that motivated it.

Fixes #94483
2026-08-25 16:34:12 -07:00
hermes-seaeye[bot] 1a5ca62233 fmt(js): npm run fix on merge (#95114)
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-08-25 23:31:59 +00:00
Teknium 903354587a test: pin CreateRoutineDialog owner-object label resolution (follow-up for salvaged #93572) 2026-08-25 16:31:02 -07:00
zhangfei1231231-sketch b73e057855 fix(hermes-bots): stop New Cronjob dialog crash when the owner is a roster object
CreateRoutineDialog receives routineCreateTarget() output, which is an
owner OBJECT for roster-scoped bots; wrapping it in {name: bot} rendered
'[object Object]' and broke the meta lookup keyed by object. Resolve the
label through the object-aware botRosterMeta() path instead.

(Salvaged from #93572; the defensive coercion inside displayName was
dropped in favor of fixing the call site only.)
2026-08-25 16:31:02 -07:00
Jack Lau fab931fa28 docs(desktop): name the timer drain in the effect that performs it
Review follow-up, comments and one type annotation. No behaviour change.

The unmount effect now clears pending timers, but its leading comment is
still entirely about the focus bus, so the cleanup reads as unrelated code
that happens to be in the same block. Say why it lives there: both concerns
are "this composer is going away", they unmount together by definition, and
a sibling unmount-only effect would only be a second place to forget.

In the regression test, the clearTimeout mock declared id as number. Nothing
that reaches it is a number: jsdom under node returns a Timeout object, which
is why the scheduled and cleared arrays are unknown[] and compared by
identity. The annotation documented a shape the test never sees, so it is now
unknown with the cast moved to the one call that genuinely wants a number.
2026-08-25 16:25:50 -07:00
Jack Lau 37a8c10ab1 fix(desktop): stop the edit composer's timers from outliving it
Confirming an inline edit unmounts UserEditComposer while its 200ms submit
latch is still pending, so the callback resumes on an unmounted tree and calls
setSubmitting. Five window.setTimeout calls in the component, none cleared on
unmount; the latch is the one with a delay long enough to reliably survive.

In the app the stray state update is an invisible no-op. In vitest it can land
after the test file's jsdom environment is gone, and React reaches for a window
that no longer exists:

  Test Files  570 passed (570)
       Tests  5434 passed (5434)
      Errors  1 error

  ReferenceError: window is not defined
   at resolveUpdatePriority  react-dom-client.development.js:1308:7
   at dispatchSetState       react-dom-client.development.js:9126:14
   at Timeout._onTimeout     user-edit-composer.tsx:606:9

That is an unhandled error, so the job exits 1 with every test passing, on
whichever PR happens to be running rather than on anything related. It reads
exactly like infrastructure noise, which is the reason to fix it rather than
re-run it.

The component already knows about this hazard. Its unmount effect opens with a
comment calling it "the one composer that routinely unmounts", and two of the
other timer callbacks carry defensive try/catch for the composer core being
torn down underneath them. The timers themselves were simply never cancelled;
this cancels them instead of surviving them, which removes the race rather
than tolerating it.

All five sites now go through scheduleTimeout, which records the id and drops
it when the callback runs. The existing unmount effect clears whatever is
left. Two useCallback dep arrays gained scheduleTimeout, which is stable.

One regression test, in the file the CI error originated in: spy on
setTimeout/clearTimeout, submit an edit, unmount, assert the latch id was
cleared. It fails when the clear loop is removed. The 200ms delay is matched
explicitly so unrelated library timers cannot make it pass by accident, and
ids are compared by identity because jsdom under node returns a Timeout object
rather than a number.

Fixes #92462
2026-08-25 16:25:50 -07:00
686f6c61 961635c19c fix(desktop): copy unsafe RPC rejections instead of mutating name
asRpcError now always wraps a non-string name in a fresh Error. In-place
assignment was a silent no-op on sealed objects in sloppy mode. Catch
only host.request / requestProfile so routing TypeErrors keep their stack.
2026-08-25 16:25:41 -07:00
686f6c61 a837c7aaa2 fix(desktop): coerce bot RPC rejections for React 19 error formatting
JSON-RPC/IPC can reject with a plain object whose name is a number.
React 19 then crashes on (error.name || '').trim, which takes down the
Routines pane instead of showing the cron.manage failure. requestForBot
now wraps those values in an Error with a string name, including
cross-realm Error-like objects from the plugin test vm.
2026-08-25 16:25:41 -07:00
MagMueller 4a0b2778c7 docs(browser): call Browserbase the alternative 2026-08-25 16:25:37 -07:00
MagMueller 648361e788 chore: map contributor email 2026-08-25 16:25:37 -07:00
MagMueller 6b25e4e9fc docs(browser): put Browser Use Cloud first 2026-08-25 16:25:37 -07:00
Finn763 936a6ea8f7 fix(desktop): scope Cronjobs pane to the roster-clicked bot when the focused session has no owner (#94516)
The SDK's focusedSessionOwner store fails closed to null whenever the
focused session has no unique bot owner (a normal chat, ambiguous owner
hints) - the common case while the user browses the Bots pane.
resolveRoutineOwner treated that null as an error and returned null
before consulting the roster selection, so the Routines pane pinned
every agent on 'Cronjobs are unavailable until this agent appears in
the roster.'

Drop the fail-closed null gate and fall through to the existing
selection ladder (focusedBot || selectedBot || ...). An authoritative
focused owner still wins through its exact roster row and still fails
closed when that row is absent; a null owner with no matching selection
also still fails closed. Regression tests prove red pre-fix, green
post-fix.
2026-08-25 16:24:54 -07:00
Teknium 28f2ea86e2 fix(desktop): make --setup-tcc-identity produce a VALID signing identity on modern macOS
Fixes the two live E2E blockers @ctaylor86 found on PR #77189 (macOS 26.3.1,
OpenSSL 3.6.3):
- retry the PKCS#12 export with -legacy when security import rejects the
  OpenSSL 3 default format ('MAC verification failed during PKCS12 import')
- trust the self-signed root for the codeSign policy (security
  add-trusted-cert -r trustRoot -p codeSign) — an imported-but-untrusted cert
  is invisible to find-identity -v and unusable by codesign
- gate success on find-identity -v -p codesigning (postcondition), and use
  the same -v probe for idempotency so an untrusted leftover cert is repaired
  instead of reported as done

Tests rewritten as stateful fakes (valid only after import+trust), plus new
coverage for the -legacy retry, trust failure, postcondition gate, and the
untrusted-cert repair path; sabotage-verified (reverting to the name-in-output
probe fails 4 tests). Docs: manual fallback now includes the Trust step.
2026-08-25 16:24:19 -07:00
CriptoGus eeb391d0b1 feat(desktop): add --setup-tcc-identity to keep macOS TCC grants across rebuilds
Adds a one-shot `hermes desktop --setup-tcc-identity` command that creates a
self-signed code-signing certificate in the login keychain (openssl +
security import), grants codesign access to it, writes
desktop.macos_signing_identity to config.yaml, and re-signs the packaged app
with a certificate-anchored Designated Requirement.

macOS persists permission grants (Full Disk Access, Accessibility, Files and
Folders, microphone) against the app's code-signing identity, not its path.
The default identifier-pinned ad-hoc signature is stable across rebuilds, but
a certificate-anchored identity is the strongest guarantee — the same
mechanism yabai/skhd rely on. Previously users had to create the certificate
manually in Keychain Access; this command automates the whole flow and is
idempotent (re-run after updates).

Docs: desktop.md TCC section now leads with the command, keeps manual steps.
Tests: 4 new — fresh cert creation path, idempotent reuse, non-macOS no-op,
cmd_gui early-exit before build.
2026-08-25 16:24:19 -07:00
Brooklyn Nicholson 30b042e137 fix(desktop): clear the JS/TS check failures on the Browser pop-out
Import order, an unused test import, and the session-menu windows mock now that session-states reads isBrowserWindow.
2026-08-25 18:23:43 -05:00
Brooklyn Nicholson e8df401738 feat(desktop): hide the docked Browser tab while it is popped out
The live page stays in one window. The pop-out renderer skips shared layout/tile/unread writes so it cannot wipe the primary.
2026-08-25 18:23:43 -05:00
Brooklyn Nicholson a608799689 feat(desktop): give the in-app Browser its own OS window
A ?win=browser renderer (query before hash) so HashRouter cannot swallow the flag, and so one window per tab can focus instead of cloning.
2026-08-25 18:23:43 -05:00
alt-glitch 62b2d78025 fix(tool-search): bridge batch barrier, listing truncation, source indexing (salvage #92693, part 1)
Four fixes to the tool-search deferral layer, split from PR #92693 (the
availability-cache staleness fix ships separately):

1. The parallel batch planner now peels the tool_call bridge wrapper and
   decides admission on the underlying tool — supports_parallel_tool_calls
   works again when deferral is active. Unparseable wrappers stay
   sequential barriers; bridged calls get exactly the admission the same
   call gets direct. tool_search/tool_describe lookups batch concurrently.
2. _short_desc no longer truncates listing lines at 'e.g.', hostnames, or
   version strings — a sentence terminator must be followed by whitespace.
3. BM25 indexes the source label (e.g. 'linear' for mcp-linear), so
   service-name queries reach tools whose own name omits the service; the
   dead 'mcp' prefix token is stripped.
4. Substring-fallback docstring corrected (token misses, not zero-IDF).

Salvaged from #92693 by @alt-glitch with authorship preserved.
2026-08-25 15:24:15 -07:00
Teknium 95668f5eab fix(mcp): Atlassian catalog entry no longer 404s + Grafana defensive curation
- atlassian: /v1/sse was deprecated by Atlassian after June 30 2026 —
  installs OAuth'd fine then failed every handshake with 404 (#91538).
  Now points at /v1/mcp/authv2, the endpoint Atlassian's docs recommend
  for custom clients (live-probed: OAuth 2.1 + DCR intact).
- grafana (on top of @cedricziel's #93183 entry): defensive
  default_excluded for ask_assistant (opaque Assistant delegation
  meta-tool, bills usage) and agento11y_* (Grafana's own agent-obs
  product suite) — both from Grafana's published tool tables, both
  no-ops until the Cloud surface serves them; billing note added to
  post_install.
2026-08-25 14:49:40 -07:00
Cedric Ziel 56f94faaef feat(mcp-catalog): add Grafana Cloud MCP server
Vendor-hosted remote MCP at https://mcp.grafana.com/mcp over Streamable
HTTP with native OAuth 2.1 + Dynamic Client Registration -- the same
transport/auth shape as the existing datadog and sentry catalog entries,
handled by Hermes's MCP client + mcp_oauth_manager.

Exposes Prometheus/Loki/Tempo/Pyroscope queries, dashboards, datasources,
alerting, incidents, and Grafana Assistant investigations, user-scoped to
the operator's Grafana RBAC. This is the hosted Grafana Cloud MCP, not the
self-run OSS mcp-grafana binary; post_install points self-hosted users to
the OSS server.

Source: https://grafana.com/docs/grafana-cloud/ai-tools/mcp-servers/cloud-mcp/
2026-08-25 14:49:40 -07:00
Krzysztof Radzikowski 36f1423411 fix(update): gateway-only concurrent instances no longer abort hermes update (#37039)
On Windows, the pre-update concurrent-instance gate aborted with exit 2
whenever ANY other process held the venv hermes.exe shim — including the
gateway itself, which _pause_windows_gateways_for_update() stops moments
later and the post-update restart phase brings back. Users with a running
gateway were forced into a manual taskkill dance before every update.

The gate now filters gateway runtimes out of the abort list and proceeds
when nothing else is concurrent. Classification delegates to
_is_pausable_gateway -> gateway.status.looks_like_gateway_command_line
(the canonical shlex-tokenized, profile-selector-aware matcher shared by
the Desktop preflight exemption and the venv-holder guard fallback), so
the gate's exemption and the pause machinery cannot drift apart. Anything
not positively identified as a gateway — REPLs, dashboard, Desktop
backend children, gateway MANAGEMENT commands like 'gateway status',
unreadable cmdlines — still aborts exactly as before, and the abort
message now lists only the PIDs that are actually the user's problem.

Surgical reapply of PR #37039 by @damadorPL onto current main (the gate
moved from hermes_cli/main.py to hermes_cli/update_cmd.py in the main.py
decomposition); his substring classifier was replaced with the canonical
matcher, which also fixes the 'hermes gateway status' misclassification
flagged in review.

Co-authored-by: Hermes <hermes@nousresearch.com>
2026-08-25 14:48:51 -07:00
Brooklyn Nicholson 26777a4178 feat(desktop): open a Browser tab in the default browser from its context menu
Right-click the tab to hand its current page to the OS browser. about:blank
and other non-pages keep the row but disable it.
2026-08-25 16:43:54 -05:00
Brooklyn Nicholson b9513f2e1d feat(desktop): let a pane prefix the zone tab menu
A Browser tab needs one extra verb without replacing Reload / Close. Give
the pane a tabMenuPrefix slot the strip menu renders when it opens.
2026-08-25 16:43:54 -05:00
kshitijk4poor 874fab0ce0 feat(chat-plane): trace_id + turn telemetry, transient-delta split, seq-namespace epoch
Chat/event-plane quality work for the amended Phase 1 scope of #94484
(maintainer restructure: lean chat/event plane, no control-plane
changes). Three fixes came out of a source-level comparison against
OpenHands, Chainlit, VS Code, Zed, LangGraph, and Goose.

1. Per-turn trace_id + active-turn telemetry: _start_inflight_turn mints
   a 12-hex trace_id; _event_frame stamps it on every event frame in the
   turn, so a client can correlate the full lifecycle (dispatch -> first
   token -> tool calls -> complete) from one identifier — none of the six
   surveyed projects has frame-level turn correlation.
   session.events.stats now reports active_turns (session_id, trace_id,
   elapsed_s, streaming).

2. Transient vs durable events (OpenHands StreamingDeltaEvent pattern):
   message.delta / thinking.delta are stamped with seqs (live ordering
   holds) but never buffered — one streaming turn emitted hundreds of
   delta frames and evicted every durable control event from the
   512-slot ring, defeating replay for the exact reconnect window it
   exists to cover. The ring now evicts manually and records the highest
   DURABLE seq dropped, so truncated means real data loss and delta-only
   gaps no longer false-positive.

3. Seq-namespace epoch (Goose stale-cursor recovery): event_replay.EPOCH
   (8-hex per boot) is announced in gateway.ready and echoed by
   session.events.since; the client drops its seq watermarks when the
   epoch changes, so a stale HIGH watermark from a previous gateway
   process can no longer suppress replay/gap-detection forever. Legacy
   backends without an epoch are unaffected (client keeps watermarks).

Validation: Python 85/85 across replay/entry_ws/keepalive/protocol
(12 replay tests, 3 new); vitest 9/9 shared (2 new epoch tests), 66/66
desktop; full tui_gateway sweep 614/615 (1 known ordering flake, passes
in isolation). Live e2e on this tree: 12-event turn -> seqs contiguous
1..12, 8 durable frames buffered + 4 deltas live-only, single trace_id
on all frames, active_turns elapsed_s matches the real turn duration.

Research provenance: NousResearch/hermes-agent#94484 (comparative-scan
comment); techniques credited to OpenHands (transient split), Goose
(epoch/stale-cursor), per maintainer-restructured plan.
2026-08-26 02:55:04 +05:30
Gille 7c5c994397 fix(teams): request supported transcript content format 2026-08-26 02:54:35 +05:30
Teknium a87e8831f7 style(desktop): sort secret-storage-policy import per perfectionist lint 2026-08-25 14:10:19 -07:00
Teknium 6a6e16fa5d feat(desktop): OS-keychain encryption for stored secrets is now opt-in — no more macOS Keychain password prompt on every launch
Electron safeStorage parks a per-app key ('Hermes Key') in the macOS login
keychain; on machines with a locked/missing/corrupted default keychain that
turned every Hermes Desktop launch into a blocking 'Keychain Not Found' /
password dialog. Keychain-backed encryption is now an explicit opt-in:

- electron/secret-storage-policy.ts: standalone policy seam (default OFF,
  strict === true coercion, one-shot migration flag) + unit tests
- default path never calls any safeStorage API (including
  isEncryptionAvailable, which itself touches the keychain)
- one-shot legacy migration decrypts existing safeStorage blobs to plain
  0600 files at first launch; undecryptable blobs are kept but read as
  absent afterward (classify 'drop') so a dead keychain prompts at most once
- Settings -> Gateway toggle (all 5 locales) re-encodes every stored secret
  store in place when flipped (v1 connection.json, v2 connections.json,
  native-oauth-tokens.json)
- e2e: at-rest spec now covers both postures (opted-in unchanged contract,
  default saves without secure storage, owner-only bits, restart round-trip)
- docs: multi-connection-desktop + desktop-native-signin updated
2026-08-25 14:10:19 -07:00
Jeff Mettel bee489d563 fix(update): restart a booted-out launchd gateway instead of silently skipping it (#74973, salvage #75021)
Port of @jeff-mettel's fix onto the post-#91378/#92902 fleet-restart
shape. The current-profile restart was gated on `launchctl list <label>`
exiting 0 - a booted-out job (plist present, definition deregistered:
crashed helper, manual bootout, failed prior update) fails that check,
so the branch silently skipped: no restart, no message, KeepAlive unable
to revive a definition launchd no longer knows, update printing
'Update complete!' with the gateway down. `launchctl list` is also
session-scoped and unreliable as a loaded/unloaded classifier.

- _restart_launchd_gateway_after_update() (his extraction, adapted):
  plist-exists is the ONLY gate; launchd_restart() owns the
  bootout/bootstrap/kickstart ladder for every plist-present state;
  every failure path is loud and names the manual recovery command.
  The gate-error 'except: pass' (the second silent variant) now counts
  the label failed and tells the operator.
- Success still requires the #92902 supervision verify (fresh
  supervised PID), composing his fix with the returned-is-not-supervised
  guard.
- His regression suite adapted to the (restarted, failed) contract; the
  old 'unregistered -> left alone' pinning test FLIPPED - it pinned the
  bug.

A/B: his suite + the flipped test red on merge-base product code
(silent skip live), green at head. No macOS CI lane exists; field
evidence is #74973's reproductions plus the launchctl print output
shapes pinned in the suite.
2026-08-25 14:10:10 -07:00
Teknium 9de5460c12 feat(cron): acked failure signatures stop re-pinging — durable incidents + ack CLI (salvage #94692) (#95017)
* feat(cron): durable failure incidents with signature dedup and ack

Introduce a durable cron incident store (cron_incidents in the shared
cron/executions.db) that groups "same job + same error signature" across
runs, so a known recurring failure stops re-pinging the operator every run
once it has been acknowledged.

- cron/incidents.py: lazily-created incident table (detected -> alerted ->
  reviewed -> closed lifecycle; closed is per-signature terminal), sha256
  signature dedup over job_id + normalized error, redacted/truncated error
  storage, failure-type classification, and ack/list/get/count helpers.
- cron/scheduler.py: record an incident on the failure delivery path and
  suppress the per-run failure ping when the exact signature is acked (both
  the normal failure path and the processing-raised retry path). Best-effort:
  an incident-store error never breaks the cron run or delivery. Streak nudge,
  alert-once markers, and delivery-error behavior are untouched.
- hermes_cli: add `hermes cron incidents [--state ...]` and
  `hermes cron incidents ack <id>`.
- tests/cron/test_cron_incidents.py: dedup, lifecycle, redaction,
  classification, lazy-schema, scheduler gating, and CLI coverage.

Non-goals deferred to later slices: Discord buttons/review view, HMAC action
tokens, owner-agent review launch, approval-gated fixes, incident playbooks.

* refactor(cron): tighten incident lifecycle, wire alerted state and suppressed_acked outcome

Follow-ups on top of the salvaged #94692:
- Drop the dead 'reviewed' state and the SQLite CHECK (state validity
  lives in INCIDENT_STATES so future slices can add states without a
  table rebuild); lifecycle is detected -> alerted -> closed.
- Actually mark incidents 'alerted' after a failure ping reaches
  delivery, on both the normal and exception delivery paths.
- Record ack-suppressed runs with a distinct 'suppressed_acked'
  delivery outcome (registered in cron_health monitoring) instead of
  the ambiguous generic 'suppressed'.
- Drift-skip alerts explicitly bypass the ack gate (they carry the
  remediation command and alert once via drift_alerted already).
- Docs: failure-incidents section in the cron guide.
- Tests for the alerted transition + never-resurrect-closed.

---------

Co-authored-by: Laura López Real <113060513+laulopezreal@users.noreply.github.com>
2026-08-25 14:02:40 -07:00
Jeffrey Quesnelle 5b82658b3c Merge pull request #90129 from rroverin/fix/nvidia-nim-tool-message-name-field
fix(providers): strip name/tool_name from NVIDIA NIM tool messages
2026-08-25 16:39:36 -04:00
Teknium 4032a15ad0 refactor(prompt): remove the ~1.2K-token Nous Subscription block from the system prompt (#95005)
* refactor(prompt): remove the Nous Subscription block from the system prompt (~1.2K tokens/call)

* chore: retrigger CI (zero-job dispatch failure, auto-heal)
2026-08-25 12:59:29 -07:00
Teknium 52bcfb47de chore: map contributor email for attribution gate 2026-08-25 12:09:08 -07:00
Teknium 76d8f876f3 fix(cli): reconcile scoped-closure and reduced-lockfile freshness checks
Merge follow-up on top of the salvaged commits: fold the npm>=10 reduced
hidden-lockfile comparison (intersection of non-null fields) and the
annotation-field exclusions into a single entries_differ() helper, keeping
the workspace-closure scoping intact.
2026-08-25 12:09:08 -07:00
andyst-dev def7bdc638 fix(cli): don't re-run npm install on every TUI launch with npm>=10 reduced hidden lockfile
_tui_need_npm_install compared every field of the root package-lock.json
against node_modules/.package-lock.json. npm>=10/11 writes a reduced hidden
lockfile that omits declarative fields (version/dependencies/dev) and adds
extraneous, so nearly every package looked 'changed'; workspace link entries
("link": true, paths outside node_modules/) are never materialized by the
partial --workspace install. Both made the check return True forever, so
hermes --tui re-ran npm install (and dirtied package-lock.json) on every
launch (#84617).

Compare only the keys both sides record with non-null values (resolved,
integrity, ...), ignore workspace link entries and non-node_modules paths in
the missing-entry check, and treat extraneous as an npm runtime annotation.
Real skew (lockfile bumped while node_modules is behind) is still detected.
2026-08-25 12:09:08 -07:00
qazasdsdqaza 4d66def307 fix(tui): prevent spurious npm install on every launch
_tui_need_npm_install() compared ALL fields between
package-lock.json and .package-lock.json, treating npm's
intentionally-stripped metadata (version, license, engines,
dependencies, etc.) as real skew.  This caused 'Installing
TUI dependencies…' + rebuild on every launch.

Two changes:
1. Compare only the *intersection* of fields between the
   two lockfiles — a field present in the root lock but
   absent in npm's hidden actualized lock is a normal npm
   artefact, not a real dependency change.
2. Add , , ,
   to _NPM_LOCK_RUNTIME_KEYS — these boolean annotations
   are written non-deterministically between the two locks
   and never indicate a real dependency skew.

Real version/dependency changes still change /
, which are present in both locks and caught by
the intersection comparison.

Fixes: 347 false-positive lockfile mismatches → 0.
2026-08-25 12:09:08 -07:00
PRATHAMESH75 a96bad8e71 fix(cli): scope TUI npm-install closure to all selected workspaces
On Termux the launch install also selects ui-tui's child packages/*
workspaces (include_child_workspaces=True), so npm installs each child's
devDependencies. The freshness closure only followed devDependencies for
the ui-tui workspace itself, so a devDependency unique to a selected child
was dropped from the closure and a genuine missing package slipped past
_tui_need_npm_install.

Derive the closure from every workspace the install path selects, following
devDependencies for each. _npm_lock_workspace_closure now accepts the set of
selected workspace keys (dev-included roots); _tui_selected_workspace_keys
mirrors _make_tui_argv (ui-tui, plus child packages/* on Termux). Adds a
child-workspace-devDependency regression test (installs on Termux, ignored
off Termux) plus a closure-level dev-scope test.
2026-08-25 12:09:08 -07:00
PRATHAMESH75 0c47cd5260 fix(cli): scope TUI npm-install check to the ui-tui workspace closure
_tui_need_npm_install compared the full multi-workspace root
package-lock.json against the hidden .package-lock.json, but the launch
install is scoped with npm install --workspace ui-tui and only writes the
ui-tui dependency closure. Every dep belonging solely to another workspace
(apps/desktop, web, ...) was therefore reported as missing, so the check
returned True and printed "Installing TUI dependencies..." on every launch.

Restrict the comparison to the ui-tui workspace's dependency closure,
computed from the root lock's packages map (following npm's node-resolution
walk and workspace symlinks). Standalone / own-lockfile layouts and any
case where the workspace can't be located fall back to the full comparison,
so drift on a genuine ui-tui dependency is still detected.

Fixes #66978
2026-08-25 12:09:08 -07:00