88 Commits

Author SHA1 Message Date
teknium1
b63c138d78 fix: Hermes never falls back to the user's node/npm/npx/uv
Maintainer ruling: only Hermes and only its packaged package managers
(uv/pip/node/npm) are ever used; no PATH fallback when the managed tool is
missing, no "prefer the user's if new enough".

- hermes_constants.find_node_executable: node/npm/npx resolve to PM's
  installed copy or None. Every caller already pm.ensure()s on None, so a
  missing runtime is now provisioned instead of silently borrowing the
  user's Node (native-addon ABI / npm cache mismatches).
- agent/lsp/install._install_npm: pm.ensure('npm') when PM npm is absent,
  instead of failing over to whatever npm is on PATH.
- gateway._append_node_dir_for_service: stop baking the invoker's PATH node
  dir into generated systemd/launchd units.
- main_install_repair._resolve_node_runtime_npm: drop the PATH re-scan for
  another npm.
- source_build.source_product_current: run the freshness reader only with
  PM's node.
- doctor: Node/npm rows and npm audit use PM's copies (Termux APT distro
  keeps its system Node).
- install.sh ensure_uv / install.ps1 Get-Uv: always stage the pinned uv
  artifact; delete the "uv on PATH if new enough" developer shortcut.
2026-09-27 22:04:26 -07:00
ahisblessed
77b9ea0831 fix(agent): route every model-visible elision through the non-imitable compression marker (#121548)
The #83714 fix hardened only the tool-call args renderer; five other
renderers (plus three same-idiom siblings) still composed the bare
truncation marker, which the model imitated from replayed context into
new durable writes (#83435).

This routes all model-visible elision in agent/ through shared helpers
in agent/compression_marker.py:

- elide(text, limit): head + marker cap with accurate omitted/total counts
- elide_middle(text, head, tail): kept head/tail with the middle elided

The elision marker's first sentence is byte-identical to the args marker's,
so the existing _COMPRESSION_MARKER_RE (and the dispatch-boundary guard in
tool_dispatch_helpers) rejects a copied marker regardless of which renderer
leaked it; only the tool-call-specific second sentence is dropped so the
marker still fits small caps (the clarify summary cap is 199 chars).

Routed sites:
- context_compressor.py: static-fallback turn renderer, _sum_clarify(),
  summarizer prompt builder (middle elision), active-task snapshot,
  lean user-message quotes (2 sites)
- skill_preprocessing.py: inline-shell output embedded into skill bodies
- lsp/reporter.py: truncate() for <diagnostics> tool output
- verification_stop.py: verify-on-stop nudge output summary

Regression test asserts the imitable idiom appears nowhere in agent/
strings (comments excluded), so a new open-coded renderer cannot land
silently.

Fixes #121548

(cherry picked from commit 69c63d50b4ababedf4fb4ff88c08f5d1e144a79f)
2026-09-26 23:49:25 +05:30
ethernet
0f65d698d0 Merge remote-tracking branch 'origin/main' into ethie/pm-clean 2026-09-24 13:10:14 -04:00
kshitijk4poor
42a5ae67f4 refactor(lsp): inline the one-caller subprocess stream-limit helper into LSPClient (#31417)
Co-authored-by: leavedrop <1433810735@qq.com>
2026-09-24 22:25:06 +05:30
konsisumer
d516575df3 fix(lsp): raise LSP subprocess StreamReader limit to 16 MiB
(cherry picked from commit 0768510a14693cc5cf521373e330858a5aba576f)
2026-09-24 22:25:06 +05:30
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
teknium1
c545272568 fix(lsp): one stalled request no longer silences a workspace for good — retry window, cold-root warm-up budget, per-root exclusion
A language server that missed its budget once marked its (server, root) pair broken for the
process lifetime, the same 5 s steady-state budget was applied to a cold server that also had
to spawn, initialize and build its program, and the only escape (servers.<id>.disabled) switched
the server off for every workspace.  Three new keys under the existing `lsp` block, all defaulting
to today's behaviour:

- lsp.broken_retry_seconds (0 = lifetime): the broken set stores a monotonic retry deadline per
  pair; an expired pair gets one more try, and the INFO skip line names the retry time.
- lsp.warmup_timeout (0 = wait_timeout): the first request against a root with no running client
  waits up to this budget (outer join budget follows); warm requests keep wait_timeout.
- lsp.exclude_roots ([]): glob patterns matched against the resolved project root (a bare path
  also covers everything beneath it); a matching root never spawns, logged once at INFO.  A
  non-list value fails closed — WARNING naming the expected shape, every root skipped — because
  silently excluding nothing would re-pay the stall the key was meant to avoid.

Part of #116446 (the diagnosability slice landed in #116839, salvage of #116459 by @kokhlo).
2026-09-20 18:54:24 -07:00
teknium1
81faca2f1c fix: failed initialize keeps the LSP error type instead of raising TypeError (review follow-up)
The failure-details rewrap re-instantiated the caught exception with a single
string; LSPRequestError takes (code, message, data), so a JSON-RPC error to
`initialize` surfaced to log_spawn_failed as a TypeError with none of the exit
status / stderr details. Attach the details to the original exception in place
and re-raise. Adds an `init_error` mock-server script and one invariant test
(red on the previous head).
2026-09-20 13:54:58 -07:00
teknium1
d15b17adf5 fix(lsp): log at INFO when a request is skipped because its root is marked broken
After one timeout `_mark_broken_for_file` disables the whole (server, root)
pair for the life of the process, and every later request returned [] with no
log line at all — at default levels a skipped file and a clean file
(`log_clean` is DEBUG) looked identical, so a workspace that silently lost
TypeScript feedback was indistinguishable from a healthy one (#116446, ask 3).

`LSPService.enabled_for` — the gate every production path (snapshot,
get_diagnostics_sync, file_operations_lint) runs through — now emits
`eventlog.log_skipped_broken`: INFO once per (server, root), DEBUG on repeats,
same dedup bucket pattern as the other announce-once events.
2026-09-20 13:54:58 -07:00
Konstantin Khlopkov
b4f0553f8e fix(lsp): report server exit status and stderr tail on a failed initialize (#116446) 2026-09-20 13:54:58 -07:00
teknium1
1e9d402e07 fix: LSP tree-kill runs off the event loop (review follow-up)
kill_process_tree is synchronous (taskkill /T /F with a 15s timeout on
Windows), so the hard-kill in _cleanup_process blocked the loop for the
duration. Run it via asyncio.to_thread. The graceful path is unchanged:
shutdown() already waits SHUTDOWN_GRACE on proc.wait() after `exit`, so a
well-behaved server exits 0 before cleanup ever reaches the kill (probe:
returncode 0, no kill_process_tree call).
2026-09-20 12:54:18 -07:00
fangliquan
b773bcf271 fix(lsp): hard-kill failed server trees before reaping 2026-09-20 12:54:18 -07:00
fangliquan
bf52a9519a fix(lsp): reap servers cancelled during startup 2026-09-20 12:54:18 -07:00
liuzikaii
d23d6e8218 fix(lsp): use UTF-16 units for document replacement ranges 2026-09-20 10:24:17 -07:00
ethernet
ff92976317 fix: read package.json with encoding=utf-8-sig in the vue LSP probe (from main) 2026-09-19 13:45:57 -04:00
ethernet
d29da5fe20 Merge remote-tracking branch 'origin/main' into ethie/pm-clean
Conflicts resolved toward the branch: PM owns dependency preparation, the
Windows shim re-exec/hand-off path stays retired (main's shim-parent wait,
gateway-resume env token and update_cmd_deps tests dropped), docs describe
the PM update flow. The docker workflow parks install-stamp.json around the
toolchain step instead of deleting it so tests/docker can compare provenance.
2026-09-19 13:45:07 -04:00
teknium1
1c21a50254 fix(lsp): cap the workspace-root cache and the eventlog announce buckets (#62950)
Both are process-lifetime per-path structures with the same growth shape as the caches
this PR already bounds: _workspace_cache only cleared on service shutdown, the
(server_id, file_path) dedup bucket never. Each resets past a fixed cap (512); for the
stat cache that costs a few re-stats, for the dedup bucket one re-fired INFO line per
key, and a single clear() keeps the lock-free stat cache thread-safe.
2026-09-19 01:55:38 -07:00
teknium1
83200b359e fix(lsp): take _state_lock in _set_delta_baseline; pin the cap through the production call sites
The pop/insert/evict sequence mutates the process-wide baseline dict from arbitrary
gateway/subagent threads; on main the write was a single atomic assignment, so the cap
introduced a race (RuntimeError/KeyError past 256 entries) that would surface in the
write-tool path. Callers never hold the lock, so the helper takes it.

The cap test now drives snapshot_baseline and _apply_delta instead of the helper, so
reverting either production call site to a direct assignment is red, and asserts every
mutation runs under the lock.
2026-09-19 01:55:38 -07:00
Tikkanaditya Siddartha Jyothi
539b82698e fix: bound the LSP document cache, delta baselines, TUI fuzzy cache and the tool-call logger (#62950)
Long-running gateway/TUI processes grew four in-memory structures for their
whole lifetime:

- agent/lsp/client.py: `_docs` pinned every opened file's full text (and the
  server's mirror) until the idle reaper killed the client. Now an
  insertion-ordered LRU capped at MAX_TRACKED_FILES=64; evicted files are
  didClose'd and re-didOpen'ed on their next touch.
- agent/lsp/manager.py: `_delta_baseline` kept a pre-write snapshot for every
  path ever written; capped at 256 with write-recency eviction.
- tui_gateway/methods_complete_helpers.py: `_fuzzy_cache` only checked its TTL
  on read, so a root queried once (one .worktrees/<id> per worktree flow) was
  pinned forever; the write path now sweeps expired roots under the lock.
- gateway/run_turn.py: `logging.getLogger(f"hermes.tool_calls.{id(log_queue)}")`
  registered one permanent Logger per logged turn; a single fixed-name Logger
  with one RotatingFileHandler is shared by all turns.

Salvaged from #62934 by @Vissirexa, re-targeted onto the decomposed modules.
The per-task file-tool tracker hunks were dropped: f7a422ee4a already pops
them in AIAgent.close()/cleanup_vm via clear_file_ops_cache.

(cherry picked from commit 668f43b2ea9)
2026-09-19 01:55:38 -07:00
teknium1
06e76129cd fix(lsp): surface the pnpm failure reason and document the exotic-subdep block
pnpm prints ERR_PNPM_* to stdout, so the install warning showed an empty
reason. The composite live pass hit ERR_PNPM_EXOTIC_SUBDEP installing
@vue/language-server@2 (git-hosted transitive dep) with lsp.package_manager: pnpm;
the policy stays fail-closed, the docs now say why and how to opt out.
2026-09-19 01:55:28 -07:00
teknium1
6cf2f066a8 fix(lsp): unknown lsp.package_manager fails closed; yarn uses the global --cwd form
Review follow-up: an unknown `lsp.package_manager` value (a typo such as `pnmp`)
warned and then ran npm anyway, bypassing exactly the pnpm/yarn supply-chain
policy the option exists to enforce. It now warns with the allowed set and skips
the install, the same outcome as a configured-but-missing manager.

Yarn's argv moves to the global `yarn --cwd <staging> add ...` form, which both
Yarn Classic (1.x) and Yarn Berry (2+) accept (`add --cwd` is Classic-only), and
the docs note that Berry's default PnP linker writes no `node_modules/.bin`, so
the staging dir needs `nodeLinker: node-modules`. The test moves above the
`__main__` guard and now covers the yarn argv and the fail-closed typo case.
2026-09-19 01:55:28 -07:00
teknium1
c484be0123 feat(lsp): lsp.package_manager picks npm, pnpm or yarn for language-server installs
`_install_npm` hard-coded npm, so users who standardise on pnpm/yarn for their
supply-chain policy (minimumReleaseAge, allowBuilds, ...) could not extend it
to the LSP install path (#35448). `lsp.package_manager: npm|pnpm|yarn` (npm
default) selects the manager; the argv table `_NODE_PM_ARGV` keeps every
install inside <HERMES_HOME>/lsp/node_modules. A configured manager that is
not installed skips the install with a warning rather than silently using
npm; unknown values warn and use npm.

Ports the config shape of #70001 (@tupe12334); the request and the first
implementation (#35475, `terminal.npmCommand`) are @temalo's.

Fixes #35448
Co-authored-by: Ofek Gabay <tupe12334@users.noreply.github.com>
Co-authored-by: temalo <temalo@users.noreply.github.com>
2026-09-19 01:55:28 -07:00
teknium1
6b3005c4c8 fix(lsp): eventlog path shortener survives a removed process cwd
relpath() calls getcwd(); with the cwd deleted it raised OSError from the
DEBUG log line inside get_diagnostics_sync, so the write now succeeded but its
diagnostics were silently dropped for the rest of the process lifetime (found by
the composite live pass with real pyright). Same class as the resolver guard.
2026-09-19 01:32:28 -07:00
teknium1
311123a8da fix(lsp): inline the deleted-cwd guard and keep one real-cwd invariant test
Salvage trim of #115355: the one-line try/except lives in
resolve_workspace_for_file itself instead of a _current_dir helper, and the
regression test chdirs into a real directory and removes it (no monkeypatched
os.getcwd), so it exercises the kernel behaviour the report describes. The
helper's own change-detector test goes with the helper.
2026-09-19 01:32:28 -07:00
Konstantin Khlopkov
570b3c1a6c fix(lsp): treat a deleted process cwd as no anchor so write diagnostics never fail a landed write
resolve_workspace_for_file evaluated os.getcwd() unguarded, so after a scratch
workspace is removed under a running worker, every write_file/patch reported
[Errno 2] from the LSP enrichment step even though the write landed and
hash-verified. The resolver now reads a removed cwd as "no cwd anchor" and falls
through to the file's own worktree; _maybe_lsp_diagnostics gates through the
guarded _lsp_will_handle helper like the rest of the lint tier, so a
workspace-resolution failure degrades to "no LSP for this write".

Fixes #115342

(cherry picked from commit 6277d2a036b75881ea529e3268832a3f6262b139)
2026-09-19 01:32:28 -07:00
Sasan Sotoodehfar
51283072ad fix(lsp,gateway): give servers a grace period after exit and keep shutdown diagnostics off GNU-only tools (#72944)
- LSPClient.shutdown: after the protocol `exit` notification wait up to
  SHUTDOWN_GRACE for the server to leave on its own before SIGTERM. Every
  well-behaved server was being SIGTERMed on top of a clean exit (returncode
  -15 on Linux); on Darwin the reaped PID can already belong to another
  process.
- gateway shutdown diagnostic: run without GNU `timeout` when the host has
  neither `timeout` nor `gtimeout` (stock macOS) instead of skipping the
  diagnostic entirely; sort the `ps` listing with `sort` rather than the GNU
  `--sort` flag; fall back to `sysctl vm.loadavg` when `/proc/loadavg` is
  absent.
- s6 skeleton: chmod after chown so the setgid bit on event/ survives.

Salvages the still-open atoms of #72932 by @sasan1200 (LSP grace, portable
diagnostics, chown ordering); the BASHPID and /private/var/folders atoms
had already landed (911d380296, d4cec15b47).
2026-09-19 01:31:49 -07:00
teknium1
966959da08 fix(lsp): pin Windows wrapper resolution through _existing_binary and the pyright sibling
Review follow-up on the Windows .cmd fix: the only always-on test drove the
`_native_binary_candidates` helper directly, so reverting the production wiring
(`_existing_binary` probing npm's bin dir, wrapper-first order, `_spawn_pyright`
keeping the resolved suffix) stayed green on every non-Windows lane.

`is_windows` is now threaded as data through `_first_existing`/`_existing_binary`
(no sys.platform patch), and the test drives the production entry over an npm-shaped
staging tree (POSIX shim + `.cmd` pair under `lsp/node_modules/.bin`) and asserts
`_spawn_pyright` picks `pyright-langserver.cmd` when handed `pyright.cmd`. Each of
the three wiring reverts turns it red; the windows_only live test stays as-is.
2026-09-19 01:30:13 -07:00
BrierAinz
d6e5cda29c fix(lsp): probe Windows .cmd/.exe/.bat wrappers before npm's POSIX shim (#86445)
npm writes a POSIX `#!/bin/sh` shim under the bare binary name next to its
`.cmd` wrapper. `_native_binary_candidates` listed the bare name first and
`os.access(X_OK)` is always true on Windows, so `_existing_binary` handed the
shell script to CreateProcess (WinError 193) and `_install_npm` staged the
shim instead of the wrapper. Python/TS/YAML diagnostics were silently gone
for the whole session on native Windows.

- wrappers first, bare name last; the ordering takes `is_windows` as data so
  the invariant is testable on every host
- npm's `%~dp0`-relative `.cmd`/`.bat` wrappers stay in `node_modules/.bin`
  (a copy in `lsp/bin/` points at nothing); `_existing_binary` probes there
- PATH probing tries the wrappers before the bare name too
- pyright: the langserver sibling keeps the resolved binary's suffix

Salvages #73088 by @BrierAinz (same diagnosis and staging fix, rebuilt on
the current install.py shape).
2026-09-19 01:30:13 -07:00
teknium1
d4d9b76d8c feat(lsp): route .blade.php to laravel-lsp; plain .php stays on intelephense
`_file_ext_or_basename` used `os.path.splitext`, so `home.blade.php` became
`.php` and Blade templates went to the plain-PHP server (or nowhere useful).
Recognise compound extensions first (`_COMPOUND_EXTS`), register `laravel-lsp`
(`laravel-lsp lsp`, manual install via `composer global require laravel/lsp`)
ahead of intelephense for `.blade.php` only, languageId `blade`.

Salvages #75720 by @timoteo7 (stale against the rewritten registry; same
design: compound-extension match + manual-install entry before intelephense).

Fixes #75718
Co-authored-by: timoteo7 <timoteo7@users.noreply.github.com>
2026-09-19 01:29:55 -07:00
teknium1
894ba961cf fix(lsp): pre-write capture sees config-declared servers; e2e test goes through create_from_config
- `FileOperations._lsp_handles_extension` asks the active `LSPService`
  (`handles_extension`: config-declared servers + the built-in registry) and
  falls back to the static `SERVERS` only when no service is running. Why: it
  read `SERVERS` alone, so a file whose extension is claimed only by a
  custom server never had its pre-write content captured and the stale-
  diagnostic line-shift remap was degraded for exactly the servers #100257 adds.
- `test_service_gets_diagnostics_from_config_declared_server` now writes an
  `lsp.servers.panache` block into a temp `HERMES_HOME/config.yaml` and builds
  the service via `LSPService.create_from_config()` — the only production path
  from config to a running server — so dropping `extra_servers=custom_servers(
  servers)` from `create_from_config` goes red (it stayed green before). The
  custom-extension capture case rides in the same test.

Review follow-up on #115714.
2026-09-19 01:29:37 -07:00
teknium1
8420737a82 feat(lsp): declare custom language servers in config (lsp.servers.<id> with extensions)
An `lsp.servers` key that names no built-in server and carries `command` +
`extensions` now becomes a ServerDef (optional `root_markers`, `language_id`,
`description`; `env` / `initialization_options` work as for built-ins).
Custom entries are consulted BEFORE the registry so they can claim an
extension; malformed entries are logged and skipped. `hermes lsp status|list`
show them; no auto-install (the command must resolve on PATH or as a path).

Why: users integrating a server that is not (yet) first-party had to edit
agent/lsp/servers.py and carry merge conflicts (#100257).

Salvages the shape of #103372 (custom servers ahead of built-ins, per-entry
language_id) without its unrelated client.py / lint changes.

Fixes #100257
Co-authored-by: Zhang ChuanJin <ZHCHJ888@users.noreply.github.com>
2026-09-19 01:29:37 -07:00
teknium1
d0db722786 fix(lsp): Vue reinstall hint routes through hermes lsp install; recipe test is a major-bound invariant
- `_VUE_TUNNEL_MSG` / `_VUE_TSDK_MSG` no longer hardcode an npm argv: they
  tell the user to delete `<HERMES_HOME>/lsp/node_modules/@vue` and the staged
  `lsp/bin/vue-language-server*` launcher, then run
  `hermes lsp install vue-language-server` — the recipe (and whatever package
  manager the installer is configured with) does the rest. Why: `hermes lsp
  install` skips when the binary exists, so a stale 3.x never auto-migrates,
  and a hardcoded `npm install` contradicts a non-npm install policy.
- `test_js_toolchain_recipes_pin_a_javascript_typescript_sdk` asserts the
  invariant (typescript major < 7 and vue major < 3, parsed from the recipe
  strings) with the import inside the test, so on main the parametrized spawn
  cases are what go red instead of a module-level ImportError at collection.
- docs: same reinstall instructions.

Review follow-up on #115726.
2026-09-19 01:29:20 -07:00
teknium1
7b83a009a2 fix(lsp): Vue diagnostics arrive again — pin @vue/language-server to the self-hosting 2.x line
@vue/language-server 3.x no longer runs its own TypeScript service: it
forwards every TS request to the *client* over a `tsserver/request` /
`tsserver/response` notification tunnel that VS Code and Neovim host and
Hermes's generic LSP client does not, so `getLanguageService()` never
resolves and no diagnostics are ever published (or the process crashes on
first use when `require('typescript')` yields the Go-native TypeScript 7).

- install recipe pins `@vue/language-server@2` and co-installs
  `typescript@6`, the last JavaScript-based line — `typescript@latest` is
  now 7.x (Go port, no `lib/tsserver.js` / `lib/typescript.js`), which also
  broke the typescript-language-server recipe ("Could not find a valid
  TypeScript installation"); both recipes share `TYPESCRIPT_SDK_PKG`.
- new `_spawn_vue` starts 2.x with `vue.hybridMode: false` and an explicit
  `typescript.tsdk` (launcher tree → Hermes staging → project node_modules);
  a stale 3.x install or a Go-only TypeScript is skipped with a one-time
  WARNING carrying the reinstall command instead of failing silently.
- docs: the TypeScript SDK / Vue pinning paragraph in the LSP feature page.

Live repro (temp HERMES_HOME, real recipe, reporter's 6-line .vue in a git
repo, real write_file_tool): before — 3.3.11 installed, `lsp_diagnostics`
absent, agent.log `fresh diagnostics timed out` / server crash on
`ts.server.protocol`; after — 2.2.12 + typescript 6.0.3 installed,
`write_file` returns `ERROR [5:7] Type 'string' is not assignable to type
'number'. [2322] (ts)` for App.vue and the `.ts` control returns the same
TS2322 from typescript-language-server.

Fixes #88869
2026-09-19 01:29:20 -07:00
teknium1
d6f1de3f74 fix(lsp): release a worktree's language servers on removal; reap deleted roots
`LSPService` kept one client per `(server_id, workspace_root)` for the life
of the process.  In a long-running gateway that outlives its coding sessions
the client for a removed worktree stayed registered with its stdio pipes
held open — tsserver heaps of several GiB pointed at trees that no longer
existed (#102345).  The idle reaper (d7578018c5) does not cover this: a
client whose root vanished is not idle from the server's point of view.

- `LSPService.release_workspace(path)`: detaches every client whose folders
  live under `path` under `_state_lock`, waits for in-flight spawns so a
  concurrent `_get_or_spawn` cannot reinsert a client after release, prunes
  the delta baselines and broken-set entries beneath the path, and shuts the
  clients down on the service loop.  Multi-root servers (pyright) only drop
  the folder (`LSPClient.remove_workspace_folder`) so siblings keep their
  shared process.  Idempotent; best-effort; returns the count.
- The reaper sweep now also uses that primitive for clients whose every
  workspace folder no longer exists (externally deleted roots).
- `agent.lsp.release_workspace()` reaches every started service without
  creating one; `hermes_cli.worktree_ops.release_lsp_clients()` is called
  from both worktree-removal paths (`cli._cleanup_worktree`, kanban
  `_cleanup_worktree_workspace`) BEFORE `git worktree remove`.

Salvages the direction of #102381 (@Sahilvishnaliya, release_workspace +
cli hook) and the deleted-root eligibility of #95047 (@israellot); both
matched on `key[1]`, which is `""` for multi-root servers and would have
reaped pyright on every sweep — the primitive here matches on
`client.workspace_folders` instead.

Co-authored-by: Sahil Vishnalya <222165401+Sahilvishnaliya@users.noreply.github.com>
Co-authored-by: Israel Lot <840042+israellot@users.noreply.github.com>
2026-09-19 01:29:03 -07:00
teknium1
0bd59ba134 fix(lsp): document that wait_timeout bounds both waits; tighten the salvaged tests
Salvage trim of #75559: the docstring of _open_and_wait_async still said the
snapshot mode "uses the default wait budget", and the user docs described
wait_timeout as the post-edit wait only. The above-default test now uses 7s
instead of 10s (same red-on-base signal, 3s less wall time) and the join-budget
comment says what the ceiling is for in two lines.
2026-09-19 01:28:46 -07:00
Christopher
631baa40c2 fix(lsp): honor lsp.wait_timeout in baseline snapshot
Fixes #75551

(cherry picked from commit b951740eef9db3ff1a743c81868151873b038e20)
2026-09-19 01:28:46 -07: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
NanPan
6950959fa5 fix(lsp): attach reused multi-root client outside state lock 2026-09-16 16:50:07 -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
Teknium
bed4abd106 fix(lsp): look up nested single-root clients and version docs before didChange send
Two LSP freshness bugs reported by @tobific (#108882, #108881):

- `_current_diags_async()` keyed the client lookup by the enclosing
  workspace root while `_get_or_spawn()` stores single-root servers under
  `srv.resolve_root(...)` (a nested package.json project). The lookup
  returned [] for a live client with diagnostics, so the delta baseline was
  refreshed from nothing. Use the same resolved-root key.

- `open_or_change()` published `_DocState.version` only after awaiting the
  didChange write. A versionless publishDiagnostics read during that await
  was credited with the OLD version and judged stale once the send resumed.
  Bump the version before the send; a failed send (swallowed by
  `_send_notification`) leaves a version nothing satisfies, i.e. "no
  verdict", which is the existing contract.

The mock server gains a push-only `versionless` script so the race is
reproducible without a real language server.
2026-09-12 05:09:11 -07:00
Teknium
9c9e7ab6e5 fix(multiplex): a served profile's turn sees its own cwd, approvals, redaction and tool policy
Under gateway.multiplex_profiles a secondary profile's turn ran with the LAUNCH
profile's working directory, command allowlist, redact_secrets switch, credential
file mounts, browser engine/headed flags, LSP service, auxiliary-provider health
marks and MCP stderr log, and several TERMINAL_ENV consumers read the process env
instead of the routed profile's terminal scope. A standalone `hermes -p X gateway
run` never behaved that way.

- tools/terminal_scope.py: resolve the terminal.cwd placeholder inside the
  profile scope with the same rule gateway/run.py applies at import (local ->
  $HOME, sandbox default otherwise) so the system prompt, context files and the
  terminal of a routed turn start where the profile's standalone gateway would.
- tools/image_source.py, credential_files.py, image_generation_tool.py,
  skills_tool.py, delegate_tool_progress.py, agent/tool_executor.py: read
  TERMINAL_ENV / TERMINAL_CWD through the terminal scope.
- tools/approval.py (+ approval_floors.py): one permanent allowlist per routed
  profile home; the unscoped module set stays for single-profile processes.
- agent/redact.py: `_redact_enabled()` resolves security.redact_secrets for the
  routed profile (scope .env, then config); launch snapshot kept when unscoped.
- tools/credential_files.py, agent/auxiliary_health.py, agent/lsp/__init__.py,
  tools/browser_tool_cloud.py, tools/mcp_tool_config.py,
  tools/tool_result_storage.py: key process caches by profile home (or bypass
  the slot under an override).

Tests: tests/tools/test_multiplex_turn_parity.py (4, red on base).
Docs: multi-profile-gateways.md isolation table.
2026-09-11 19:39:12 -07:00
ethernet
80273b4507 refactor(pm): route Python tool installs through PM
Use PM-selected interpreters and tool entrypoints for Browser Use, Hindsight and Python language servers. Sync the declared Google Chat extras instead of changing the active environment with pip.

Verified the affected 11-file Nix test subset: 359 passed, 7 skipped. The full suite and real third-party package installation were not run.
2026-09-11 18:05:43 -04:00
ethernet
b64fe7b366 fix(lsp): select the analyzed project's Python first
Pyright selected the Hermes interpreter ahead of an explicit or local
project environment. Keep PM as the fallback rather than hiding the
project's dependencies.

The regression drives the real server registry and spawn options with
disposable venvs, then executes each selected interpreter. Correct the
neighboring Windows pip fixture to patch the installer seam it uses.
No language-server subprocess or user project was changed.
2026-09-09 20:48:13 -04: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
b578261584 fix: keep Kanban worker scope out of descendant processes
Carry the existing write fence across Hermes-owned spawn boundaries without
dropping board routing or changing credential policy. Grant dispatcher and
managed tool runtimes explicit task scope; align CLI task mutations with tools.

Verify real shell/CLI descendants, dispatcher startup, and supervised stdio
transport against isolated SQLite boards. This is cooperative runtime scoping,
not OS confinement.

Refs #103974, #104058, #104904
2026-09-07 07:10:28 -07:00
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
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
2776813df3 compat(plugins): temporary import-path shims for external plugins — ONE commit, revert on schedule
The Sep 2026 decomposition (PR #102117) makes internal import paths a non-API: names now live in
the focused modules that define them. This commit is the ONLY thing keeping the old paths alive,
so external plugins have time to update. It is deliberately a single, unsquashed commit:

    git revert <this sha>

removes every shim, stub and manifest at once on the announced date. Nothing in-tree may depend on
these pointers: scripts/check_compat_pointers.py (wired into lint.yml) fails CI if it does.

What it adds (see COMPAT_MANIFEST.md, compat_manifest.json):
- 332 facade modules get one delimited `PLUGIN-COMPAT` block appended at the end of the file
- 1,172 moved names resolved lazily via a module `__getattr__` (PEP 562) — never a top-level import,
  so no import cycles; facades that already had `__getattr__` get a chained one
- 592 third-party/stdlib names the old modules used to expose, with their original import statements
- 266 public definitions that had been deleted as unused, restored byte-for-byte from the pre-decomposition
  tree (+40 private helpers and 16 imports pulled in only because a restored definition needs them)
- 3 deleted modules recreated as re-export stubs (gateway/startup_watchdog, hermes_cli/observability/
  relay_runtime, tools/environments/modal_utils)
- private names (`_x`) get no pointer: they were never API (3,792 skipped)

Verified: all 335 touched modules import under a fresh HERMES_HOME and every manifest name resolves;
the lint reports zero in-tree uses; ruff clean; targeted suites unchanged.
2026-09-03 17:13:22 -07:00
Teknium
0071ba9965 Merge origin/main (561b053f79) into simp/forwardport: forward-port 220 main commits into the simplified tree 2026-09-03 03:31:03 -07:00