Commit Graph

791 Commits

Author SHA1 Message Date
Teknium
5ba559c9e4 fix(relay): keep migrated Relay configs off 0.10's OTLP auto-export
Relay 0.10 starts gen_ai trace/log/metric export on its own whenever an
active configuration meets an OTEL_EXPORTER_OTLP_* endpoint in the process
environment, and those spans carry prompt and response messages. The
legacy HERMES_NEMO_RELAY_ATIF/ATOF variables never exported OTLP, so the
generated relay-plugins.toml now pins opentelemetry.enabled = false.

Also restore the discovery-only test's DISABLED-state assertions: they
pass on 0.10 and only trip on a host with a machine plugins.toml.
2026-10-09 19:58:29 -07:00
mnajafian-nv
9280fef70c chore(relay): tighten 0.10 upgrade validation
Signed-off-by: mnajafian-nv <mnajafian@nvidia.com>
2026-10-09 19:58:29 -07:00
mnajafian-nv
6caac042b1 chore(relay): upgrade to 0.10.0
Signed-off-by: mnajafian-nv <mnajafian@nvidia.com>
2026-10-09 19:58:29 -07:00
Teknium
097fcaa1de fix(skills): every turn announces skill changes once, on every surface
#118661's note only fired on the stored-prompt restore path, which a long-lived agent (classic
CLI, TUI, Desktop) runs once per conversation: those surfaces never saw a mid-conversation skill
change. The check now runs from build_turn_context every turn against the prompt the turn
actually sends.

- What was told is kept on the agent for its prompt; a fresh agent (gateway, -q --resume) reads
  the newest note back from the WHOLE transcript instead of the last 200 rows, so a long session
  no longer re-announces an unchanged delta (review by @kvnloo).
- A compaction rebuild that now lists the announced skills stays quiet.
- Index rows split on ": ", so qualified plugin skills (plugin:skill) parse.
- The plugin go-live note drops its own skills list: the per-turn note covers plugin skills like
  any other, so a plugin install no longer announces its skills twice.
