Commit Graph

32101 Commits

Author SHA1 Message Date
ethernet
e8fcb007b9 Merge remote-tracking branch 'upstream/main' into ethie/pm-clean
# Conflicts:
#	AGENTS.md
#	acp_adapter/edit_approval.py
#	acp_adapter/server.py
#	agent/agent_init.py
#	agent/anthropic_adapter.py
#	agent/anthropic_credentials.py
#	agent/auxiliary_client.py
#	agent/azure_identity_adapter.py
#	agent/bedrock_adapter.py
#	agent/browser_registry.py
#	agent/chat_completion_helpers.py
#	agent/coding_context.py
#	agent/context_references.py
#	agent/conversation_loop.py
#	agent/copilot_acp_client.py
#	agent/credits_tracker.py
#	agent/curator.py
#	agent/curator_backup.py
#	agent/deadline.py
#	agent/display.py
#	agent/errors.py
#	agent/estop.py
#	agent/i18n.py
#	agent/image_gen_registry.py
#	agent/image_routing.py
#	agent/learning_graph.py
#	agent/learning_mutations.py
#	agent/lsp/servers.py
#	agent/model_metadata.py
#	agent/models_dev.py
#	agent/monitoring/gateway_health_export.py
#	agent/monitoring/otlp_exporter.py
#	agent/pet/store.py
#	agent/process_bootstrap.py
#	agent/prompt_builder.py
#	agent/proxy_sources/iron_proxy.py
#	agent/secret_sources/_cache.py
#	agent/secret_sources/bitwarden.py
#	agent/secret_sources/registry.py
#	agent/shell_hooks.py
#	agent/skill_bundles.py
#	agent/skill_commands.py
#	agent/skill_utils.py
#	agent/ssl_guard.py
#	agent/ssl_verify.py
#	agent/system_prompt.py
#	agent/terminal_env_registry.py
#	agent/trace_upload.py
#	agent/transcription_registry.py
#	agent/tts_registry.py
#	agent/verify/environment.py
#	agent/vertex_adapter.py
#	agent/video_gen_registry.py
#	agent/web_search_registry.py
#	cli.py
#	cron/jobs.py
#	cron/scheduler.py
#	gateway/agent_cache_pressure.py
#	gateway/cgroup_cleanup.py
#	gateway/channel_directory.py
#	gateway/config.py
#	gateway/control_socket.py
#	gateway/dead_targets.py
#	gateway/drain_control.py
#	gateway/hooks.py
#	gateway/kanban_watchers.py
#	gateway/lifecycle_ledger.py
#	gateway/mirror.py
#	gateway/pairing.py
#	gateway/platform_registry.py
#	gateway/platforms/helpers.py
#	gateway/platforms/weixin.py
#	gateway/readiness.py
#	gateway/restart_loop_guard.py
#	gateway/rich_sent_store.py
#	gateway/run.py
#	gateway/session.py
#	gateway/shutdown_flush.py
#	gateway/shutdown_forensics.py
#	gateway/slash_commands.py
#	gateway/status.py
#	gateway/sticker_cache.py
#	gateway/whatsapp_identity.py
#	hermes_bootstrap.py
#	hermes_cli/_early_recovery.py
#	hermes_cli/_install_repair.py
#	hermes_cli/_startup_fast.py
#	hermes_cli/_subprocess_compat.py
#	hermes_cli/agent_plugins.py
#	hermes_cli/auth.py
#	hermes_cli/backup.py
#	hermes_cli/banner.py
#	hermes_cli/browser_connect.py
#	hermes_cli/build_info.py
#	hermes_cli/cli_agent_setup_mixin.py
#	hermes_cli/cli_commands_mixin.py
#	hermes_cli/codex_models.py
#	hermes_cli/config.py
#	hermes_cli/config_defaults.py
#	hermes_cli/config_migrations.py
#	hermes_cli/container_boot.py
#	hermes_cli/dashboard_auth/registry.py
#	hermes_cli/debug.py
#	hermes_cli/dep_ensure.py
#	hermes_cli/doctor.py
#	hermes_cli/doctor_live.py
#	hermes_cli/dump.py
#	hermes_cli/env_loader.py
#	hermes_cli/foreign_sessions.py
#	hermes_cli/gateway.py
#	hermes_cli/gateway_windows.py
#	hermes_cli/gui_uninstall.py
#	hermes_cli/image_provenance.py
#	hermes_cli/install_identity.py
#	hermes_cli/kanban.py
#	hermes_cli/kanban_db.py
#	hermes_cli/linux_desktop_entry.py
#	hermes_cli/local_runtime/binaries.py
#	hermes_cli/local_runtime/endpoint.py
#	hermes_cli/local_runtime/growth.py
#	hermes_cli/local_runtime/supervisor.py
#	hermes_cli/logs.py
#	hermes_cli/macos_tcc_anchor.py
#	hermes_cli/main.py
#	hermes_cli/memory_setup.py
#	hermes_cli/model_catalog.py
#	hermes_cli/models.py
#	hermes_cli/nous_subscription.py
#	hermes_cli/npm_engine.py
#	hermes_cli/plugin_index.py
#	hermes_cli/plugins.py
#	hermes_cli/plugins_cmd.py
#	hermes_cli/profile_distribution.py
#	hermes_cli/profiles.py
#	hermes_cli/prompt_size.py
#	hermes_cli/psutil_android.py
#	hermes_cli/runtime_repair.py
#	hermes_cli/security_advisories.py
#	hermes_cli/security_audit.py
#	hermes_cli/security_audit_startup.py
#	hermes_cli/service_manager.py
#	hermes_cli/session_export_md.py
#	hermes_cli/setup.py
#	hermes_cli/skills_hub.py
#	hermes_cli/slack_cli.py
#	hermes_cli/status.py
#	hermes_cli/subcommands/gateway.py
#	hermes_cli/subcommands/uninstall.py
#	hermes_cli/tools_config.py
#	hermes_cli/uninstall.py
#	hermes_cli/update_cmd.py
#	hermes_cli/update_contract.py
#	hermes_cli/update_inventory.py
#	hermes_cli/update_lock.py
#	hermes_cli/update_receipt.py
#	hermes_cli/urllib_security.py
#	hermes_cli/web_routers/local_models.py
#	hermes_cli/web_routers/profiles.py
#	hermes_cli/web_routers/skills.py
#	hermes_cli/web_server.py
#	hermes_constants.py
#	hermes_state.py
#	plugins/disk-cleanup/__init__.py
#	plugins/disk-cleanup/disk_cleanup.py
#	plugins/google_meet/node/registry.py
#	plugins/google_meet/node/server.py
#	plugins/google_meet/process_manager.py
#	plugins/google_meet/realtime/openai_client.py
#	plugins/hermes-achievements/dashboard/plugin_api.py
#	plugins/memory/hindsight/__init__.py
#	plugins/memory/honcho/__init__.py
#	plugins/memory/honcho/cli.py
#	plugins/memory/honcho/client.py
#	plugins/memory/honcho/oauth.py
#	plugins/memory/honcho/session.py
#	plugins/memory/mem0/__init__.py
#	plugins/memory/mem0/_setup.py
#	plugins/memory/openviking/__init__.py
#	plugins/memory/retaindb/__init__.py
#	plugins/memory/supermemory/__init__.py
#	plugins/platforms/a2a/protocol.py
#	plugins/platforms/dingtalk/adapter.py
#	plugins/platforms/discord/adapter.py
#	plugins/platforms/feishu/adapter.py
#	plugins/platforms/google_chat/adapter.py
#	plugins/platforms/matrix/adapter.py
#	plugins/platforms/photon/adapter.py
#	plugins/platforms/photon/auth.py
#	plugins/platforms/photon/cli.py
#	plugins/platforms/slack/adapter.py
#	plugins/platforms/teams/adapter.py
#	plugins/platforms/telegram/adapter.py
#	plugins/platforms/wecom/callback_adapter.py
#	plugins/platforms/whatsapp/adapter.py
#	plugins/teams_pipeline/store.py
#	plugins/video_gen/fal/__init__.py
#	plugins/web/ddgs/provider.py
#	plugins/web/exa/provider.py
#	plugins/web/firecrawl/provider.py
#	plugins/web/parallel/provider.py
#	tests/agent/test_ssl_ca_guard.py
#	tests/hermes_cli/test_certifi_repair.py
#	tests/hermes_cli/test_cmd_update.py
#	tests/hermes_cli/test_cmd_update_apt.py
#	tests/hermes_cli/test_dashboard_unified_launch.py
#	tests/hermes_cli/test_dep_ensure.py
#	tests/hermes_cli/test_doctor.py
#	tests/hermes_cli/test_doctor_live.py
#	tests/hermes_cli/test_gui_command.py
#	tests/hermes_cli/test_kanban_boards.py
#	tests/hermes_cli/test_kanban_db.py
#	tests/hermes_cli/test_lazy_refresh_venv_repair.py
#	tests/hermes_cli/test_memory_setup_provider_arg.py
#	tests/hermes_cli/test_nous_subscription.py
#	tests/hermes_cli/test_pip_install_detection.py
#	tests/hermes_cli/test_profile_export_credentials.py
#	tests/hermes_cli/test_psutil_android_extract.py
#	tests/hermes_cli/test_status.py
#	tests/hermes_cli/test_tui_npm_install.py
#	tests/hermes_cli/test_update_fleet_restart_pending.py
#	tests/hermes_cli/test_update_head_moved_gate.py
#	tests/hermes_cli/test_update_interrupted_recovery.py
#	tests/hermes_cli/test_web_server.py
#	tests/hermes_cli/test_web_ui_build.py
#	tests/test_hermes_logging.py
#	tests/test_managed_runtime_resolution.py
#	tests/tools/test_browser_chromium_autoinstall.py
#	tests/tools/test_browser_chromium_check.py
#	tests/tools/test_browser_homebrew_paths.py
#	tests/tools/test_browser_lightpanda.py
#	tests/tools/test_browser_npx_warmup.py
#	tests/tools/test_browser_open_timeout.py
#	tests/tools/test_browser_orphan_reaper.py
#	tests/tools/test_browser_real_profile.py
#	tests/tools/test_browser_suspect_recycle.py
#	tests/tools/test_find_shell.py
#	tests/tools/test_local_env_blocklist.py
#	tests/tools/test_macos_protected_search.py
#	tests/tui_gateway/test_compute_host.py
#	tools/approval.py
#	tools/blueprints.py
#	tools/bot_mode_dm.py
#	tools/bot_mode_probe.py
#	tools/bot_relay.py
#	tools/browser_tool.py
#	tools/browser_use_cli.py
#	tools/checkpoint_manager.py
#	tools/code_execution_tool.py
#	tools/code_kernel.py
#	tools/computer_use/cua_backend.py
#	tools/cronjob_tools.py
#	tools/discord_tool.py
#	tools/environments/base.py
#	tools/environments/daytona.py
#	tools/environments/local.py
#	tools/environments/modal.py
#	tools/environments/vercel_sandbox.py
#	tools/fal_common.py
#	tools/file_operations.py
#	tools/lazy_deps.py
#	tools/mcp_tool.py
#	tools/neutts_synth.py
#	tools/process_registry.py
#	tools/read_extract.py
#	tools/registry.py
#	tools/skill_ledger.py
#	tools/skill_linter.py
#	tools/skill_manager_tool.py
#	tools/skill_usage.py
#	tools/skills_ast_audit.py
#	tools/skills_guard.py
#	tools/skills_hub.py
#	tools/skills_sync.py
#	tools/skills_sync_client.py
#	tools/skills_tool.py
#	tools/terminal_scope.py
#	tools/terminal_tool.py
#	tools/tirith_security.py
#	tools/transcription_tools.py
#	tools/tts_tool.py
#	tools/vision_tools.py
#	tools/voice_mode.py
#	tools/wake_word.py
#	tools/web_result_cache.py
#	tools/website_policy.py
#	tools/working_diff.py
#	tools/write_approval.py
#	tui_gateway/entry.py
#	tui_gateway/methods_tools.py
#	tui_gateway/server.py
2026-09-04 13:03:39 -04:00
ethernet
f2a6ce524b chore: no-op — refresh PR head ref 2026-09-04 09:12:54 -04:00
ethernet
cb25022783 fix(ci): strip CRLF from marked-test file list on Windows lanes
list_os_marked_tests.py's stdout, when redirected on Windows, carries
CRLF line endings (Python text-mode \n -> \r\n). The workflow's
'set -- ' word-split on the \r too, leaving it attached to
every path — pytest then failed 'file or directory not found:
tests/.../test_foo.py\r' on the first file. macOS passed because LF-only
there. Strip \r via 'tr -d' before word-splitting (no-op on POSIX).
2026-09-04 08:53:52 -04:00
Teknium
d3630f8532 refactor: whole-codebase simplification — −34% source LOC, every god file decomposed, zero behavior change (#102117)
refactor: whole-codebase simplification — −34% source LOC, every god file decomposed, zero behavior change
2026-09-04 05:50:52 -07:00
ethernet
95eba6d940 ci(tests): restore upstream split — full suite on Linux, OS-marked tests on their own lanes
The windows-tests merge made tests.yml a 3-OS matrix with the FULL
suite on Windows (pytest-xdist loadfile) — whose worker-crash flake
(INTERNALERROR on a random file every run) has been red-flagging the
branch. Per ethie: don't rip the windows-tests merge's test fixes, just
match upstream/main's CI shape. tests.yml is back to the Linux-only
96-core full-suite lane (per-file subprocess isolation); tests-os.yml
restored for the macOS/Windows marked-test lanes (plain pytest, small
set, no xdist — adapted 2 our platforms() marker convention with
'-m platforms and not integration'); ci.yaml wires tests-os back with
its desktop_updater input gate + all-checks-pass dependency.
2026-09-04 08:37:44 -04:00
Teknium
957710a119 fix(plugin-compat): scan the plugin's real package, never the CWD; override needs literal true
The compat scanner derived a directory from manifest.path by splitting at the
first ':' and falling back to .parent when the result was not a dir. An
entry-point plugin (`vendor_plugin:register`) and a Windows path
(`C:\Users\...`) both collapsed to '.', so a stray .py in the launch directory
was attributed to the plugin and the installed package was never scanned.
After the removal date that disables the wrong plugin. `_scan_root()` now
takes directory manifests verbatim and resolves entry points via
importlib.util.find_spec to the installed package dir; anything unresolvable
scans nothing.

`plugins.allow_deprecated_imports` used bool(), so the YAML string "false"
opened the post-removal bypass. It now requires the literal boolean True, and
summary_lines() says "force-loaded" instead of "DISABLED" when the override is
what kept the plugins running.

Reported-by: ayushnangia (PR #102117 review)
2026-09-04 04:56:11 -07:00
Teknium
38742342b2 fix(auth): qwen OAuth login reaches _mark_qwen_oauth_active in its moved owner
The compat sweep dropped the auth-facade re-export of _mark_qwen_oauth_active
while hermes_cli/auth_commands.py still reached it through the auth_mod module
alias, so every 'hermes auth add qwen' died with AttributeError on this branch.
Import it from hermes_cli/auth_qwen where it now lives. Module-alias attribute
reads dodge import-time checks; the sweep in the PR thread found this to be the
only production hit of that class.

Reported-by: yoniebans (PR #102117 review)
2026-09-04 04:37:08 -07:00
Teknium
2c6c645803 style(desktop): eslint/prettier pass on the plugin-compat notice (sort-imports, curly) 2026-09-04 02:45:43 -07:00
ethernet
ff685877f7 fix(pm): revert llamacpp pins 10679 → 10362 (b10679 darwin-arm64 links librdma.dylib)
PM Bundle darwin-arm64 lane failed verify: b10679's macos-arm64
llama binaries link librdma.dylib which doesn't exist on macOS — a
broken upstream release. 10362 was the known-good committed state that
passed every bundle lane. config_defaults' b10679 rolling tag is
aspirational; re-pin 10679 only when a darwin-working tag exists.
5890f3bbe3's re-pin was wrong — reverted.
2026-09-04 05:36:05 -04:00
ethernet
bb76156c08 test(gui): macos password-store test uses staging mock (was still [ok,ok])
The earlier adopt didn't replace this test's subprocess side_effect
(multi-line form evaded the string replace). Now mocks with
_pack_into_staging(root) so cmd_gui's pack produces the staged tree and
reaches the launch run — fixing the macos-lane IndexError.
2026-09-04 05:24:16 -04:00
ethernet
276d80dcae test(gui): adopt upstream stage-and-swap (#86443) mocks for cmd_gui tests
Our merged cmd_gui packs into a staging dir (stage-and-swap, #86443)
then renames over release/; the e97-era tests mocked pack w/ plain
CompletedProcess and never laid the staged exe down, so post-merge the
pack-then-verify path exited 1 (macos password-store test: IndexError,
no 2nd run). Adopted upstream's _staging_dir_from/_packaged_exe_rel/
_pack_into_staging helpers + the staging-aware test bodies for the
pack/launch + password-store family. Locally: 32 pass, 2 pre-existing
Windows-host failures (linux-marked tests exercising /usr/bin/npm
paths).
2026-09-04 05:16:03 -04:00
Teknium
4441a2a28d docs(agents): split AGENTS.md into root + per-area files (≤8k each, the subdirectory-hint cap)
Root AGENTS.md 100,797 → 29,295 chars: what applies everywhere (invariants, rubric, footprint ladder, layout + shape rules, commit/PR, testing) plus a routing table. Area rules move to agent/, hermes_cli/, gateway/, tools/, plugins/, tui_gateway/, web/, skills/, cron/, apps/desktop/src/ AGENTS.md (3–9k each; ceiling is now 32k after d61cff60e3, target ~8k). Long-form process-identity and skin key tables go to website/docs/developer-guide/cli-internals.md. Zero rule loss; map in /tmp/rf/agents_md_zero_loss.md. Stale Bot Mode test paths corrected to apps/desktop/src/plugins/hermes-bots/*.test.ts.
2026-09-04 02:12:35 -07:00
ethernet
dd68009673 fix(install): remove upstream stage-system splice that shadowed pm-era stages
The upstream merge concatenated upstream's owner-era stage system
(InstallStages array + Stage-Uv/Git/Python/SystemPackages wrappers +
Get-InstallStage/Step-OutOfInstallDir/Invoke-Stage) onto the pm-era
installer's stage functions. PowerShell last-def-wins meant the orphaned
wrappers (e.g. 'function Stage-Repository { Install-Repository }' where
Install-Repository exists NOWHERE) shadowed the real pm-era bodies —
every -Stage invocation crashed with 'not recognized'. Deleted the
entire upstream splice (lines 986-1221), restored Stage-Desktop wrapper
(Install-DesktopVoiceDeps + Install-Desktop); file parses clean and
each stage fn has exactly one def.
2026-09-04 05:04:26 -04:00
Teknium
d61cff60e3 fix(context): subdirectory AGENTS.md hints keep head+tail and warn when over the ceiling; ceiling 8k -> 32k
The on-demand subdirectory hint loader (agent/subdirectory_hints.py, #5291) tail-chopped anything past
8,000 chars with a bare marker and no log line. apps/desktop/AGENTS.md (11k) has been arriving cut off in
every Desktop-area session since it was written, and nobody could tell. Compared with the field: Codex
caps its whole instruction chain at 32 KiB (project_doc_max_bytes) and documents it; Claude Code and
Cursor apply no cap to nested files; OpenCode has no nested discovery at all. Our on-demand + cache-safe
+ ancestor-walk design is the strongest of the four; only the constant and the silent cut were wrong.

Now: ceiling 32,000 chars (Codex's number, a guard against a stray huge CLAUDE.md in a vendored tree, not
a target), and truncation goes through prompt_builder._truncate_content — head 70% + tail 20%, a marker
naming the file to read_file for the rest, and a WARNING in the log. Area AGENTS.md files should stay
around 8k anyway: the text lands in a tool result on the first touch of the directory.

Tests: oversized hint keeps head+tail, names the path, and logs; a 12k area file (over the old cap, under
the new) arrives intact.
2026-09-04 02:03:08 -07:00
ethernet
0ee0bd903e fix(icons): commit linux-truth regenerated icons; drop harvest temp
resvg-py 0.4.0 rasterizes identically per-host but the committed set was
generated on Windows (22440-byte icon.png) while the freshness lane runs
on linux (24200). Harvested linux regeneration via a temp artifact step,
committed it (all 23 targets now match linux output), reverted the temp.
2026-09-04 04:53:42 -04:00
Teknium
78288b488b compat(plugins): discovery itself refreshes the Desktop's report file
Live Desktop E2E (real Electron, worktree backend, demo plugin on old paths) found the modal never fired:
compat_report() was only called from the CLI banner / doctor / update / plugins-compat surfaces, none of
which run inside the Desktop's `serve` backend, so .plugin-compat-report.json was never written.
PluginManager.discover_and_load now refreshes the report from the manifests it just discovered (fail-open).

E2E after the fix, all five acceptance steps green on the real seat: report written and names the plugin;
native dialog 'Plugins need an update' with plugin, date, and `hermes plugins compat`; OK persists the
dismissal; relaunch with the same userData shows nothing and logs no 'compat notice shown'; fixing the
plugin's imports deletes the report and shows nothing.
2026-09-04 01:48:47 -07:00
ethernet
b485e87397 fix(ci): install.sh reads HERMES_INSTALL_DIR, not INSTALL_DIR
bootstrap-installer's sandbox stage set INSTALL_DIR but install.sh
resolves its root from HERMES_INSTALL_DIR (default $HOME/.hermes/
hermes-agent) — so every stage cloned/wrote to $HOME while the marker
check looked in runner.temp. Aligned all steps on HERMES_INSTALL_DIR.
2026-09-04 04:48:14 -04:00
ethernet
3b2882a799 temp(ci): harvest linux-truth icons artifact from freshness lane
resvg-py 0.4.0 rasterizes differently per host (windows 22440 vs linux
24200 bytes for icon.png) so the committed (windows-generated) icons
drift on the linux check lane. Temporary: upload the linux regeneration
as an artifact so the committed set can be refreshed from the host CI
checks against.
2026-09-04 04:36:50 -04:00
ethernet
111cef2dfd fix(installer): repair install.ps1 merge splice + bootstrap-installer mirror depth
install.ps1's merge resolution spliced Install-DesktopVoiceDeps/Install-Desktop
inside Stage-Complete's marker hash — parse error (MissingEqualsInHashLiteral)
broke every install.ps1 invocation incl. the protocol-surface CI lane and
6 managed-python provenance tests. Closed Stage-Complete, removed the dead
duplicate Stage-Desktop. bootstrap-installer.yml: checkout needs
fetch-depth 0 (shallow HEAD can't push to the bare mirror: 'shallow update
not allowed').
2026-09-04 04:35:27 -04:00
Teknium
064dcda706 compat(plugins): interactive CLI shows the banner notice only, not the raw per-name warnings
Live PTY check: the banner block named the plugin correctly but was preceded by one stderr
HermesPluginCompatWarning per moved name, duplicating it without the plugin name. warn_once now also
logs at WARNING (agent.log/gateway.log keep the record); cli.main() appends an ignore filter for the
category before plugin discovery. Appended, not overriding: -W error::...HermesPluginCompatWarning
(tests, plugin authors' CI) still wins, verified with the strict test run.
2026-09-04 01:33:40 -07:00
Teknium
0a5164cebe compat(plugins): tell users which installed plugins break on 2026-09-14, and stop loading them after
hermes_cli/plugin_compat.py is now the single source of truth for the compat window:
  COMPAT_REMOVAL_DATE = 2026-09-14; scan_plugin() statically finds `from F import n`, `import F` + `F.n`,
  alias forms and string targets against compat_manifest.json; compat_report() aggregates over the user's
  ENABLED external (non-bundled) plugins; disable_reason() decides the loader's skip.

Surfaces (all read from that one report):
  * CLI: yellow block under the banner naming plugins + date + `hermes plugins compat` (red + DISABLED after)
  * `hermes plugins compat [--json] [path]`: file:line, old -> new per hit; exit 1 while anything remains;
    `path` lets a plugin author scan their own checkout
  * `hermes doctor`: "Plugin import paths (removed Sep 14, 2026)" section next to the xAI retirement check
  * `hermes update`: post-update notice alongside the FTS/curator notices
  * Desktop: compat_report() writes HERMES_HOME/.plugin-compat-report.json (deleted when clean); Electron
    shows ONE warning dialog per distinct report after the backend is up and persists the dismissal in
    userData/plugin-compat-dismissed.json. A new affected plugin, or the date passing, is a new report.

From the date, PluginManager skips a hitting external plugin before importing it, with the reason in
LoadedPlugin.error ("uses N import path(s) removed on 2026-09-14; run `hermes plugins compat` ...") — the
same path a plugin with a broken register() takes, so nothing else is affected. Escape hatch:
plugins.allow_deprecated_imports: true (config_defaults), which only helps until the compat commit is
actually reverted.

Docs: COMPAT_MANIFEST.md (removal date, what-happens table, author instructions), plugin dev guide section.
Tests: tests/test_plugin_compat_notice.py (scanner forms, report scope, date gate + escape hatch, summary
text, report file lifecycle, loader skip via a real PluginManager), electron/plugin-compat-notice.test.ts
(show once, re-show on a different set or on the date passing, malformed file ignored).

Live A/B on this box with a demo plugin on old paths: before the date it loads and the banner/doctor/report
name it; with today=2026-09-14 it is skipped with the reason and the banner turns red; with the escape
hatch it loads again.
2026-09-04 01:28:31 -07:00
ethernet
9c859a6ba6 fix(desktop): eslint --fix merge artifacts (import sort + curly from conflict resolution)
The upstream merge's hand-resolved desktop files carried perfectionist
import-order + curly violations (26 errors, 220 problems). Autofixed;
typecheck + affected tests green.
2026-09-04 04:23:06 -04:00
ethernet
5b5306c426 fix(docker): copy hermes_constants.py beside pm for sealed-stage pm.cli install
The pm stage copies only pm/ then runs python3 -m pm.cli install uv
chromium chromium-headless-shell. pm/packages.py's uv_cache_dir()
lazily imports get_default_hermes_root() from hermes_constants
(unconditional, not ImportError-guarded like ensure.py's
hermes_cli.config) — with only pm/ on the path the stage died with
'No module named hermes_constants'. hermes_constants is stdlib-only;
COPY it next to pm (reproduced the failure shape locally: pm.cli
imports + install fine with pm/+HERMES_RUNTIME_DIR, fails the moment
uv_cache_dir resolves).
2026-09-04 04:20:07 -04:00
ethernet
588f65f956 fix(footguns): utf-8-sig on all reads flagged by check-windows-footguns
33 read sites across 25 files used BOM-intolerant encoding='utf-8'.
Windows tooling BOMs files it touches; json.load on a BOM'd file fails
with 'Expecting value'. Reads now use utf-8-sig (writes unchanged).
check-windows-footguns.py --all: 33 → 0.
2026-09-04 04:15:10 -04:00
ethernet
191c6c6648 test(install): restore test-install-ps1-longpath.ps1 lost in pm-era deletion sweep
installer-tests.yml (matching upstream) calls
test-install-ps1-longpath.ps1 + test-install-ps1-node-compatibility.ps1;
only the node-compat one survived the pm-era sweep + merge resolution.
Restored from upstream/main (323 lines, 8.3 short-path normalization
test that RUNS install.ps1 rather than parsing it).
2026-09-04 04:13:19 -04:00
ethernet
7df0b56e7a fix(deps): winrt [all] extra does not exist — declare both namespaces, drop bogus extra
check-appinstaller-update.py imports winrt.windows.applicationmodel
(Package/PackageUpdateAvailability) AND winrt.windows.management.
deployment (PackageManager) — separate winrt-windows-* distributions.
'[all]' on winrt-windows-management-deployment made uv lock reference
the whole family WITH an 'all' extra none of them ship ('does not have
an extra named all'), desyncing uv.lock so every '--locked' CI sync
failed. Declare both namespaces plain (>=3.2.1,<4; win32-gated);
uv lock regenerated (0 bogus extras, 6 winrt entries); lock --check
passes; CI's exact extra set resolves.
2026-09-04 04:11:26 -04:00
ethernet
559da18132 fix(ci): move runner.temp job-env to step level — graph dispatch killer
bootstrap-installer.yml declared job-level env with ${{ runner.temp }},
which GitHub only allows at step level. Any ci.yaml dispatch validated
the whole reusable-workflow graph incl. this callee and failed with
0 jobs (invisible silent failure — no jobs, no logs). Our merge wired
this lane into ci.yaml, surfacing the latent bug. Actionlint flagged it;
moved INSTALL_DIR/HERMES_HOME/HERMES_RUNTIME_DIR to the steps that use
them.
2026-09-04 04:05:42 -04:00
ethernet
096e4dbe68 fix(icons): regenerate all 23 targets from icon-master.svg (audit RED: --check drift)
generate_icons.py --check was RED on 23 targets — committed files
diverged from icon-master.svg output (large stale binaries: logo.png
1.3MB→60KB, icon.icns 1.5MB→94KB). Regenerated via the single-master
pipeline; --check now passes. Also gates the icons-freshness-check CI
lane that ci.yaml calls.
2026-09-04 03:59:04 -04:00
ethernet
bf029cb147 fix(pm-bundle): drop dead shim check from payload smoke test
smoke-payload.sh's third check ran '../bin/hermes' — a self-relative CLI
trampoline minted only by the desktop release workflow
(build-bundled-desktop.mjs → mint-launchers.py). 'pm bundle --out' stages
repo+store+venv only, so the shim never exists and every pm-bundle lane
died at that check (exit 127) since the workflow's birth (Aug 28, same
error on every branch). Keep the real boot checks (store python imports,
hermes_cli entrypoint --version, pm doctor); drop the shim check.
2026-09-04 03:57:14 -04:00
Teknium
1392e4b857 test: modal integration test imports terminal helpers from their defining modules
It side-loaded tools/terminal_tool.py via importlib.spec_from_file_location under the bare name
'terminal_tool', which bypassed both the package and scripts/check_compat_pointers.py, then read
cleanup_vm through the compat pointer. Found by running the suite with HermesPluginCompatWarning
promoted to an error — the only in-tree resolution through a pointer across 44,7k tests.
2026-09-04 00:53:42 -07:00
ethernet
81eb707b9e fix(merge): adopt upstream linux_desktop_entry icon/launch rewrite; keep pm-era gui tests
Upstream's linux_desktop_entry.py (806 lines) supersedes the pm-era
227-line version: hicolor icon install with PNG resize, launch-context
independent Exec, running-interpreter venv-semantic resolution. Take it
wholesale; _repo_pythonpath_entry had no external callers.

test_linux_desktop_entry: use upstream's icon-fallback test + keep the
quote-aware Exec assertion (Windows paths trigger _quote_exec_arg).

test_gui_command: restore pre-upstream-merge version — our main.py is
the pm-era one, so upstream's newer tests expect behavior we don't have;
2 remaining failures are pre-existing Windows-host npm-seam issues.
2026-09-04 03:36:09 -04:00
ethernet
f6d88e4151 fix(ssl): drop env-ladder rungs that re-entered resolve_httpx_verify in the upstream merge
The upstream/main merge re-added HERMES_CA_BUNDLE/SSL_CERT_FILE/
REQUESTS_CA_BUNDLE/CURL_CA_BUNDLE fallbacks to resolve_httpx_verify —
the exact env-ladder the truststore port (2f20ceb6f7) removed. Explicit
per-provider ssl_ca_cert is the only thing above the platform store.
test_env_ca_bundle_vars_no_longer_steer_trust caught it red.
2026-09-04 03:22:42 -04:00
ethernet
642579db60 Merge remote-tracking branch 'upstream/main' into ethie/pm-clean
# Conflicts:
#	.github/actions/detect-changes/action.yml
#	.github/workflows/ci.yaml
#	.github/workflows/tests-os.yml
#	agent/prompt_builder.py
#	agent/ssl_verify.py
#	agent/subdirectory_hints.py
#	apps/desktop/electron/main.ts
#	apps/desktop/electron/preload.ts
#	apps/desktop/src/app/settings/about-settings.tsx
#	apps/desktop/src/global.d.ts
#	apps/desktop/src/i18n/ar.ts
#	apps/desktop/src/store/updates.ts
#	cron/suggestions.py
#	gateway/channel_directory.py
#	hermes_cli/config.py
#	hermes_cli/doctor.py
#	hermes_cli/linux_desktop_entry.py
#	hermes_cli/main.py
#	hermes_cli/web_routers/profiles.py
#	hermes_constants.py
#	plugins/platforms/photon/adapter.py
#	scripts/ci/classify_changes.py
#	scripts/install.ps1
#	tests/agent/test_relay_runtime_plugins.py
#	tests/ci/test_classify_changes.py
#	tests/hermes_cli/test_gui_command.py
#	tests/hermes_cli/test_linux_desktop_entry.py
#	tests/hermes_cli/test_update_fleet_restart_pending.py
#	tests/state/test_fts_runtime_rebuild.py
#	tests/tools/test_lazy_deps.py
#	tests/tools/test_macos_protected_search.py
#	tools/browser_tool.py
#	tools/file_operations.py
#	tools/lazy_deps.py
#	tools/mcp_tool.py
#	tools/working_diff.py
#	uv.lock
2026-09-04 03:18:16 -04:00
Teknium
d63e380324 compat(plugins): warn once per name when a plugin resolves an old import path; lint step restored in CI
Every PLUGIN-COMPAT __getattr__ now calls hermes_cli.plugin_compat.warn_once(facade, name, target) before
resolving, emitting a HermesPluginCompatWarning (FutureWarning) once per process per name: old path, new
path, removal target. Importing a facade for its live API stays silent; only resolving a moved name warns.
COMPAT_MANIFEST.md documents the warning and how to silence it during migration.

Verified the runtime never routes through a pointer: every entry point (run_agent, cli, hermes_cli.main,
gateway.run, tui_gateway.server, web_server, model_tools + tool discovery, hermes_state, cron.scheduler,
browser_tool, mcp_tool, kanban, auth) imports clean and `hermes doctor` runs end to end with the warning
promoted to an error.

Also restores the check_compat_pointers CI step to .github/workflows/lint.yml, which a0be177aac dropped
when the compat layer was regenerated (the lint script itself was present; the workflow step was not).

hermes_cli/plugin_compat.py, tests/test_plugin_compat_warning.py and the two-line insert per facade are
part of the compat layer and go away with it.
2026-09-04 00:15:16 -07:00
Teknium
4ab96371d2 docs: sync developer-guide tooling/gateway/CLI +
user-guide with the facade/siblings layout (#102117)
2026-09-04 00:14:22 -07:00
Teknium
66d478c6d3 docs: sync root docs, CONTRIBUTING tree, docs/*.md,
windows-quirks with the facade/siblings layout (#102117)
2026-09-04 00:14:22 -07:00
Teknium
4fb332b427 docs: sync developer-guide agent-core docs with the facade/siblings layout (#102117) 2026-09-04 00:07:14 -07:00
ethernet
a4bb1a9de0 test(desktop): i18n key-parity guard — the class-fix for missed-locale renames
Add a vitest that flattens every locale's key tree and fails when a locale
declares a key whose en section is still live but no longer has it — the
recurring 'missed locale' bug family (view.terminalSelection →
view.selectionToComposer missed ru.ts for weeks). Sections en empties
entirely (platformIntro: {} — free-form Record overrides read before a
code fallback const) are exempt.

The guard immediately caught live drift, now fixed:
- ru: view.terminalSelection → view.selectionToComposer (translation
  preserved — this is the audit's headline i18n gap)
- ru: dropped stale settings.model.tasks.web_extract (removed from en's
  task list)
- ar: dropped stale keybinds.actions.view.closePreviewTab,
  onboarding.flowSubtitles.loopback, sidebar.nav.{agents,chat,settings}

34 i18n tests + renderer tsc clean.
2026-09-04 03:02:57 -04:00
Teknium
27a4023791 docs+evals: AGENTS.md for the facade/siblings layout; codebase-navigability benchmark harness
AGENTS.md: Project Structure tree reflects the decomposition (run_agent 1.5k not 12k, cli 4.6k not 11k,
hermes_state facade + 21 siblings, web_routers/, evals/, test counts); new "Facade + siblings layout"
section with the sibling families table and the rules that follow (find by topic, patch where production
reads, compat pointers off limits, don't recreate god files); AIAgent/Agent Loop point at agent/turn_*.py
and conversation_loop; CLI dispatch documents _SLASH_DISPATCH + the _handle_<name>_command convention and
"Adding a Slash Command" no longer tells you to add an elif (there is no ladder to add to on either surface).

evals/codebase_navigability/: what the codebase costs an agent, not the CPU.
  bench.py         ~19k real "locate X" tasks from tests/ imports; tokens (tiktoken o200k) of the defining
                   file vs the symbol, context-window fit, read windows, siblings, symbol CC
  lookup_sim.py    paired grep+read simulation over 4k common symbols; tool calls + tokens returned
  static_metrics.py LOC split, size distributions, elif/nesting, radon CC/MI, import graph + SCC cycles
  runtime_bench.py  fresh-interpreter import/CLI/hot-path/collection timings with tree-purity assertion
tests/evals/test_codebase_navigability.py pins the resolver's facade/sibling behaviour.
2026-09-03 23:55:56 -07:00
ethernet
d967e9ed2b fix(local-models): restore pm.downloader router integration clobbered by upstream merge
Upstream's 43e67d872f (feat: local models) reintroduced the old bespoke
ranged-parallel machinery (download_file/_probe_range_support/
_DOWNLOAD_CONNECTIONS) into the local-models router and deleted the
pm.downloader-based _download_job plus the pause/resume routes.

Re-swap all 3 call sites (model download, runtime-install leg, browsed
download) onto pm.downloader.Download via _download_job: one resumable
8-way parallel job per plan, progress via the shared tick callback,
partials in the managed partials root. Restore /download/pause +
/download/resume routes with the _RUNNING handle registry (dl handle
kept off the JSON job dict; resume re-runs the job body).

Restore the pre-clobber route tests (FakeRangeOpener stands in for
pm.downloader._OPENER with honest Range support) incl. the pause /
resume / finished-job-releases-handles coverage.
2026-09-04 02:33:49 -04:00
ethernet
5890f3bbe3 fix(pm): re-pin llamacpp backends 10362 → 10679
Session 20260828_194913 bumped all four backends to b10679 with real
hashes; the pm-clean rebuild reverted the pins to 10362 while
config_defaults/binaries still reference b10679 rolling tags. Re-run
pm update (the designed resolution path) — lock now 19× 10679, 0× 10362.
2026-09-04 02:10:31 -04:00
ethernet
492e9c6688 chore: re-rip tracked test.txt (UTF-16 pip-freeze junk)
Ripped in-session (21d36d5c1a), re-added by the pm-clean rebuild
(3d12e86ef1). It is a 52 KB UTF-16 pip-freeze dump that belongs at
%LOCALAPPDATA%\Temp, not in the repo.
2026-09-04 02:09:00 -04:00
ethernet
9c80d20f8b docs: fix stale packaging/SSL/PHOTON references
- desktop.md: dist:win is MSIX-only, not NSIS+MSI
- BUILDING.md: sign-nested-chromium is LIVE (after-pack.mjs wires it), not dead
- pyproject: lazy_deps.py comment -> pm
- photon docs: drop dead PHOTON_NODE_BIN rows (adapter is pm-store-first);
  restore PHOTON_MENTION_PATTERNS row my earlier edit wrongly removed
- urllib_security/models docstrings: SSL_CERT_FILE/certifi fallback ->
  platform trust store (post truststore port)
2026-09-04 02:08:39 -04:00
ethernet
fdafef44a5 fix(version_info): degrade to unknown instead of crashing on a malformed stamp
_stamp_version_info raises RuntimeError when install-stamp.json has a
missing/illegal updateMechanism or a mispackaged 'light' payload. Every
uncached caller of get_version_info died on that. Wrap the call so a
malformed stamp falls through to git/unknown provenance; the authoring
build lane's own tests still surface the bad stamp.
2026-09-04 02:01:34 -04:00
ethernet
2f20ceb6f7 refactor(tls): trust the OS certificate store, one resolver
Cherry-pick of 03b45db777 from ethie/bundles-local-models: TLS trust was
five hand-rolled ladders (agent/ssl_verify, hermes_cli/auth,
agent/model_metadata, hermes_cli/urllib_security, gateway/run's SSL_CERT_FILE
mutation) all ultimately pointing at certifi's frozen list. Trust now comes
from the platform verifier via truststore: CryptoAPI on Windows,
Security.framework on macOS, OpenSSL's store on Linux; install_truststore()
patches ssl.SSLContext process-wide. agent/ssl_verify.py is the one
authority; agent/ssl_guard.py deleted.

Also closes the session-flagged coverage gap: hermes_cli/main.py (CLI
entrypoint) and tui_gateway/entry.py now call install_truststore() so
subcommands/help that never construct an AIAgent still get OS-store trust.
2026-09-04 01:57:30 -04:00
ethernet
4486fe1e30 test(merge-fix): drop windows-tests' dep_ensure/npm_engine divergent tests; make Exec assertion quote-aware
- test_dep_ensure: revert to pm-clean version (windows-tests' _DEP_CHECKS
  test targets a different dep_ensure API)
- test_linux_desktop_entry: Exec= is quoted per desktop-entry spec when
  the path has reserved chars (Windows backslashes) — parse instead of
  literal compare
2026-09-04 01:47:16 -04:00
ethernet
7d71bec845 test(npm_engine): keep pm-clean version — windows-tests deferral tests target the removed managed-node-tree system 2026-09-04 01:42:19 -04:00
ethernet
0f5c7803f6 fix(tests): resolve windows-tests merge conflicts + platform() marker migration
- Merge ethie/windows-tests: keep host-dispatch runner (xdist win /
  per-file posix) + NixOS env passthrough; drop tests for dead systems
  (lazy_deps, termux, managed_uv, old node bootstrap ladder)
- Migrate remaining windows_only/linux_only/macos_only markers to
  platforms(); add missing sys import in test_linux_desktop_entry
- conftest: scrub PYTHONPYCACHEPREFIX so pytest assertion-rewrite .pyc
  mirrors land beside sandboxed sources, not the REAL hermes home
  (desktop-spawned shells export it; tripped the real-home tripwire)
- test_tui_npm_install: mock find_node_executable + _ensure_tui_workspace
  seams (post pm-store-node refactor); test_approval: drop MAX_SAFE
  assert conflicting with our 40-day fallback
2026-09-04 01:39:07 -04:00
ethernet
e97de8d8c3 Merge branch 'ethie/windows-tests' into ethie/pm-clean
# Conflicts:
#	.gitignore
#	agent/deadline.py
#	pyproject.toml
#	scripts/run_tests.sh
#	tests/agent/lsp/test_install_and_lint_fixes.py
#	tests/cron/test_file_permissions.py
#	tests/cron/test_media_delivery_parity.py
#	tests/cron/test_script_claim_heartbeat.py
#	tests/hermes_cli/test_dep_ensure.py
#	tests/hermes_cli/test_gateway_wsl.py
#	tests/hermes_cli/test_linux_desktop_entry.py
#	tests/hermes_cli/test_npm_engine.py
#	tests/hermes_cli/test_runtime_repair.py
#	tests/hermes_cli/test_tui_npm_install.py
#	tests/test_hermes_constants.py
#	tests/test_install_autostash_conflict_recovery.py
#	tests/test_install_macos_launcher.py
#	tests/test_install_sh_acp_launcher.py
#	tests/test_install_sh_bootstrap_marker.py
#	tests/test_install_sh_node_deps_failure.py
#	tests/test_install_sh_symlink_stomp.py
#	tests/test_windows_subprocess_no_window_flags.py
#	tests/tools/test_approval_timeout_overflow.py
#	tests/tools/test_browser_homebrew_paths.py
#	tests/tools/test_find_shell.py
#	tests/tools/test_lazy_deps.py
#	tests/tools/test_lazy_deps_durable_target.py
#	tools/approval.py
#	uv.lock
2026-09-04 01:06:06 -04:00
Teknium
a0be177aac fix(compat): pointers resolve to the object that MOVED, not a same-named stranger; stdin checker binds stdin= to the splatted definition
Review findings on #102117 (independent reviewer + itsflownium):

* hermes_cli.kanban_db.connect / connect_closing pointed at hermes_cli.projects_db (different DB, no
  board= parameter). The compat generator ranked candidate homes by path proximity when a name is
  defined in several modules. Now it requires shape compatibility with the BASE definition (same
  literal for constants, superset of parameter names for defs) and prefers the facade's own
  <stem>_* sibling. Same class fixed for tools.tts_tool.DEFAULT_XAI_BASE_URL (-> tts_tool_providers),
  and 17 constants/defs that had been pointed at same-named strangers (Matrix MAX_MESSAGE_LENGTH ->
  Signal's 8000, tts MAX_TEXT_LENGTH -> BlueBubbles', honcho/retaindb/supermemory *_SCHEMA -> another
  plugin's schema, ...) are now restored from BASE verbatim instead.
* send_yuanbao_direct (restored-def): body called adapter._outbound.send_direct, which HEAD moved to
  the sender; rewritten to adapter._outbound.sender.send_direct.
* COMPAT_MANIFEST.md states the scope explicitly: public top-level names only; private names and
  test monkeypatch seams are not preserved.
* scripts/check_subprocess_stdin.py: _splat_carries_stdin looked 30 lines ahead in the file text
  and was satisfied by an unrelated later stdin=; it now finds the splatted name's definition via AST
  and requires stdin inside that expression/body.

Tests: tests/test_compat_manifest_targets.py (pointer identity vs the facade's sibling; kanban
connect(board=) opens a Kanban DB, not projects.db; both FAIL on the previous layer),
test_subprocess_stdin_guard gains the false-negative probe, and the MoA -Q quiet-output contract
tests are back (tests/agent/test_moa_quiet_reference_output.py) against build_moa_facade.
2026-09-03 22:00:01 -07:00