Commit Graph

81 Commits

Author SHA1 Message Date
ethernet
9ee4397408 fix: round-9 Windows lane — retry argv keeps --force; mint fixture carries venv_sync; stamp probe reports the child
- desktop-update/windows.ps1: the legacy-install retry re-sends the identical request
  (--force included); the contract test compares both attempts.
- test_mint_launchers: the bootstrap imports hermes_cli.venv_sync/steward before
  prepare_launch can return early for a fixture repo; copy them into the tree.
- test_source_build_env: when the pwsh child writes no stamp, fail with the child's
  stdout/stderr instead of a bare FileNotFoundError (the Windows lane hides the cause).
2026-09-21 06:48:59 -04:00
ethernet
f7561667c7 fix: round-8 CI backlog (Windows-only lane)
- tests/conftest.py: `real_bash` fixture — the Windows runners resolve `bash` to System32's
  WSL launcher (UTF-16 "no installed distributions", exit 1); prefer Git for Windows'. Used
  by the setup-pin, install stage-frame and source-launcher shell tests.
- source-build-env.ps1: Test-Path before Remove-Item — under $ErrorActionPreference='Stop'
  a missing identity variable aborted the try block before the child ran (Windows PS 5.1
  raised where pwsh on Unix did not).
- desktop-update/windows.ps1: `--force` precedes the target arguments (the hand-off contract
  test reads argv in that order).
- test_install_ps1_desktop_stage: assert the current contract — the shared completion tail
  (source_completion.py --desktop) builds the products and -IncludeDesktop selects the desktop
  product inside `products` rather than adding a stage. The test predated the completion-tail
  refactor and had been red on the Windows lane since.
- test_windows_native_support: the restart watcher argv is `runtime_command` shaped
  ([python, -I, -c, bootstrap, pid, delay, ...]).
- test_mint_launchers: create the fixture repo's pm/ dir before copying pm/environments.py.
- test_browser_use_pm: console-script launchers report sys.argv[0] without `.exe`.
- test_update_stale_gateway_yield (from main): `_verify_fleet_after_update` has no
  `node_failures` here (PM owns node).