2026-10-09 19:31:10 -07:00
teknium1
ec78ae2211 feat(prompt): skills installed mid-session reach the model without a prompt rebuild (port cloudflare/cloudflare-os#267)
The persisted <available_skills> index is reused byte-for-byte across turns so
the provider prefix cache stays warm (#104414). A skill that lands after the
prompt was built (hub install, skill_manage from another session, org sync,
curator) therefore never reached the index the model actually routes on until
compaction rebuilt the prompt: skills_list is live, but the prompt tells the
model to scan the index, not to call it. cloudflare-os#267 found the same fault
in their chat-cached catalog ("a catalog states what a session can reach now,
which is not a fact a chat can hold").

Hermes' idiom for stored-prompt drift is a one-shot note behind the cached
prefix, not a rebuild, so agent/skills_index_delta.py stages the delta on the
same per-turn user-message channel the surface-switch note rides: the added
skills' own index lines (name + description, the routing signal), the removed
names, cumulative against the stored prompt and re-staged only when the delta
changes (read back from the api_content sidecar, so the gateway's fresh agent
per turn does not stack copies). An undone delta retires the stale note once.
The current index comes from the same cached builder the prompt did, so an
unchanged turn costs one LRU hit.
2026-10-09 19:31:10 -07:00
Teknium
87b5e05620 docs(skills): note inline-shell scoping for nested, external_dirs and unreadable-lock cases 2026-10-09 18:18:41 -07:00
Teknium
46d7718a52 docs(telemetry): v4 takes additive values in place after its stable release
Owner ruling for gateway_pause_failed: extend package schema v4 in place
(additive only; ingest validates the envelope and every shape already sent
stays valid) instead of bumping to v5. The two sentences that said v4 was
extended only while canary-only now say so.
2026-10-09 14:36:08 -07:00
Teknium
f549ce3fbd fix(update): drop config_unreadable: an unreadable config.yaml also hides consent, so no row is ever sent
The exit is real (RuntimeError from require_readable_config_before_write), but the consent gate
reads the same config.yaml and reads it as collection off: a live run on a scratch install with
an unparseable config.yaml wrote no metrics store at all. A closed value nothing can emit only
widens the contract.
2026-10-09 14:36:08 -07:00
Teknium
2379b2e2eb fix(update): pre-apply exits that read exception/subprocess_failed name their reason
hermes.update.run rows with apply_mode=unknown still had three exits with no stop_class on main:

- The startup-syntax preflight (update_cmd_commit.read_target_files) runs `git cat-file --batch`
  under a 120 s limit. On a blobless install (install.sh clones --filter=blob:none) every changed
  blob lazily fetches in its own round trip, so a slow link times out and the uncaught
  TimeoutExpired ended the whole update as `subprocess_failed` after 2+ minutes (31 CLI + 23
  Desktop install-days). An unread batch now counts as unread files, which is the preflight's
  documented contract; the post-move syntax check stays the backstop.
- An unreadable/unparseable config.yaml (require_readable_config_before_write RuntimeError) or an
  invalid configured channel name (ChannelError) while picking the channel read `exception`. They
  now record `config_unreadable` (new) / `channel_unresolved`.
- A failed Windows gateway pause (_abort_on_error / ServicePauseFailed RuntimeError) read
  `exception`; it now records `gateway_pause_failed` (new).

Two closed UPDATE_STOP_CLASSES values, the v4 schema enum and the relay-shared-metrics doc row
change together; every legacy value stays valid.
2026-10-09 14:36:08 -07:00
Teknium
b1bba19558 fix(telemetry): the Windows hand-off's relaunched update child also reads its own claim as failed
On Windows the hand-off names the process it created as the marker's delegate, and
hermes_bootstrap's relaunch (subprocess.call) or the legacy .cmd's cmd.exe makes the updater
its child, so the delegate check now also accepts our parent. The docs sentence moves to the
hermes.update.run outcome row and names the checkout-lock case (another mutator holding it).
2026-10-09 14:22:08 -07:00
Teknium
975b62663a fix(telemetry): a Desktop hand-off's update refused by its own claim counts as failed
The hand-off quits the Desktop and launches `hermes update` as the marker's
delegate. When the update lock then refuses that claim (the pre-a28a5d03a9f
whole-second creation-time bug on macOS, still present on installs that cannot
pull the fix through the button), no other updater exists: the Desktop shows
"Hermes couldn't finish updating" and the user retries. The metrics row read
outcome `refused`, which the rollup treats as neither attempt nor failure, so
~320 Desktop update failures a fortnight vanished from the failure rate.

Record that one case (marker names this process as its delegate) as `failed`;
a refusal under another update's claim stays `refused`.
2026-10-09 14:22:08 -07:00
Mariano Nicolini
bcf216d461 feat(image_gen): describe Krea image inputs as style references so the model stops rewriting the style into the prompt 2026-10-09 15:36:14 -04:00
ethernet
894a26a39e Merge pull request #135459 from NousResearch/fix/rc5-darwin-baseline
fix(release): stable releases pass signed-package acceptance when no upgrade baseline exists
2026-10-09 13:06:34 -04:00
teknium1
8bff64d6ed fix(memory): keep a runaway-recall ceiling when prefetch spilling is off
Without the opt-in, external recall is kept whole up to
max(10 x hooks.output_spill.max_chars, 100k chars) and still spills above
that, so one runaway provider can't overflow the context window and wedge
the session. Normal provider-budgeted recall (~16 KB for Honcho) is
unaffected.
2026-10-09 02:59:16 -07:00
Erosika
2327290b0d fix(memory): make external prefetch spilling opt-in 2026-10-09 02:59:16 -07:00
Teknium
ecb66f60dd feat(desktop): plugin SDK pet bubble (ctx.pet.say)
Plugins can now put a short plain-text line in the core pet's speech bubble
instead of locating the pet canvas in the app DOM and floating their own
overlay over it (pet-wallet, #135178).

- ctx.pet.say(text, { id?, tone?, ttlMs? }) -> disposer; ctx.pet.clear(id?);
  ctx.pet.visible (ReadableAtom<boolean>)
- host-owned rendering in PetBubble, in-window (plugin lines only) and in the
  pop-out overlay (lines ride the existing pet-state IPC push)
- plain text, 120-char cap, control/bidi chars stripped, plugin-name label,
  TTL 6 s default (1-30 s), 3 live lines and 10 says/10 s per plugin
- core error / waiting states keep the bubble; nothing shows without a pet
- every line a plugin has up is cleared on unload/disable/reload
- docs: API section, migration-table row for pet-wallet, SDK exports
2026-10-09 02:04:56 -07:00
web3blind
aad11beae0 fix(browser): fence captured CDP dispatch and retain late replies
# Conflicts:
#	tools/browser_supervisor.py
#	tools/browser_supervisor_capture.py
2026-10-09 02:04:29 -07:00
ethernet
25a096f317 fix(release): treat any missing stable baseline as no upgrade arm
A release that skipped bundles never writes the stable baseline pointer,
so its absence is normal at any point, not only before the first bundle
release. Drop the final-tag scan. A 404 on the default pointer now always
plans no upgrade arm. A supplied baseline-manifest that does not exist
still blocks, because that is a bad input.
2026-10-09 01:41:35 -04:00
ethernet
4e23d79de4 fix(release): let the first bundle release pass without an upgrade baseline
No stable release has shipped signed packages yet. Every final tag so far
skipped bundles or predates the claim pipeline. So
releases/stable/release-candidates.json does not exist, and the three
"Pin actual OLD and NEW signed packages" jobs refused with "No published
stable package baseline".

transitions now treats a 404 as a first release only when no final stable
tag records a candidate manifest and no baseline-manifest input was given.
It still validates the receipt, emits empty matrices and baseline=none.
The packaged upgrade arms skip on that output. The gate accepts their skip
only when their own planner succeeded with baseline=none, so a missing
baseline after a real bundle release still blocks.
2026-10-09 01:37:59 -04:00
teknium1
cc1500a071 fix(desktop): a linked plugin checkout drives its desktop half; doctor flags a stale copy
The Desktop loads a unified package's desktop half from the copy in
desktop-plugins/<name>/, not from the package. Editing the source shipped
nothing and nothing said so:

- a package symlinked into plugins/ (the in-place dev layout the Python
  loader already follows) was never copied at all — Dirent.isDirectory()
  is false for a symlink;
- an installed package edited in place stayed stale until Rescan/restart.

Electron now follows symlinked package dirs, marks their copy linked, and
re-syncs it on any byte difference. The renderer watches a linked copy's
source plugin.js and, on save, runs the reconcile and hot-reloads. Catalog
and git installs keep the mtime rule. `hermes plugins doctor` warns when
the copy differs from the source and says how to refresh it.
2026-10-08 04:07:30 -07:00
teknium1
82b9bfedd6 fix(plugins): enabling a plugin from Desktop/TUI/dashboard no longer strips the core tools
With no saved platform_toolsets, _toggle_plugin_toolset seeded
platform_toolsets.cli: [<plugin_ts>]. A list naming only plugin keys
replaces the hermes-cli composite, so the model lost every built-in tool
(26 -> 1, no terminal). The seed is dropped: an unknown plugin toolset is
already on by default when no list is saved.

plugins remove now clears the plugin's toolset from saved lists even when
plugin.yaml has no provides_tools (the toggle already no-ops without a
toolset); before, hermes config check warned about an unknown toolset.

Docs: say what provides_tools actually drives.
2026-10-08 03:40:04 -07:00
witcheer
dde8800ed9 docs(plugins): list the built-in author tools at the top of the plugin guide 2026-10-08 01:02:23 -07:00
Teknium
65c6fb521d fix(release): continue the version line after the legacy v0.21.5 release
The pipeline seeded its version line at 0.21.4, but the legacy CalVer line
already published "Hermes Agent v0.21.5" as v2026.9.24. A first `--bump patch`
cut therefore derived 0.21.5 again: installs already reporting 0.21.5 would
compare equal and never see the new build as an update, and the release page
would carry two v0.21.5 entries.

Seed at 0.21.5 so the first pipeline patch is 0.21.6 and the first minor stays
0.22.0. Entrypoint fixtures now pin their own published head instead of
tracking the production seed.
2026-10-07 20:58:19 -07:00
alt-glitch
2c8df45ee5 Merge remote-tracking branch 'origin/main' into sid/onboarding
# Conflicts:
#	agent/conversation_loop.py
#	tui_gateway/prompt_turn.py
2026-10-07 19:29:35 +05:30
Teknium
b8f007cc4f docs(telemetry): how update dashboards count a partial run 2026-10-07 06:15:58 -07:00
Teknium
476e0f1138 fix(telemetry): pre-receipt exits keep their initiator; index.lock permission errors; bare-interpreter updates park
- record_stop_without_receipt reads the update marker raw and tags initiator=desktop only when the
  marker names this run's hand-off (delegate line = us, or HERMES_UPDATE_HANDOFF_PID / our parent),
  so a Desktop-started early refusal reads kind=desktop and a CLI run refused by someone else's
  update stays cli.
- _move_checkout_to classifies its git error with the shared classifier (git_output_stop_class):
  index.lock 'File exists' -> git_index_locked, 'Permission denied'/EACCES -> permission_denied
  (now a stop class), else checkout_move_failed.
- _publish_shared_metrics: an interpreter that cannot import the config reader (the -I -S bootstrap
  that finalizes a dependency-preparation failure) never emits; it parks the bounded receipt in
  pending_updates unless it can tell collection is off (loaded config says off, or no config.yaml),
  and the next normal start applies the existing gate (report_pending_updates / begin_process purge).
2026-10-07 06:15:58 -07:00
Teknium
b49e2ff2b9 fix(telemetry): review findings on pre-apply stop reasons
- The parked copy keeps a stop reason's label only when it is an exception type name (plus its
  errno token) or one of Hermes' two fixed phrases; any other text before a colon reads '-'
  (a free phrase like 'my project name:' used to survive).
- _exit_after_failed_branch_switch's branch_missing/checkout_move_failed probe cannot raise, so
  the exit it names is unchanged even when git refuses.
- hermes update --plan/--check/--list-venv-holders on a managed install records no update run.
- Docs/contract say 'before the apply stage mark' and name the tokens that fire after git moved
  and restored the tree; tests drive git_error_stop_class and _zip_stop_class directly.
2026-10-07 06:15:58 -07:00
Teknium
b5a8fba1b4 test(telemetry): pre-apply exit classes, parked parity, receipt-less refusals; docs + smoke
- test_shared_metrics_update_stop_reasons: every stop token reads as its own class in-process
  and through the parked copy (no free text kept), unknown tokens and post-apply tokens fall
  back, a committed run that only owes parked changes is partial; the lock and Git-operation
  refusals each record one row and leave the holder's latest.json byte-identical.
- test_shared_metrics_install_failures: a PermissionError stop reason now reads its own class
  permission_denied (was os_error); ENOSPC reads disk_full; other OSErrors keep os_error.
- relay-shared-metrics.md: the new classes, partial outcome and parked fields.
- relay smoke: the failed update receipt names its fetch failure and the row asserts fetch_failed.
2026-10-07 06:15:58 -07:00
Teknium
1e347ccf2a feat(plugins): a portable package can ask Hermes to gate its MCP server
Agent Plugins v1 has no trust field, and Hermes's portable mcp.json reader
accepts only type/url/headers for a remote server, so a package server always
ran at the default trust: full. A package whose tools trade, spend or send
(senpi resolving trade approvals on a user's Hyperliquid account) could only
ask the model to check with the user; Hermes itself never prompted.

plugin.json's Hermes extension now accepts
  extensions.com.nousresearch.hermes.servers.<name>.trust: untrusted
which is applied to that mcp.json server exactly like a config.yaml
'trust: untrusted': every write-capable call asks first and fails closed
when unattended. 'full' (the default) is accepted and changes nothing, any
other value refuses the package, so a package can narrow access but never
widen it, and a config.yaml server with the same name still replaces the
package's entry. 'hermes plugins validate' lists the gated servers.
2026-10-07 04:51:23 -07:00
kshitijk4poor
77bf0d7f73 refactor(gateway): one drain predicate for the fire gate and the adapter
Lead fold from the independent review (quality + thermo + 2c + efficiency
lenses, all Low): the fire startup gate and
APIServerAdapter._gateway_is_draining now share runner_is_draining, so a
future drain flag cannot be added to one and missed by the other; the gate
returns early on no runner instead of looping on an unchanging guard. The
cold-adapter test closes its loop and joins its thread, the slow-verify
test keeps the same ordering at a fifth of the wall-clock, and the Chronos
contract doc lists the startup 503.
2026-10-07 13:43:19 +05:30
alt-glitch
7f8fa7843e refactor(onboarding): drop app discovery and the guide_* funnel steps
The desktop's old recommendation inputs were the only client of
GET /api/mcp/catalog?detect_apps=true and the only sender of the guide_*
onboarding steps; both went with the old ::onboarding chain.

- The catalog route loses detect_apps; mcp_app_detection.py and its two
  test files go. The manifest's suggest.applications validator moves into
  mcp_catalog.py, which still validates applications / requires_app /
  examples (documented in features/mcp.md).
- DESKTOP_ONBOARDING_STEPS and the v4 schema drop guide_connectors,
  guide_first_build, guide_layout and guide_look. v3 stays as published.
- developer-guide/onboarding-recommendations.md described that pipeline
  and is removed.
2026-10-07 11:38:21 +05:30
alt-glitch
8b89b68fb4 Merge origin/main into sid/onboarding
Brings the onboarding branch up to main 0c2ff854a5. Where a Wave A PR
already landed a reviewed version of a piece of this branch (free tier
#134049, keyless web #134048, app resolver #134050, bundle and uninstall
#134051, catalog card and toolsets #134052, built-in tour #134054, stdin
guard #134055, retired key #126146), main's version wins. The PM long-path
files also take main's version: that work lands through #130430.

Kept from the branch on top of main:
- the setup and start_chat toolsets, declared with main's platforms key
  (desktop only), and the setup_choose server request contract;
- the setup-profile marker file (.setup-profile.json) instead of the
  profile.yaml role, as c3f85bc54f decided;
- the first-contact note's message argument beside main's internal flag;
- the setup-profile gate on the shared-metrics consent dialog;
- window sizing (window-growth) in place of chat-onboarding-window.

Desktop i18n follows main's per-section sibling files. The onboarding
strings go into them (modelMenu.localSetup, handoffTour.localTitle and
localText) and setupChoose/startChat stay in en.ts. guidedGreeting is
gone. The shared-metrics copy uses the branch's rewrite in English, with
the pre-opt-in sentence corrected for the fresh-install note
(d3674513c8); locales keep main's translations, with installId and
consentWindow folded into the new sending key.

The built-in tour reads local-model eligibility from the local-setup
offer store, the one place that rule lives on this branch. The gateway
contract and the onboarding skill docs pages are regenerated.
2026-10-07 03:27:01 +05:30
alt-glitch
1590669243 onboarding: leave MEDIA and the Store bundle env out of this PR
MEDIA (2f1fabd4bf, the backend MEDIA: parser and attachment parts) is
deferred to its own PR. Its 53 MEDIA-only files go back to the merge-base,
so they take main's version; the 10 files it shares with onboarding keep only
their onboarding hunks (setup_choose and start_chat cards, the start_chat
contracts, the scripted prelude, the first-contact note, the local-setup
turn report, intro_resends, visible_text, the bigger chat headings). The
generated gateway contract is regenerated after the merge with main.

The Store variant's HERMES_GUEST_ONBOARDING bundle env (39af862aec) is
reverted here; it ships through Ari's bundle work.
2026-10-07 03:07:33 +05:30
alt-glitch
05311267e6 onboarding: rip out the userscan; the setup chat gets a small facts block
The setup chat no longer scans the computer. The collector, its host_facts.py
hook, the inline-shell setting on the setup profile, the ${HERMES_PYTHON}
skill token and the insights/ cache are gone.

The first /initiate-setup turn now sends the skill plus one JSON block that the
model reads as it is: machine basics (OS, release, architecture, CPU, RAM, GPU
class, vendor, WSL, container), machine_kind, has_nvidia_gpu, is_spark,
suggested_name and locale, beside the session keys. A key that was not
measured is left out.

The non-scan parts of host_facts.py (account name, locale, Spark detection,
the fork card, the NVIDIA and Blender tasks, the handoff text) move to
agent/initiate_setup_facts.py and are imported normally instead of loaded by
file path.
2026-10-07 03:05:26 +05:30
Teknium
fd0b331c6f test(e2e): mid_git asserts recovery outright (the launcher now reaches the repair first)
The known_failure on the mid-merge launcher gap is gone: #132361's launchers import
hermes_bootstrap (the interrupted-pull repair) before any other checkout module, so the
merge-time kill must leave a runnable install with no gate.

(cherry picked from commit 96fd6724a86aa9b48ac7f88b56230e94f3447d7e)
(cherry picked from commit f975552309b97b7b2e4ac97b63ad278d185e2369)
2026-10-06 13:02:14 -07:00
Teknium
46a5fa2e14 test(e2e): gate the mid-merge launcher gap, run mid_git last
Native run 37190464303 (first merge-time kill): git merge was held writing
hermes_constants.py with index.lock held and killed there. Every next
launch then died with ModuleNotFoundError: the launcher
(hermes_cli/_launchers.py::_launcher_script) imports hermes_constants from
the checkout before import hermes_bootstrap, whose
restore_interrupted_pull is the repair, so the repair never runs and even
hermes update fails. The bricked install then failed every later cell.

mid_git now runs last, and its recovery assertions sit under a
known_failure keyed on exactly that traceback (owner upd-txn: strict
acceptance fails on it). The hold precondition (index.lock left by the
kill) is asserted outside the gate.

(cherry picked from commit 7aca86f2341f6135b4da2666d58e24e3e14b7945)
(cherry picked from commit 95595e12f6a0e6ddc022a43e871c8e654149fbbe)
2026-10-06 13:02:14 -07:00
Teknium
e60927ea89 test(e2e): mid_git kills inside the merge's checkout, not at fetch
Every green native run killed the mid_git cell during git fetch (fetch was
in GIT_OPS and is the long step), so the merge-time index.lock schedule the
cell is named for never ran. A smudge filter the harness names in the
install's .git/config + .git/info/attributes (git's own filter hook; no
product change, no env var) now holds git merge while it writes
hermes_constants.py: index.lock held, the cell's new file and
hermes_cli/main.py already at the target, hermes_constants.py unlinked.
The kill point requires the hold, a merge/reset/checkout git child by argv
and index.lock; the cell asserts the lock was left. The fetch-time kill
keeps its own cell (mid_fetch).

(cherry picked from commit 592d57c3a9f78e04d617be659342e5338e080381)
(cherry picked from commit ba54f871bd580bb3fe9de7f840a7a21baf564dd2)
2026-10-06 13:02:14 -07:00
Teknium
9c4c440b3d docs: crash-cell oracle, strict acceptance and derived update routing
(cherry picked from commit 0c85f0722acc9a1564b219a316ffed342cf86865)
(cherry picked from commit 592180086b29c5a4e226c1a33529ca846b613650)
2026-10-06 13:02:14 -07:00
Teknium
a950c449ea test(e2e): orphaned hand-off update cell; deterministic kill points; mid-git no longer xfail
- mid_git: the xfail is gone; #132361 (this branch is stacked on it) fixes the stale
  index.lock + bare-git interrupted-pull repair.
- orphaned_update: kill ONLY windows.ps1 (no /T) once its `hermes update` child runs
  under the claimed marker; the update must finish, the marker must read LIVE (owner or
  line-4 delegate, C1) for as long as it runs and be gone after. The marker assertions
  are a message-gated known_failure until #132354 + #132365 land the delegate line.
- _marker_text parses the line-format marker (it ran json.loads: always <unreadable>).
- tree_moved reads HEAD from the ref files instead of spawning git rev-parse every poll.

(cherry picked from commit aa8a8734785fdcc2d7fe958ab15ae002afdd12d9)
(cherry picked from commit eaf540f555cc936e798edc84ee1fd18fa802beac)
2026-10-06 13:02:14 -07:00
Teknium
888209e375 docs: crash-cell matrix for source updates
Cell -> kill point -> test -> fixing lane, and when the real-update suites
are required on a pull request.

(cherry picked from commit 9a8d1406641ee1a66e755267677c2b029a38f067)
(cherry picked from commit 05d543c6a6e4da288939ab44b198cd3dd18a6f9a)
2026-10-06 13:02:14 -07:00
Siddharth Balyan
3aaae8c537 Plugins: apps are found wherever they are installed on Windows, macOS and Linux (#134050)
* feat(platform): app declarations list every place an app may be installed

An app block's location may now be one path or an ordered list. A list
item is a path, where a `*` segment stands for a versioned folder and the
highest version is tried first, or a mapping naming a location kind:
command (PATH), uninstall_registry (InstallLocation plus a file, Windows),
app_bundle (/Applications, ~/Applications), flatpak (app id) and snap.
The directories behind each kind live in known_dirs.

Each kind's OS, key, presence and locator are one LOCATION_KINDS entry in
resolver/app.py. The declaration parser validates against that table, so
a new kind is one entry instead of three coordinated edits in two modules.

With requires.min_version, availability reads the version of every
present copy and returns the first that qualifies, so an old copy found
first does not hide a newer one; if none qualifies it reports the highest
readable version. A Linux block may omit a version source (Linux has
none) and is then gated on presence only; Windows and macOS blocks still
need one, and a Linux-only declaration cannot set a minimum.

Brought over from #129246. The Blender 0.2.0 catalog pin stays out: older
clients reject a list location, so it follows a release that ships this.

* fix(platform): bad app declarations and vanished registry entries no longer break app lookup (review)

A location with a non-string kind raises DeclarationError instead of TypeError.
A location's file and key value are checked after stripping, so a leading / or \ or a drive cannot escape the install folder.
An uninstall registry entry that cannot be opened or read is skipped instead of ending the scan of its root.
2026-10-07 00:58:22 +05:30
Teknium
32a79afb27 Merge pull request #132361 from NousResearch/fix/update-commit-point
fix(update): make the git/ZIP swap a single crash-safe commit point
2026-10-06 11:11:39 -07:00
Teknium
e032fdbca1 refactor(telemetry): registry prefixes come from the hub adapters; update reasons use tables
- `skills_hub._registry_prefixes` builds the identifier prefixes from the adapters' own
  SOURCE_IDs and `SkillsShSource._ID_PREFIX_ALIASES`, case-insensitively, so `skills.sh/...`,
  `skils-sh/...` and `OFFICIAL/...` stop reading `unresolved`. A matched source is named by
  `source_id()`, the method the router and the contract test use; two test fakes gain it.
- `update_failure_class`: exception type -> class and fixed stop-reason prefix -> class are
  tables instead of an if-ladder. `Windows gateway recovery failed: ...`, the fixed prefix
  update_completion writes when the post-update gateway resume fails, now reads
  `restart_failed` instead of `other` (prefix only, never the message).
- `mcp.servers.add` records through `mcp_rpc_helpers.record_mcp_add`, dropping the 175-column
  noqa line that kept methods_tools.py at its cap (2011 lines now).
2026-10-06 11:10:43 -07:00
Teknium
d6813a5dd7 fix(telemetry): extension failure classifiers are inert; the contract rejects contradictory install rows
- `_publish_failure_class`, `probe_failure_class` and `exception_failure_class` read a raise
  site's `failure_class` with `inspect.getattr_static` (`tagged_failure_class`), and the MCP
  type walk falls back to `connect_failed`. A third-party exception whose `__getattr__` or
  property raised replaced the user's PluginOperationError with RuntimeError (they run at the
  raise site, collection on or off), and the metric row was dropped; it now reads `exception`.
- `counter_dimensions_are_valid` adds the cross-field rule the per-field enums cannot state:
  failure_class in the kind's own set, `none` exactly on success, registry only on skill rows.
  Pre-split rows (no failure_class/registry) still validate.
- A refused dependency consent returns its reason as `_ConsentRefusal`, a str carrying the
  closed class (`non_interactive`, `deps_declined`, `manifest_invalid`), so publication no
  longer classifies by matching the user-facing copy. The text the user reads is unchanged.
- `extension_registry` imports the hub's `_SOURCE_ID_ALIASES` instead of a hand copy.
- test file: schema read uses utf-8-sig (windows-footguns).
2026-10-06 11:10:43 -07:00
Teknium
ce14639879 fix(telemetry): a partial update with a failed build or restart keeps that failed_stage
update_cmd_fleet._verify_fleet_after_update writes `partial` for a failed build
(`not update_complete`) and for an incomplete restart as well as for a
verification gap. The relabel marked the synthetic verify stage failed for every
`partial`, so failed_stage moved from build/restart to verify and every such run
added a failed hermes.update.stage verify row. Verify is now failed by `partial`
only when no stage mark already failed (a stale/down fleet row still fails it).
The appendix row states the rule.
2026-10-06 11:10:43 -07:00
Teknium
717d8a3e5e fix(install-metrics): count the install note on a consented day; purge receipts on every opt-out
report_pending_installs recorded the fresh-install row at the first start,
usually the opt-in day itself (setup asks inside the install). The sender's
CONSENT_GATE_SQL needs period_start >= opened_at, so that day's package is
never sent: the metric mostly measured re-runs over already-consented homes.

- A receipt is recorded only when collection is on AND (sending is off, or an
  open send consent window opened at or before today 00:00Z); otherwise it is
  kept for the first start on a later day. Tested against the real store,
  real reconcile_send_consent and the real CONSENT_GATE_SQL: kept on the
  opt-in day, recorded the next day, and that package passes the gate.
- Every opt-out answer purges pending_installs AND pending_updates:
  save_consent(enabled=False) (setup, pre-chat offer, dashboard banner,
  `hermes setup telemetry`) and the tui_gateway shared_metrics.set RPC, via
  one shared_metrics_process.purge_pending_receipts used by begin_process too.
- Receipts are claimed by a plain rename, so an unparseable or non-dict
  receipt is deleted instead of sitting forever; a receipt no start counted
  within 7 days is deleted unreported (it would count under a later version).
- With Relay instrumentation off the store "settles" a row without recording
  it; the reporter now leaves the receipts for a start that records, instead
  of latching and deleting them uncounted.
- Docs: the hermes.install.run row states the consented-day rule, the 7-day
  cap, opt-out deletion and the interrupted rules.

Invariant tests red at 6246dd0f69d (5 in test_shared_metrics_install_run,
1 in test_shared_metrics_consent_rpc).
2026-10-06 11:10:43 -07:00
Teknium
74e0225377 feat(observability): hermes.install.run from a local installer receipt
install.sh / install.ps1 leave one closed-token receipt per full ladder run
(installer, outcome, failed_stage, failure_class, start/finish epoch) under
$HERMES_HOME/telemetry/shared_metrics/pending_installs. Every fail()/Fail call
site passes a closed failure class; the installer never sends anything.

The next Hermes start records each receipt once (latched by id, deleted after
the row is saved) only while collection is on; a start with collection off
purges the receipts unreported. Out-of-contract receipts are dropped.
2026-10-06 11:10:43 -07:00
Teknium
ab6f123eb4 docs+smoke(telemetry): failure_class/registry appendix rows; relay smoke asserts a real failed plugin install and a failed update run 2026-10-06 11:10:43 -07:00
Austin Pickett
ccff4df98c fix(skills): trust-scope inline-shell auto-exec to non-community hub skills
Inline `!`cmd`` expansion in SKILL.md bodies ran for every skill whenever the
global skills.inline_shell flag was on — a community hub skill and a builtin
were expanded identically. The hub's install gate (INSTALL_POLICY) blocks a
community install on the caution/dangerous verdicts the inline-shell DSL scans
as, but --force or a pre-scanner install puts that skill on disk anyway, and
the next view/slash/preload would auto-execute it on the host.

Gate expansion at the single preprocess_skill_content choke point (all three
surfaces route through it): community-trust hub lock entries never expand;
trusted/builtin hub entries and anything without a hub lock entry (bundled-
synced, user-created, project/external) keep the flag's contract. Lock read
failures skip the gate, like every other provenance consumer.

Fixes #63307
2026-10-06 11:20:43 -04:00
Teknium
38e83857e5 docs(update): name the git path's intended behaviour changes (kshitijk4poor 5411135640)
What: a "Behaviour changes on the git path" section in the source-update developer guide: ZIP
fallback only from an untouched checkout, arm failure stops before any move, startup syntax
judged at the target by an admitted interpreter in one cat-file spawn, moves by commit id with
HEAD verified, the upstream ff judged before it moves, and a gone-target marker kept over a
dirty tree.
Why: the reviewer asked which of these are intended; each is, and release notes need the list.
2026-10-06 06:22:27 -07:00