2026-09-21 06:03:05 -04:00
ethernet
f3b1399211 fix: round-5 CI backlog after the 779-commit main merge
Merge fallout (my resolution errors, all caught by CI):
- hermes_cli/backup.py + gateway.py: `theirs` on those hunks re-imported clusters HEAD had
  already moved to backup_restore.py / kept in the facade. backup.py loses the 349-line
  duplicate (main's #110179 fix is ported into backup_restore._import_db_member); the
  systemd service-unit cluster returns to gateway.py (PM's _prepare_service_launcher /
  _pm_managed_node_dirs / _systemd_command have no home in main's extraction) with main's
  utf-8-sig read. gateway_service_unit.py is dropped.
- gateway/run.py: main's plugin-update chore is not profile-scoped (the housekeeping
  ordering test pins the scope/drain sequence).
- pyproject + 30 test files: `import yaml` -> `import hermes_yaml as yaml` (pm-clean has no
  pyyaml); gateway/config._bundled_platform_manifest_name reads through hermes_yaml.
- tests re-seamed onto pm-clean's shape: residency admission (installed_engine),
  supervisor child env (binary is a constructor argument), update import guard
  (update_cmd_deps is gone; our probe already scrubs PYTHONPATH — both #115032 invariants
  pass), shallow-count git responses (stash path asks `status --porcelain -z`); dropped
  tests for retired code (_run_node_bootstrap/_ensure_tui_node, Windows resume demotion).
- tests/tools/test_local_env_blocklist.py: restore the two helpers the suite-reduction
  commit dropped and the blocklist import.

Real fixes:
- pm: classify_uv_failure/ResolutionConflict move beside the uv runner (pm.environment,
  stdlib-only). pm.workspace imports tomllib at module level and cannot load on the 3.10
  bootstrap python that streams uv output in the Docker arm64 image.
- tools/browser_tool.warm_agent_browser_npx_cache: back as a permanent definition — it is on
  the frozen old-updater surface, and the revert-scheduled compat pointer does not count.
- hermes_cli/memory_setup: the dashboard's pip row uses pm.environments.
  running_from_selected_environment for installed vs restart_required.
- scripts/windows-build-deps.ps1: export DISTUTILS_USE_SDK/MSSdk so setuptools trusts the
  primed MSVC environment instead of asking vswhere (`env -i` test runner on win32-arm64
  compiling ruamel-yaml-clib); run_tests.sh forwards them.
- tests/pm/test_windows_build_deps.py: start the protocol test from a parent env without the
  toolchain variables the runner job already exports.
- tests/conftest.py scrubs HERMES_BUNDLED_PLUGINS (Nix-wrapped hermes on the dev host);
  tests/home_io_guard.py treats sys.path site-packages under the real home as the
  interpreter's installation (PM-activated developer shell).
- tests-js: four `curly` lint errors from main's new scripts.
2026-09-21 02:47:48 -04:00
ethernet
9f2ba1b74d merge origin/main (779 commits) into ethie/pm-clean
Branch semantics kept where main and PM disagree: update_cmd_deps.py,
constraints-termux.txt, the Electron update-api-check module and the
post-swap hand-off test stay deleted; the pending-fleet-restart catch-up
and the local_runtime tag/download ladder stay retired (PM owns engines).

Ported from main onto the branch's shape: profile_scoped_chore for the
auto-archive and plugin-update housekeeping chores, the local-runtime
cross-process boot lock and residency cap, the checkpoint tmp_pack sweep,
the cua daemon-liveness status probe, the remote-served Desktop update
flag (posix.sh / windows.ps1), sign-in for env-pinned remote gateways
(urlDisabled on RemoteSetupFields), the uvloop extra split (uvicorn
without [standard]), and the umask-scoping spawn test.

uv.lock regenerated with pm.build_env --lock-only; new utf-8 reads from
main switched to utf-8-sig (check-windows-footguns).
2026-09-21 00:58:39 -04:00
liuhao1024
04845f5f3e fix(desktop): skip the local gateway restart on update when the Desktop is remote-served
A Desktop whose active connection is remote (SSH/remote/cloud, including the
registry primary) owns no local messaging gateway, yet the update hand-off
always ran `hermes update --gateway`. On hosts where launchd/service recovery
fails, the updater falls back to a detached local `gateway run --replace`;
with the same Telegram bot token as the remote VPS gateway, the two processes
compete for getUpdates and Telegram rejects one consumer, taking the
production bot offline (#117529).

Pass the ownership down the hand-off: globalRemoteActive() now adds
--no-gateway (posix) / -NoGateway (windows) when the Desktop is remote-served,
and both orchestrators drop --gateway from every update invocation (initial +
retry). The local-ownership default keeps --gateway exactly as before.
2026-09-20 19:30:16 -07:00
joaomarcos
f58605b5c6 fix(desktop): keep Windows update hand-off hidden 2026-09-20 11:14:36 -07:00
ethernet
e1576d06a6 Merge remote-tracking branch 'origin/main' into ethie/pm-clean
Resolved toward the branch: PM provisions uv/python (main's install.ps1 uv-shim
salvage + its test and workflow steps dropped), the shim re-exec stays retired,
package.json carries no electron-builder block (afterExtract identity stamp wired
into electron-builder.config.cjs instead; after-pack.mjs keeps signing only),
Desktop workspace-deps helpers stay retired. Main's scratch-dir bootstrap
(export_scratch_tmp_env) is taken and re-run after profile resolution.
2026-09-19 22:57:07 -04:00
teknium1
e16fee4db1 refactor: resolve runtime temp paths via tempfile/TMPDIR instead of literal /tmp
Hermes now routes scratch space through HERMES_HOME/cache/scratch (exported as
TMPDIR), so every production path that still spelled out /tmp bypassed that and
kept teaching the agent the habit. Fallbacks in tool_result_storage,
code_execution_tool, process_registry, the ACP child HOME, mini_swe_runner's
local cwd, and the CI/profiling scripts now use tempfile.gettempdir(); shell
installers fall back to $TMPDIR (then HERMES_HOME) when mktemp is missing, and
repro/eval shells use `mktemp -d -t`. User-facing help text and sample payloads
(hermes send, approvals test, hooks test, voice-mode WSL hints, meet_bot debug
line) no longer suggest /tmp.

Container-side paths (mini_swe_runner docker cwd, sandbox base env, remote
sync tarballs) keep the literal because they name the sandbox filesystem,
not the host.
2026-09-19 10:44:26 -07:00
ethernet
79e6643ee3 fix(desktop-update): center the panel labels and make linger a real sleep
wrappingLabel's alignment must reach the cell — setAlignment on the
field alone left the title and stage line left-aligned inside
full-width frames ('Updating Hermes' starting at center-looking-box
left edge). Labels now get full-width frames and cell-level centering.

linger() also switched from a runloop pump to a blocking
NSThread.sleepForTimeInterval: with the animation stopped there are no
timer sources to service, so the pump returned immediately (terminal
states lived <1s instead of the designed 1.5s/15s).
2026-09-18 01:17:48 -04:00
ethernet
1cf9d27b81 feat(desktop-update): port the status panel to JXA with the Fourier Flow loader
The AppleScript panel rendered a stock NSProgressIndicator spinner and
could not port the shim's curve (AppleScript has no trig). Rewrite as
JavaScript for Automation: the ui.html curve math moves over
character-for-character (real JS), rendered into an 80x80 NSImage per
pump tick and swapped into an NSImageView — the JXA analog of the
page's requestAnimationFrame loop. Title, stage line, dark/light
seeds, terminal glyphs and the lingering rules all match ui.html's
apply().

JXA bridge notes baked into the code: variadic NSArray selectors do
not bridge (use the $() JS-array bridge), color selectors must use
bundled names (colorWithCalibratedRedGreenBlueAlpha), and 'delay' is a
reserved JXA global. spawn sites (posix.sh start_status_panel,
update_stage.ensure_panel) pass -l JavaScript; the .applescript panel
is deleted.

Verified on a macOS host: animates without beachballing, self-exits
~2s after a done publish and ~2s after the status file vanishes.
2026-09-17 22:59:02 -04:00
ethernet
61839f052f fix(desktop-update): the status panel must exit when the status file vanishes
On a real update the panel stayed on 'Installing the new app' after the
app relaunched: the shim publishes 'done' and then removes the status
file, and the panel treated a missing file as 'keep waiting' — it only
ever exited if a poll happened to land in the half-second window before
the removal. Race lost, panel immortal.

A disappearance AFTER the file has been seen to exist now means done
(the shim removes the file only after publishing a terminal state and
tearing down its UI; an error publish keeps the file alive through the
15s leave-window grace, so a failure cannot be masked). A file that
never existed still means 'no status yet'. stop_ui also resets
UI_PANEL_PID with the other UI handles.

Verified on a macOS host: the panel compiles, survives while the file
exists, and self-exits ~3s after the file vanishes.
2026-09-17 22:35:10 -04:00
ethernet
a58e1f377b fix(desktop-update): pump the runloop so the status panel repaints
The panel polled the status file with 'delay 0.5', which blocks the
main runloop: AppKit never repaints, the label stays on its initial
'Preparing update...' and macOS beachballs the window - observed on
the first real desktop update, where the stage publishes were landing
in the status file but the panel never showed them. Drain
NSDefaultRunLoopMode for the poll interval instead, so label updates
and the progress animation render between ticks.
2026-09-17 19:39:23 -04:00
ethernet
aaf5889207 feat(update): stream long update stages into the desktop hand-off UI
The shim renders progress from its status JSON file, but everything
after 'Updating code and dependencies' — the PM dependency sync, Node
deps, the TUI/web/desktop builds — ran for minutes without touching
that file, so the window froze on a stale stage (or showed nothing at
all when the old shim skipped its browser window).

hermes_cli/update_stage.py publishes stages to the watching UI,
std-only and never raising. Two discovery paths: the exported
HERMES_UPDATE_STATUS_FILE (current shim), and, for an OLD shim that
never exported it (every old-to-new checkout transition), the marker's
owner pid, which names the shim whose status file is deterministically
/tmp/hermes-update-status.<pid>. On macOS the takeover child also pops
update-panel.applescript from the freshly pulled tree when the old
shim's log shows it started no renderer.

Publishes: PM sync (_update_takeover.prepare, venv_sync.sync), Node
deps and each product build (source_build.build_update_products).
Only 'running' stages are ever written — terminal states stay the
shim's.
2026-09-17 18:58:42 -04:00
ethernet
2f0ed3780b feat(desktop-update): native status panel when no Chromium renderer exists
On macOS the shim only rendered through Chrome/Chromium honoring the
default-browser rule, so Safari/Firefox users (most of them) watched
the app quit and the update run invisibly - 'shim: no renderer;
skipping UI'. Add update-panel.applescript: an osascript/AppleScriptObjC
panel (NSWindow + label + indeterminate NSProgressIndicator, accessory
policy, no Dock icon) that polls the same status JSON write_status
emits - no HTTP server, no browser, no other-app scripting, hence no
TCC automation prompt.

start_status_panel is best-effort: osascript absent or the panel dying
instantly (headless session) falls back to today's UI-less behavior.
The panel self-exits after a terminal state (done after 1.5s, error/
manual after the leave-window grace so the message is readable) and
stop_ui KILLs it on early teardown, matching the SIG_IGN-survives-exec
contract of the HTTP server. Status fields are extracted with grep -
NSJSONSerialization's by-ref |error|: label does not parse under
osascript, and write_status output is flat JSON we control.

Verified on a macOS host: script compiles clean under osacompile; the
status reader returns running/done/missing/garbage correctly; GUI
rendering itself requires a WindowServer session (headless ssh cannot
show it) - first real desktop update exercises that path.
2026-09-17 18:28:55 -04:00
ethernet
b4a294fff9 Merge origin/main; keep PM as plugin dependency owner
Reconcile plugin declarations and validation through PM's atomic generation publication; preserve external runtimes, target markers, and conflict refusal. Keep one source-update completion owner and port upstream lifecycle changes to the PM desktop/runtime paths.
2026-09-17 13:52:05 -04:00
teknium1
659f5ae94f fix(update): keep .venv installs whole through ZIP fallback and venv repair
Follow-up to the cherry-picked #112966 so the uv-default `.venv` layout is
supported end to end, not only at the lookup sites:

- `_ZIP_PRESERVED_TOP_LEVEL` gains `.venv`. The dirty-tree guard runs
  `git status --ignored=matching`, so a gitignored `.venv/` surfaced as
  `!! .venv/` and refused every ZIP fallback on such installs ("the working
  tree has uncommitted changes or untracked files") — the live runtime was
  being treated as user data the overlay would destroy.
- `_repair_venv_on_current_checkout` recreates the venv at the resolved
  directory instead of a literal `venv`, so a broken `.venv` is rebuilt in
  place rather than growing a second environment that `project_venv_dir()`
  then prefers while `bin/hermes.cmd` still launches the old one.
- `_refuse_update_if_venv_foreign_owned` scans the resolved venv (the only
  remaining `PROJECT_ROOT / "venv"` literal on the update path).
- windows.ps1 names the actual shim path in the lock-timeout message.
- Tests: extend the real-git ZIP guard test with the `.venv` case (red
  before this commit); the holder-guard test now uses a kernel-runner child
  whose cmdline lacks `hermes_cli.main`, so only the venv-prefix arm can match
  it (red on origin/main); drop the `process.platform`-override vitest case,
  which exercised the same resolver as the `.venv` case with a different
  directory string.

Co-authored-by: fangliquanflq <fangliquan@qq.com>
2026-09-16 17:13:56 -07:00
KoNit-K
b6cc751e5f fix(update): support uv default venv in desktop updates 2026-09-16 17:13:56 -07:00
ethernet
612d542281 Merge remote-tracking branch 'origin/main' into ethie/pm-clean
# Conflicts:
#	.gitignore
#	Dockerfile
#	agent/onboarding.py
#	apps/desktop/electron/main.ts
#	apps/desktop/electron/pool-stop.ts
#	apps/desktop/src/components/model-picker.test.tsx
#	apps/desktop/src/store/updates.ts
#	apps/desktop/vite.config.ts
#	datagen-config-examples/run_browser_tasks.sh
#	docs/rca-ssl-cacert-post-git-pull.md
#	gateway/run.py
#	hermes_cli/backup.py
#	hermes_cli/credential_lifecycle.py
#	hermes_cli/dashboard_procs.py
#	hermes_cli/doctor_state.py
#	hermes_cli/env_loader.py
#	hermes_cli/gateway_windows.py
#	hermes_cli/local_runtime/endpoint.py
#	hermes_cli/psutil_android.py
#	hermes_cli/update_cmd.py
#	hermes_cli/update_cmd_windows.py
#	hermes_cli/web_routers/local_models.py
#	hermes_cli/web_server_config.py
#	hermes_cli/web_server_cron.py
#	plugins/memory/hindsight/__init__.py
#	plugins/memory/holographic/__init__.py
#	plugins/memory/honcho/cli.py
#	plugins/memory/mem0/__init__.py
#	plugins/platforms/google_chat/oauth.py
#	plugins/platforms/photon/adapter.py
#	scripts/ci/list_os_marked_tests.py
#	scripts/run_tests.sh
#	tests/agent/test_compression_stall_fallback.py
#	tests/agent/test_create_openai_client_ssl_verify.py
#	tests/gateway/test_google_chat_oauth_dependencies.py
#	tests/hermes_cli/conftest.py
#	tests/hermes_cli/test_cli_init.py
#	tests/hermes_cli/test_gateway_migrate_multiplex.py
#	tests/hermes_cli/test_psutil_android_extract.py
#	tests/hermes_cli/test_relaunch.py
#	tests/hermes_cli/test_update_check.py
#	tests/hermes_cli/test_update_handoff_desktop_rebuild.py
#	tests/hermes_cli/test_worktree_gc.py
#	tests/scripts/desktop_update/test_desktop_update_windows_python_handoff.py
#	tests/scripts/desktop_update/test_desktop_update_windows_retry_policy.py
#	tests/scripts/desktop_update/test_desktop_update_windows_timestamp.py
#	tests/scripts/install/test_install_autostash_conflict_recovery.py
#	tests/scripts/install/test_install_clone_throttle_fallback.py
#	tests/scripts/install/test_install_commit_pin_rollback.py
#	tests/scripts/install/test_install_diverged_update.py
#	tests/scripts/install/test_install_lockfile_churn.py
#	tests/scripts/install/test_install_macos_launcher.py
#	tests/scripts/install/test_install_no_initial_commit.py
#	tests/scripts/install/test_install_ps1_ascii_only.py
#	tests/scripts/install/test_install_ps1_browser_install.py
#	tests/scripts/install/test_install_ps1_managed_node_swap.py
#	tests/scripts/install/test_install_ps1_native_stderr_eap.py
#	tests/scripts/install/test_install_ps1_node_path_for_npm.py
#	tests/scripts/install/test_install_ps1_python_fallback_venv.py
#	tests/scripts/install/test_install_ps1_resolver_strictmode.py
#	tests/scripts/install/test_install_ps1_uv_install_fallback.py
#	tests/scripts/install/test_install_ps1_uv_powershell_host.py
#	tests/scripts/install/test_install_ps1_venv_process_tree.py
#	tests/scripts/install/test_install_ps1_venv_recreate_safety.py
#	tests/scripts/install/test_install_ps1_venv_rename_abort.py
#	tests/scripts/install/test_install_ps1_venv_transaction_boundary.py
#	tests/scripts/install/test_install_ps1_web_server_syntax_probe.py
#	tests/scripts/install/test_install_scripts_computer_use.py
#	tests/scripts/install/test_install_sh_acp_launcher.py
#	tests/scripts/install/test_install_sh_bootstrap_marker.py
#	tests/scripts/install/test_install_sh_browser_install.py
#	tests/scripts/install/test_install_sh_install_method_stamp.py
#	tests/scripts/install/test_install_sh_node_deps_failure.py
#	tests/scripts/install/test_install_sh_node_deps_workspaces.py
#	tests/scripts/install/test_install_sh_node_global_prefix.py
#	tests/scripts/install/test_install_sh_node_npm_check.py
#	tests/scripts/install/test_install_sh_node_prerelease.py
#	tests/scripts/install/test_install_sh_node_probe.py
#	tests/scripts/install/test_install_sh_node_tarball_without_xz.py
#	tests/scripts/install/test_install_sh_pythonpath_sanitization.py
#	tests/scripts/install/test_install_sh_reuse_supported_python.py
#	tests/scripts/install/test_install_sh_root_fhs_uv_python_path.py
#	tests/scripts/install/test_install_sh_setup_wizard_tty_probe.py
#	tests/scripts/install/test_install_sh_symlink_stomp.py
#	tests/scripts/install/test_install_sh_termux_network_prereqs.py
#	tests/scripts/install/test_install_sh_termux_python_bounds.py
#	tests/scripts/install/test_install_sh_uv_lock_config.py
#	tests/scripts/install/test_install_unmerged_index.py
#	tests/scripts/test_run_tests_parallel.py
#	tests/test_managed_runtime_resolution.py
#	tests/test_project_metadata.py
#	tests/tools/test_browser_use_cli.py
#	tests/tools/test_tts_pythonpath_fallback.py
#	tests/tui_gateway/test_hosted_room_driver_runtime.py
#	tests/tui_gateway/test_tui_gateway_server.py
#	tools/lazy_deps.py
#	tools/voice_mode.py
#	uv.lock
#	website/docs/developer-guide/macos-bundle-updates.md
#	website/docs/developer-guide/pm-audit-status.md
#	website/docs/developer-guide/shared-bundle-builds.md
#	website/docs/developer-guide/source-update-completion.md
#	website/docs/developer-guide/stable-releases.md
2026-09-14 15:38:34 -04:00
teknium1
d10bb2ab6f test: make tests/ mirror the source tree; drop issue numbers from filenames
`scripts/run_tests.sh tests/<dir>/` is how a change gets its regression
coverage run, so a test filed under the wrong directory is a test nobody
runs when that code changes. Two kinds of drift had accumulated.

Parallel directories for one source package, folded into the mirror:
  tests/acp        -> tests/acp_adapter   (its __init__/conftest move with it)
  tests/cli        -> tests/hermes_cli    (prompt_toolkit fixture merged into
                                           hermes_cli/conftest.py)
  tests/run_agent  -> tests/agent         (backoff fixture becomes
                                           agent/conftest.py)
  tests/relay      -> tests/gateway/relay
  tests/state      -> tests/hermes_state

246 loose files at tests/ root, routed by the package they import/patch:
hermes_cli, hermes_state, agent, gateway, tools, plugins, tui_gateway, cron.
Installer and desktop-update script tests go to tests/scripts/{install,
desktop_update}/. 43 tests of root-level modules (batch_runner, utils,
hermes_constants, packaging) stay at the root.

Filenames drop their issue numbers (95 files: test_89315_x.py -> test_x.py);
the number stays in the module docstring where it has context.

Collisions: test_cli_skin_integration.py existed in both tests/ and tests/cli
with different subsets — merged into one (10 tests, all kept);
run_agent/test_pre_compress_memory_context.py -> agent/..._handoff.py;
tests/test_account_usage.py -> agent/test_account_usage_fetch.py;
tests/test_web_server.py -> hermes_cli/test_web_server_ws_ping.py.
Deleted: test_minisweagent_path.py (empty since PR #2804),
test_model_picker_scroll.py (tested a private copy of the logic, imported
nothing), test_process_loop_event_loop_warning.py (asserted asyncio behaviour,
imported nothing from Hermes).

Repo-root path arithmetic (Path(__file__).parents[N], dirname chains) is
bumped for the 202 files that changed depth and verified by evaluating every
such expression against the new location. classify_changes' desktop-updater
lane prefix, tests-os.yml's ignore glob and every in-tree path comment follow
the moves. tests/test_tests_tree_layout.py keeps the tree from drifting back.
2026-09-13 09:18:02 -07:00
ethernet
5e4a2a3d24 refactor(pm): remove legacy dependency and launch managers
Competing installers and checkout-local venv assumptions bypassed PM
selection, install consent, and generation lifetimes. Route consumers
through PM and installation-bound launchers. Refresh source launchers
before obsolete Python entries can be collected.

Remove Node, browser, and CUA acquisition engines, obsolete venv-holder
handling, detached sync, and unused PM APIs. Keep historical updater
exports inert and preserve external tool ownership and native integration.

Share product freshness and prepared inputs across builders. Align plugin
admission, Docker provisioning, setup instructions, and behavioral tests.

Verified targeted Python and JavaScript tests, desktop and web typechecks,
scoped lint, real product builds, and the Docker frontend smoke test.
The missed post-setup test cleanup is included and verified.

Native Windows/macOS execution, full Rust compilation, and the complete
repository suite remain unverified. Historical compatibility requirements
were preserved and extended, not fully rescanned.
2026-09-12 14:57:38 -04:00
ethernet
38dfe6df50 merge: current main into consolidated PM and onboarding 2026-09-12 10:37:00 -04:00
MatthewHines
31bf8e504c fix(update): address review — neutral gate comment, pin empty-target skew case
- Rewrite the linux_gate comment to describe the environment fact
  (symlinked /home, kernel-canonicalised /proc/<pid>/exe) without
  local-patch markers or the unfiled-issue reference.
- Add test_empty_relaunch_target_falls_to_skew pinning the [ -n ... ]
  guard behavior so it cannot be simplified away.
- Add the missing trailing newline to the test file.
2026-09-12 05:09:59 -07:00
MatthewHines
d3b090a397 fix(update): canonicalise relaunch-target paths before the linux skew gate
The linux relaunch gate compares the running desktop's exe path
(relaunch target, read from /proc/<pid>/exe — kernel-canonicalised)
against the checkout's unpacked-app prefix with a raw case-pattern.
On hosts where /home is a symlink to /var/home (e.g. Fedora), the two
sides spell the same tree differently (/home/... vs /var/home/...) and
the gate false-positives "skew", telling the user to reinstall the
desktop app after every successful self-update.

Canonicalise both sides with readlink -m (which resolves existing
leading components without requiring the full path to exist, unlike
-f) before the prefix compare. No-op when both sides already agree.
2026-09-12 05:09:59 -07:00
ethernet
cb4fa18915 fix: preserve source channel in desktop update handoffs 2026-09-11 20:27:21 -04:00
ethernet
8f6d98e4c3 fix activation of devenv, use /usr/bin/env bash everywhere 2026-09-11 11:17:18 -04:00
ethernet
b3bfc3afe5 Merge remote-tracking branch 'origin/main' into ethie/pm-clean
# Conflicts:
#	apps/desktop/electron/backend-connection-state.test.ts
#	apps/desktop/electron/backend-connection-state.ts
#	apps/desktop/electron/backend-exit.test.ts
#	apps/desktop/electron/main.ts
#	apps/desktop/electron/pool-spawn-coordinator.test.ts
#	apps/desktop/electron/pool-stop.ts
#	apps/desktop/electron/preload.ts
#	apps/desktop/src/app/settings/about-settings.tsx
#	apps/desktop/src/app/updates-overlay.tsx
#	apps/desktop/src/global.d.ts
#	apps/desktop/src/store/notifications.ts
#	apps/desktop/src/store/updates.ts
#	gateway/config_loader.py
#	hermes_cli/banner.py
#	plugins/platforms/dingtalk/adapter.py
#	tests/hermes_cli/test_plugins_cmd.py
#	tests/test_live_system_guard.py
#	tui_gateway/server.py
#	website/docs/user-guide/desktop.md
2026-09-11 09:36:04 -04:00
Teknium
8068c09432 fix(desktop): verify the Windows update receipt against the checkout, not cwd
The Windows hand-off script runs from the PRE-update checkout, spawned from
HERMES_HOME by the Desktop. e7eaff68 shipped the post-update verify step
without the cwd pin that landed a day later, so verify_windows_desktop_update(Path.cwd())
looked for apps/desktop/release under ~/.hermes and reported a healthy,
fully updated install as "The updated Desktop executable is missing" (exit 8,
error dialog, app relaunched fine).

Derive the root from the imported hermes_cli package instead; the ps1 no
longer passes a cwd. The cwd pin stays as belt for the other child steps.
2026-09-10 01:25:45 -07:00
ethernet
e4cc7f09d9 merge: integrate upstream catalog with PM publication
Keep upstream's reviewed catalog as the only plugin name index.
Catalog pins and custom update sources share staged PM validation.
Publish code and dependencies with recovery after process death.
Reject a concurrent enablement change before publishing disabled code.

Use the manifest loader's supported version in the installer. Keep
probe cooldowns for timeouts, not TLS failures that a CA change fixes.
Preserve the backup, uninstall, browser and memory-provider repairs.

Verified with the canonical runner on native Windows ARM64, real Git
repositories, local TLS endpoints and UV dependency generations.
Desktop catalog tests and both TypeScript checks pass. The full suite
and native release builds were not run. No remote push.
2026-09-09 16:49:27 -04:00
Teknium
c32e0acb0e test(desktop): exercise production cwd setup in Windows self-test 2026-09-08 17:13:48 -07:00
fangliquanflq
610b6731d4 fix(desktop): pin Windows update handoff cwd 2026-09-08 17:13:48 -07:00
ethernet
7d2b3b767d merge: integrate upstream/main into ethie/pm-clean
Merge upstream b1f003e186 while preserving PM runtime ownership and
Python 3.14 worker startup, Windows signing, and macOS wait recovery.

Keep retired runtime modules deleted. Port upstream updater preflight
checks into the checkout strategy and preserve live build logging.
Carry checkpoint filename handling and process recovery into the current
module layout. Regenerate locks and adapt incoming platform test markers.

Focused Python and JavaScript tests, desktop and root-test typechecks,
conflict-path lint checks, lock validation, and retired-import checks pass.
The full test suite and packaged release builds were not run.
2026-09-08 19:17:39 -04:00
Teknium
a662f9d513 fix(desktop-update): discard failed profile allocation output 2026-09-07 06:09:56 -07:00
Teknium
ca812ba3b5 fix(desktop-update): atomically claim the temporary browser profile
Use mktemp -d before launching the optional UI; skip UI if allocation fails. Native Chrome collision and allocation-failure probes preserve preexisting directories.
2026-09-07 06:09:56 -07:00
Teknium
1bd7364d8e fix(desktop-update): clean only the captured shim profile
Track the path actually launched, preserving the no-UI case and unrelated profiles. Adapted the ownership approach from #104362.

Co-authored-by: liuhao1024 <sunsky.lau@gmail.com>
2026-09-07 06:09:56 -07:00
Rohith Pariki
97f74b8361 fix(desktop-update): clean up throwaway browser profile directory
Deletes the temporary --user-data-dir used by the update UI shim when the browser process is shut down, preventing ~100MB leaks per update. Fixes issue #104350.
2026-09-07 06:09:56 -07:00
Teknium
5ce8e974c2 fix(desktop): reject incomplete Windows builds before success receipts
Native Windows run 34096838164 reports false success for absent and corrupt executables, missing bundle files, missing chunks and missing or stale stamps. Reuse the existing build identity and PE validators, and check interpreter presence before waiting for Desktop. Preserve dependency recovery and exit-2 refusal behavior.

Co-authored-by: Konstantin Khlopkov <konstantin.khlopkov93@gmail.com>
2026-09-07 05:55:26 -07:00
Teknium
90ac288c7d fix(desktop): verify updated runtime before success receipt
Port the runtime verification portion of #104692 after native run 34095483533 reproduced ok=true for a zero-exit controlled child that removed its runtime module. Artifact/build-stamp validation remains unaddressed.

Co-authored-by: Konstantin Khlopkov <konstantin.khlopkov93@gmail.com>
2026-09-07 05:55:26 -07:00
ethernet
c1bb7116c0 test(updater): measure pipe abandonment after fixture startup
Record the child-ready monotonic timestamp before it exits. The leak arm keeps the same drain bound and live-descendant assertion without timing cold PowerShell initialization. The stall arm retains its short watchdog.
2026-09-06 17:12:53 -04:00
Teknium
d024a9c494 fix(desktop-update): drop the dead WinForms pump from the ack wait
Publish-UiEvent only enters the delivery-wait loop under $script:UiServer,
and Show-ProgressWindow returns before the WinForms card is built whenever
the browser shim is up, so $script:Ui is always null there. The DoEvents
call could never run; the 50ms sleep loop is the whole wait.
2026-09-06 07:19:21 -07:00
Axl Ibiza, MBA
cacf1218d7 fix(desktop-update): acknowledge Windows progress completion
A delayed browser could miss the 900ms terminal event and spin forever after the updater exited. Retain terminal delivery until the page acknowledges it, bound unavailable-client teardown and failed requests, and preserve a truthful final display.

Fixes #103747. Builds on OutThisLife and Teknium detached handoff work in #83634 and the #75895 quiet-window design. Continues Axl Ibiza Windows update investigation (#60233, #94107, #100763), including source/review contributions carried by merged #93353 and #85170. Existing #102373, #103140, #95719, #97299 and #103632 retain their separate scopes.
2026-09-06 07:19:21 -07:00
Teknium
bfbb34bbec fix(update): Windows progress server hands out its URL only once it is serving
`Start-UiServer` printed the -SelfTestUi URL (and opened the browser window)
as soon as the TcpListener was bound, but the runspace that answers /progress
starts asynchronously — BeginInvoke returns before the pipeline is open and
the script block is JIT'd, which is seconds on a loaded runner. The kernel
accepted connections into the backlog during that gap and nobody answered
them. The self-test hit it three times (#90371 and two follow-ups each
widened a timeout instead of removing the race) and it just failed an
unrelated hermes_state.py PR (run 33591547099, two 5s stale-backlog
timeouts = red).

- windows.ps1: readiness handshake after BeginInvoke — one /progress
  round-trip must succeed (≤15s) before the server is returned; on failure
  tear the listener down and continue without UI. The URL now means
  "serving", not "bound". Also fixes the browser opening to a page that never
  loads on a slow machine.
- test: 1s per-attempt probe timeout so a single dead backlog socket cannot
  consume half the readiness budget.
- CI: new `desktop_updater` classifier lane. tests/test_desktop_update_windows_*.py
  spawn the real PowerShell script; the Windows-only job now runs them only
  when scripts/desktop-update/**, the Electron updater launcher, conftest,
  pyproject, or those tests change (push/dispatch fail open). A PR that
  never touched that surface cannot be failed by its process timing.
2026-09-02 00:44:19 -07:00
Teknium
28044757aa fix(desktop-update): self-heal a TCC-anchor-bricked venv in the posix hand-off (#95759)
Installs converted by the reverted macOS TCC interpreter anchor
(#95425/#95541) are left with a real-file venv/bin/python copy, a
.tcc-anchor-source marker, and python3/python3.N aliases that die at
interpreter init ("No module named 'encodings'"). venv/bin/hermes execs
venv/bin/python3, so EVERY CLI entrypoint is dead — hermes doctor and
hermes update included — and the desktop hand-off loops on "Update failed
(exit 1)" forever. No Python-side heal can ever run on this class; the
hand-off shell is the last surface that still executes, so the heal lives
there.

posix.sh gains, before the update invocation:

* tcc_anchor_heal — probe-gated (only fires when venv/bin/python3 fails
  a scrubbed-env `import encodings` boot probe), marker-validated
  (absolute path, outside the venv), staged with per-attempt backups and
  full rollback if the repaired interpreter still fails its probe.
  Two repair shapes:
  - alias-brick (#95541 class): the anchored copy boots — re-materialize
    python3* as REAL FILES of the anchor (hardlink/copy; an alias symlink
    onto the copy is the crash shape). Marker kept: this is exactly the
    layout ensure_tcc_anchor marks "active", so no anchor ping-pong.
  - full brick: restore python → symlink to the marker-recorded store
    interpreter (if it boots) and aliases → symlinks; marker removed.
    The unblocked `hermes update` then re-installs a boot-gated healthy
    anchor — one-shot convergence, not a loop.
  Fail-closed on missing/unbootable source (vanished uv store class),
  missing marker, or relative/in-venv marker paths.
* tcc_pick_update_invoke — if aliases stay dead but venv/bin/python
  boots (the launchd-gateway shape), drive the update via
  `venv/bin/python -m hermes_cli.main` instead of the dead hermes shim.
* Honest terminal message: an unrecoverable dead interpreter is reported
  as a venv repair problem instead of a generic "Update failed (exit 1)".
* --self-test-tcc-heal runs the real heal + invoke selection against an
  --install-root and reports, for the test harness.

Tests (tests/test_desktop_update_tcc_heal.py) drive the REAL posix.sh
functions on Linux against synthetic venv trees: healthy no-op, alias
heal, symlink restore, fail-closed classes, rollback on failed
verification, invoke fallback, and an A/B of the reported loop (bricked
venv/bin/hermes fails with the exact field error before, boots after).
Sabotage-verified (re-introducing the alias-symlink bug fails 2 tests).

NOT mac-live-tested (no macOS runner); the heal is platform-independent
shell exercised through the self-test path without the uname gate.

Recovery design (validation/staging/rollback/probe pattern) after
@aeonsong's #96231; in-update heal intent from @liuhao1024's #95775
(its target function no longer exists on main and its heal point is
unreachable on dead-CLI installs); heal-point and ping-pong analysis by
@ahrazzle and @tokenfires on #95759.

Fixes #95759
2026-09-01 08:36:03 -07:00
4dlt
3a7f2234a6 fix(cli): use Chromium's namespace sandbox when userns is available on Linux
The desktop launcher demanded a root-owned 4755 chrome-sandbox on every
Linux host and shelled out to sudo to configure it. Launched from the
.desktop entry there is no TTY, so sudo fails silently and `hermes
desktop` exits without a window — and every update rebuilds the helper
user-owned, re-breaking the app (#88032, #51327). The update hand-off's
relaunch gate blocked on the same condition, so post-update auto-relaunch
never fired either (#58593).

On hosts where unprivileged user namespaces work, Chromium uses its
namespace sandbox and never consults the setuid helper. Probe the actual
capability with `unshare --user --map-root-user true` (fails closed) and
skip the sudo path when the probe succeeds; hosts with userns disabled or
AppArmor-restricted (Ubuntu 23.10+) keep the existing setuid-helper and
--no-sandbox fallback behavior unchanged. Sandboxing stays fully enabled
in both cases.

Fixes #88032
Fixes #51327
Fixes #58593

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-01 02:32:39 -07:00
fangliquanflq
12116f7619 fix(scripts): align retry recovery documentation 2026-08-31 10:05:13 -07:00
fangliquanflq
fd596e2492 fix(scripts): preserve update retry fallback 2026-08-31 10:05:13 -07:00
fangliquanflq
f10a231efa fix(scripts): clarify Windows update retry marker semantics 2026-08-31 10:05:13 -07:00
fangliquanflq
fd24ac94e0 fix(update): resume deferred Windows desktop updates 2026-08-31 10:05:13 -07:00
Teknium
7a1aafb4e1 fix(update): raise the step-idle watchdog default to 10 minutes
Desktop builds legitimately produce no output for minutes on Windows
(the updater terminal is static until a step completes), so 300s risks
cancelling healthy long steps. 600s keeps the watchdog meaningful for
true stalls while clearing slow builds; HERMES_UPDATE_STEP_IDLE_SECONDS
still overrides. Per Teknium's review.
2026-08-26 18:46:34 -07:00
Teknium
266d8ce2bb fix(update): normalize windows.ps1 to LF and keep fixture here-string braces off column 0
The cherry-pick landed the file with CRLF endings and a fixture
here-string whose col-0 brace prematurely terminated the handoff test's
SelfTest-block strip, tripping the drive-python-not-the-shim guard on
fixture code. LF restored (matching main), child-script loop inlined.
2026-08-26 18:23:33 -07:00
Teknium
50d3c53f6c fix(update): count logs/update.log growth as watchdog progress
The #95625 watchdog cancels a step after StepIdleTimeoutSeconds (300s)
with no stdout/stderr. But a real `hermes update` is stdout-silent for
40+ minutes by design: the Electron/vite build streams to
logs/update.log, not the child's pipes (hermes_cli/update_cmd.py's
update-log tee). An output-only ceiling would therefore kill every
healthy large update at 5 minutes and mark it exit 124.

The drain now fingerprints logs/update.log (size + mtime) and, when the
idle ceiling is otherwise reached, treats growth of that file as
progress: reset the clock instead of terminating the tree. The stat
runs only once the ceiling fires, so the hot drain path never touches
the filesystem. HERMES_UPDATE_STEP_IDLE_SECONDS remains the override;
HERMES_UPDATE_PROGRESS_LOG points the self-test at its own file.

TDD proof: -SelfTestPipeDrain gains a fourth arm, logstall -- a step
that is silent on its pipes but appends to the progress log every
second and must reach its natural exit 3, never 124. Linux CI pins the
same contract at source level (TestIdleWatchdogCountsUpdateLogGrowth);
sabotage-verified: making the log-growth consult inert fails
test_stall_branch_consults_log_growth_before_terminating.
2026-08-26 18:23:33 -07:00