256 Commits
Author SHA1 Message Date
Maciej Lisiewski d62c60787f fix: pnpm review follow-ups in nix, evals, the hang repro and an e2e test
- nix dev shell: pnpm-workspace.yaml (overrides, catalog, allowBuilds)
  joins the package.json stamp, so a settings-only change relocks.
- desktop-pane-fixture: resolve vitest/config from apps/desktop; pnpm
  links it only there.
- short-session hang repro: name the lockfile in the target metadata, so
  an npm-era target fails as a lockfile mismatch rather than a bare hash.
- test_mirrors: the pnpm mirror test prefetches from the registry and
  needs allow_network, like its siblings.
2026-11-06 04:08:46 +01:00
Maciej Lisiewski 29e78b124e docs: pnpm commands for the JS workspaces
READMEs, contributor guides, AGENTS.md files, skills and the website (with
the zh-Hans translations) give pnpm commands for the workspaces; the
website itself is still an npm project.
2026-11-06 04:08:45 +01:00
Maciej Lisiewski 7062229090 chore(js): switch the workspaces from npm to pnpm
pnpm 12.15.0 (native executable) installs every JS workspace from one
pnpm-lock.yaml, imported from package-lock.json with the same resolved
versions. website/, the WhatsApp bridge and the photon sidecar stay separate
npm projects.

Configuration moves from .npmrc and the root manifest into
pnpm-workspace.yaml: the 14-day minimumReleaseAge and its excludes,
overrides, allowBuilds (the old allowScripts set, with strictDepBuilds
failing any unlisted build script), engineStrict, sideEffectsCache: false
(in-place rebuilds must not write into the store) and
verifyDepsBeforeRun: false (a `pnpm run` never installs).

The pnpm version is pinned in pm/lock.json and in root devEngines.
pmOnFail: ignore keeps that pin out of pnpm-lock.yaml: a leading pin
document makes osv-scanner read zero packages. scripts/build/node-deps.mjs
enforces the exact version instead, and every lock reader refuses a
multi-document lock. A root preinstall refuses `npm install`; devEngines
onFail is warn so npx (MCP servers, the tsc linter) keeps working inside
the checkout.

User-machine installs run --frozen-lockfile --trust-lockfile (release age is
checked when locking, in CI, not per user). node-deps removes npm-era
node_modules trees before the first pnpm install. Nix builds the store with
fetchPnpmDeps and a hash file that CI checks.
2026-11-06 04:08:24 +01:00
unsupportedpastels 936f3465f8 refactor(cli): remove the unused one-shot agent that ran outside the gateway
`hermes -z` has run through the gateway (`_run_oneshot_from_args` ->
`launch_gateway_chat`) at every dispatch site for a while, but the old
in-process runner stayed behind: `hermes_cli.oneshot.run_oneshot` /
`_run_agent` built a bare AIAgent outside the session authority, and
`hermes_cli.main._run_and_exit_oneshot` (with its
`_cleanup_oneshot_runtime` cleanup table) was its only caller. Nothing
outside tests reached any of it.

Delete that path. `hermes_cli.main._exit_after_oneshot` stays exactly as
it is: `--cpu-profile` names it as the one-shot hard-exit path and it
writes the profile before `os._exit`; only its docstring loses the
pointer to the removed cleanup helper. `hermes_cli/oneshot.py` keeps
only what the gateway path and gateway CLI turns still import: the
exit-code mapping, the `--usage-file` ledger with its auxiliary-usage
delta, and `--toolsets` normalization. The process-receipt eval probe
now calls `process_registry.wait_for_pending_completions` directly.

Tests that only exercised the deleted runner are removed; the exit-code
and usage-file tests now call the kept helpers directly; the cost-guard
test patches the gateway launcher instead of the removed function. A new
test pins that `hermes -z` dispatches to `launch_gateway_chat`.

Refs #145171 (EP-11a)
2026-11-05 17:59:21 -05:00
Maciej Lisiewski 9f9b9f54a1 fix: review follow-ups for the workspace dependency moves
- The MCP OAuth renderer probe bundled its entry with esbuild resolving from
  the checkout root, so its react, react-dom and react-query imports still
  relied on hoisting. Resolve them from apps/desktop, which declares them
  (verified: from the root they fail to resolve in an isolated pnpm tree).
- The Windows MSIX commit fixture linked node_modules before `git add apps`,
  so the commit could pick up the dependency link. Link the install trees
  after the commit, which needs only the fixture's own files.
2026-11-05 14:08:26 -08:00
Maciej Lisiewski c2a0ecc734 fix: resolve electron, vite and playwright from the workspaces that declare them
Scripts outside the declaring workspace found these through the hoisted root node_modules:
mock-server and connector-rehearsal (whose desktop-local Electron path did not exist under npm),
ui-tui's visual harness, the eval harnesses, and the tests/install/e2e-assets drivers, which import
tests-js's @playwright/test and zod. They now resolve from apps/desktop or tests-js; the typed
e2e-assets files go through a tests-js re-export module so their types resolve the same way.
2026-11-05 14:08:26 -08:00
Teknium b4d7128975 test(cron): the fork-race probe waits for killed descendants to exit
test_cron_timeout_closes_the_descendant_snapshot_fork_window read each
descendant's status the instant _run_job_script returned. SIGKILL is
asynchronous: the killed process stays in state R until the kernel
finishes tearing it down, and freeing its memory waits for a CPU on a
loaded runner. CI caught the pre-existing setsid child there
({gone, running, zombie}).

The kill was delivered. A scratch dump at the check showed the "running"
child in state R with SIGKILL pending (ShdPnd 0x100) and PF_EXITING set,
and it exited 86-233 ms later. kill_process_tree is correct.

The probe now waits up to 10 s for every recorded descendant to be gone,
a zombie or dead (the transient X state while its parent reaps it). The
children now sleep 60 s instead of 15, so a descendant the kill missed
is still alive when the wait ends and the probe still fails.

Repro: give the first child 1.5 GiB of touched memory, so its exit takes
~100-200 ms. Base fails 12/12 with CI's shape; the fixed probe fails
0/12. The fixed probe still fails when the fork-window stop+rescan is
reverted, and when snapshotted descendants are never signalled.
2026-10-25 04:45:42 -07:00
Teknium e82c354b28 fix(evals): desktop eval probes find pm's Chromium through PLAYWRIGHT_BROWSERS_PATH, not the retired AGENT_BROWSER_EXECUTABLE_PATH 2026-10-23 01:43:16 -07:00
Teknium 68500d9ff9 refactor(browser)!: delete the built-in browser_* tools, agent-browser and bundled Camofox
browser_exec is the only core browser tool (plus browser_cdp, browser_dialog,
browser_vault, all CDP). Deleted: browser_navigate/snapshot/click/type/
scroll/back/press/get_images/vision/console and every module that served
them (browser_tool_session/_lifecycle/_cloud/_cdp/_real_profile/
_lightpanda_fallback/_eval_policy/_origin/_snapshot/_vision) plus
browser_camofox*.py (Camofox ships as the catalog plugin 'camofox').
tools/browser_tool.py and tools/browser_tool_install.py shrink to the
frozen old-updater stub warm_agent_browser_npx_cache.

toolsets: _HERMES_CORE_TOOLS / browser toolset list only the CDP tools.
model_tools: browser_tools legacy alias and the browser_navigate /
browser_type schema rewriters removed. Cloud providers no longer request an
install post_setup hook (nothing to install).
2026-10-23 01:41:24 -07:00
Teknium d7db4d109d refactor: OpenClaw migration leaves core too; first-time setup offers the agent-importers plugin
`hermes claw` (hermes_cli/claw.py) and the openclaw-migration optional skill
moved into the agent-importers plugin (hermes-official-plugins #140/#141), which
keeps `hermes claw migrate|cleanup` as the older spelling of
`hermes import-agent openclaw`. Core drops them with their tests (ported to the
plugin) and the eval leg that loaded the skill's script.

Setup: hermes_cli/setup_agent_import.py replaces setup_migration's OpenClaw
offer. On a first-time `hermes setup` it checks ~/.openclaw (or ~/.clawdbot /
~/.moltbot), ~/.claude, ~/.codex and ~/.cursor; when any is found it asks
"Import your setup from <found>?" (default yes), shows a checklist when more
than one was found, installs agent-importers through the normal catalog install
(or enables it when present), then runs `hermes import-agent <agent>` for each
pick in a child process, which previews and asks first. OpenClaw gets
--migrate-secrets, as core's step did. Nothing found, a no, or
HERMES_NONINTERACTIVE: no prompt, nothing installed. The skip-configured-section
offer now follows any import that changed config.yaml or .env.

LEFT_CORE: `claw` joins the agent-importers row as old_cli, so `hermes claw`
without the plugin names the install. No automatic install for it: an OpenClaw
migration is one-shot and leaves no state that needs the plugin later.

Docs: import guide, OpenClaw guide, cli-commands, quickstart, bundled
cli-reference; locale tips and README pointers.
2026-10-22 11:22:12 -07:00
kshitijk4poor 9ca23c6840 fix(cli): run every /cron form on the gateway that owns the cron store
Symptom: in a classic `hermes chat` session on the shared gateway, /cron
add|edit|pause|resume|run|remove still ran main's handler in the CLI
process, which called the cronjob tool there and wrote the profile's
cron/jobs.json from the client. Under strace, every write to jobs.json
came from the `chat --cli` process and none from the gateway.

Root cause: the one-gateway runtime (#106742) made the gateway the
owner of the profile's runtime state and of the ticker that fires cron
jobs. It left CLICommandsMixin._cron_* in the client, and the
gateway's slash.exec refused /cron for every local session except TUI
reads (2efe028116). That left the cron store with two writers.

Change:
- Move main's /cron handler (overview, list and every write) into
  hermes_cli/cli_cron_lines.py as line builders (cron_command_lines),
  with no other behaviour change. The CLI prints them when it has no
  gateway session. The unused CLICommandsMixin._cron_list goes; its
  test and eval read cron_list_lines directly.
- On the gateway, /cron is an owner command
  (gateway/session_cron_commands.py). Bare /cron and `list` are reads
  for every local session; that includes Desktop- and ACP-created
  sessions, which main refused with unsupported_command. Every other
  form is an admission-fenced control. An unclosed quote is refused as
  invalid_params before pre_command fires. `/cron list -a` keeps the
  list-all alias that the TUI read path accepted.
- A /cron write runs only when it arrives on a connection that is not
  attached to the session: the classic CLI's own command socket
  (GatewayConnection.call_until), which waits with no deadline. A
  connection attached to the session keeps main's unsupported_command.
  That is how Ink, Desktop and ACP send /cron: on their main
  connection, which the gateway answers one request at a time and
  which waits with a deadline, while a /cron run lasts as long as its
  job. The gate is on the requesting connection, not on the surface
  that created the session, so a classic CLI that resumed a TUI-made
  session still writes (as on main), and Ink on a CLI-made session is
  still refused.
- The classic CLI sends /cron to slash.exec
  (gateway_setting_commands.GATEWAY_SETTINGS) with no deadline, on a
  socket of its own (gateway_plugin_commands.call_unbounded, now shared
  with plugin commands; its Ctrl+C handle is renamed
  _gateway_command_wait). Ctrl+C prints the stopped line and
  propagates.
- Regenerate the parity table. test_pre_command_in_gateway pinned the
  old refusal of every classic CLI /cron; it now pins that the accepted
  /cron forms fire pre_command once and a refused one (an unclosed
  quote) fires nothing.

Why: the design contract allows one writer per owned store, and
clients only read and submit. The gateway already serves this
profile's cron store to its ticker and to Desktop (cron.manage). Now
the classic CLI's writes go through the same process.

Refs #134889 (T1)
Refs #139158
2026-10-20 13:08:24 +05:30
Maciej Lisiewski 91fae5f8cb ci: run everything on blacksmith.sh runners 2026-10-15 05:23:46 -07:00
Teknium 0715474bfd Merge origin/main into feat/unified-gateway-runtime
375 commits from main (0e427d788d). 24 files conflicted; the resolutions that carry a decision:

- /initiate-setup: main removed the command (25bf722063). Its gateway rewrite, CLI handler and the
  Desktop kickoff go with main; this branch's command-layer admission of it is dropped too
  (gateway/session_commands.py _PROMPT_COMMANDS, its slash.exec test row, the parity doc row), and
  the Desktop slash dispatcher loses the `hidden` option that only the kickoff used.
- Code this branch moved out of its old file, carrying main's edits to the moved copy:
  first-contact notes (gateway/run_turn.py -> run_turn_prepare.py: no message/setup_command),
  the TUI agent factory (tui_gateway/server.py -> agent_factory.py: onboarding toolsets at build,
  setup_beat/set_theme lifecycle cards, no cli platform extras on a coding selection), the serve
  lifespan (web_server.py -> web_server_app.py standalone_lifespan: first-install probe before the
  state.db reconcile, onboarding due answer after the setup bootstrap), and the compression child
  publish (the shared _publish_compression_child_on_conn reads the parent's model_config).
- agent/auxiliary_client.py: both import changes (_route_key from main; this branch's move of
  _without_structured_output_format).
- Desktop: main's onboarding run (decideOnboarding, OnboardingEnd, card-waiting Send) replaces the
  kickoff; this branch's prepared-submission slash path, request_id on session.create and
  PreparedImageRecovery stay. Pool-limits imports are dropped (this branch removed the pool).
  createBackendSessionForSend takes main's (preview, overrides) signature.
- ci.yaml detect job: main's `ref: inputs.commit` plus this branch's sparse checkout.
- package.json / lock: main's vitest 4.1.11 plus this branch's ws 8.21.1.
- Generated: gateway-contract.openrpc.json regenerated; parity doc regenerated.
2026-10-14 05:52:49 -07:00
alt-glitch 72e7085e9e feat: set_theme replaces apply_layout and reports what the desktop applied
set_theme (desktop_ui, deferred like apply_layout was) sets the accent,
light/dark mode and layout preset, or with `picker` shows an accent or
layout card and waits up to 300 s. apply_layout emitted a layout.apply
event and returned success whether or not the renderer applied anything;
set_theme asks the renderer through the theme.apply server request and
returns its answer, so an unknown preset or a chat that is not on screen
comes back as an error with the available ids.

theme.pick waits for a person; the backend tells a dismissed card
({cancelled}) from an expired one ({timed_out}). The tool description
lists the Simple and Advanced preset ids from one constant, which fixes
the old description that named neither sidebar preset. The layout.apply
event and its renderer handler are deleted with the tool.
2026-10-13 23:55:10 +05:30
Teknium a89f374d7c Merge origin/main (#45: 255 commits to 3b63f7b0a4) into feat/unified-gateway-runtime
Conflicts (2 files, 1 hunk each); every main-side change is kept, ported to
where the branch moved the code:

hermes_cli/kanban_db.py (build_worker_context helpers _ctx_cap .. _ctx_comments)
  main:   f5a6651231c/d56e357556f coerce foreign/text timestamps on read; in
          _ctx_role_history the "Recent work" stamp became
          _ctx_stamp(_to_epoch(row['ended_at']) or 0, now) instead of int().
  branch: moved the whole _ctx_* helper block into hermes_cli/kanban_db_context.py
          (kanban_db.py imports it back), deleting it from kanban_db.py.
  merged: kept the branch's deletion in kanban_db.py and applied main's change in
          kanban_db_context.py::_ctx_role_history as
          _ctx_stamp(_kb._to_epoch(row['ended_at']) or 0, now). Main's other
          timestamp coercions in kanban_db.py (Task/Run/Comment/Attachment/Event
          from_row, _TASK_EPOCH_COLUMNS, release_stale_claims,
          _extend_live_stale_claim, task_age) auto-merged unchanged.

hermes_cli/web_server.py (_lifespan)
  main:   d7830c6f42 reworded one comment in the free-tier bootstrap block
          ("the ONE place its identity is created" -> "the one boot-time creator
          of its identity"; guests are now also created on first connector use).
  branch: moved _lifespan's body into hermes_cli/web_server_app.py::app_lifespan.
  merged: kept the branch's relocation and applied main's comment rewording in
          web_server_app.py.

Merge-induced health fix (no conflict marker; the combination tripped the ratchet):
apps/desktop/src/app/session/hooks/use-cwd-actions.ts::changeSessionCwd
  main:   63ed3caa4ba/dbc538547fc grew the function (statusbar cwd change for the
          focused chat / tile runtime ids) to CC 20.
  branch: added the session_busy refusal mapping (sidebar.projects.moveBusy) in
          its catch, +1 branch -> CC 21 > target 20 (scripts/check health blocking).
  merged: both behaviours kept; the busy-error mapping moved verbatim into a
          module-level cwdChangeError(err) helper. Health back to 0 blocking.

Check: every line main added between 1fd1827726 and 3b63f7b0a4 (317 files) is
present in the merged tree.
2026-10-12 16:55:20 -07:00
Teknium 2ddbd66ac6 eval(prefix_rediet): tolerate string-encoded tool_call entries in the call tally 2026-10-12 03:18:48 -07:00
Teknium 3bf97898ac perf(prompt): fixed prefix back under the post-diet size (-2.1k tokens per request)
The stock prompt surface regrew ~3.4k tokens after the August diet, almost
all from features added straight into the never-deferred core. This takes
back the biggest pieces:

- browser_vault: the five eager browser_vault_* tools (list, fill, unlock,
  save_login, enter_code) become one browser_vault tool with an action enum,
  deferred behind tool_search by default. It still rides with the browser
  (an empty vault keeps save_login reachable), so discovery holds; only a
  catalog line is paid per request. Results and errors name the new actions;
  the input-tool password note points at it through tool_call.
- terminal: schema re-dieted (1,136 -> 651 Qwen tokens). Description and
  param docs no longer repeat each other; heartbeat / persist_on_release keep
  their rules in one line each; process(...) references name process_manage.
- tool_search / tool_call: the keyword-query rule lives once, in the queries
  param; description and connector hint trimmed.
- system prompt: the "Async handoff" block is removed. Its rule ships in the
  delegate_task description and in the background dispatch result the model
  reads at the moment it matters, and the polling loop it targeted is guarded
  structurally (#124724).

Stock CLI surface (prompt-surface render, chars/4): 18,612 -> 16,524.
Fresh install, Qwen3 tokenizer: 14,962 -> 13,120 (Sep 7 post-diet: 11,595).

Live A/B harness: evals/prefix_rediet/.
2026-10-12 03:18:48 -07:00
Teknium aefb5b097e docs(evals): the normal-boot probe is expected to pass; drop stale wave notes
evals/shared_gateway/README.md said the "unintegrated bootstrap is expected to fail"
normal_boot.py; this PR integrates it and tests/gateway/test_normal_runtime_boot.py
asserts state == 'ready' from the real control socket. The README now says a pass is
expected and a failure is a regression.

The four worker wave notes (worker-persistence-inventory.md and the -substrate,
-lifecycle-wave22, -constructor-context-wave20 notes; ~2,000 lines) were pinned to
an old snapshot, cited private worktrees and .hermes/plans paths, called worker
switching "still blocked", and nothing links to them: removed from the shipped tree.

Docs only; no test.

Review: ahrazzle B2-28
2026-10-11 19:31:40 -07:00
Teknium 129234cee6 docs(cli): classic chat resumes latest and queues at the gateway
Docs follow code (round-13 ruling for stale docs):
- cli.md said classic `hermes chat` refuses bare --continue and --resume latest
  (exit 2). gateway_chat.wants_latest/_resume_latest resolve both through the owner
  and _UNSUPPORTED holds neither; the developer guide already documents them. The
  refused list also omitted --compact, which _UNSUPPORTED refuses. Examples and the
  info box now match _UNSUPPORTED.
- The busy_input_mode `queue` row said the classic CLI queues in-process.
  GatewayChatView.submit sends every line to prompt.submit, never reads
  busy_input_mode and has no steer/redirect path: classic chat always queues at the
  gateway; interrupt/steer are TUI/Desktop behaviours.

Docs only; no test.

Review: ahrazzle B2-23
Review: ahrazzle B2-24
2026-10-11 19:31:40 -07:00
Teknium 994759f4c4 Merge origin/main (#39: 29 commits to d4d2ab7b5d) into feat/unified-gateway-runtime
No conflicts. Brings in main's reasoning-replay series, holographic memory leaving core
(run_profile_reconcile now imports it through import_provider_module), and the update
main-channel fix.
2026-10-10 23:38:42 -07:00
Teknium 655546246c chore: strip the trailing blank lines at EOF that git diff --check flags
Eight files the branch adds ended in one or more blank lines, so
`git diff --check $(git merge-base HEAD origin/main) HEAD` reported 8 "new blank line at EOF"
errors. Whitespace-only: each file now ends with exactly one newline; no other line changes.

Check (8 errors on r11-base, 0 here): git diff --check c57331677d HEAD

Review: andrexibiza 24
Review: andrexibiza 5479692359-P2.10
Review: JoaoMarcos44 diffcheck
(cherry picked from commit 6b34c53b7041b037431405282a6fb2d76f657893)
2026-10-10 22:49:21 -07:00
Teknium 87da183a8a refactor(evals): split reasoning-replay summarize into per-cell helpers
Keeps the matrix byte-identical while meeting the code-health CC/nesting
targets, and narrows the network catch to the errors urlopen can raise.
2026-10-10 20:49:30 -07:00
Teknium 7a70cbd0db fix(evals): reasoning-replay treats Anthropic refusals like xAI 403s
Anthropic answers a probe that asks about "earlier reasoning" with a 200
content_filter refusal and no usage block, which left cross-turn cells
without a baseline. Both refusal shapes now rerun the cell with neutral
wording, and opaque cells pick the newest turn-1 capture per position so a
partial rerun does not mix captures.
2026-10-10 20:49:30 -07:00
Teknium 2d1077b001 fix(evals): reasoning-replay harness measures opaque carriers reliably
Newer OpenAI/Gemini models emit no reasoning on a trivial turn 1, so step 2
had nothing to replay; turn 1 now poses a small arithmetic task and escalates
effort until a reasoning carrier and tool call come back. Opaque cells are
compared only within one turn-1 capture (run_id), xAI's safety-layer 403 on
"earlier reasoning" falls back to neutral probe wording per cell, xAI
cost_in_usd_ticks is read as billed cost, and keyless local servers cost $0.
2026-10-10 20:49:30 -07:00
Teknium cb4342c238 feat(evals): reasoning-replay consumption harness
A 200 only proves a provider accepted a replayed reasoning field; the gateway or
chat template may still drop it. This stdlib-only harness measures whether each
carrier (reasoning_content / reasoning / reasoning_details / Responses reasoning
items / Gemini thought parts and signatures) reaches the model's prompt: the
prompt-token delta against a byte-identical baseline, plus canary recall, for
in-loop and cross-turn positions, and real turn-1 opaque carriers replayed
verbatim vs stripped. Portal calls are keyed by lane (gen- = OpenRouter) and
can be resampled until a direct-lane result exists. Results stay out of tree.
2026-10-10 20:49:30 -07:00
Teknium e1a10b0bc2 ci: bring the branch's own code under main's strict ruff config
Main made the strict ruff config the default (`ruff check .`, blocking), and
the branch's code carried 278 findings under it (main itself is clean). Fixed:

- PLW1510 (86): `check=False` on each subprocess.run, preserving behaviour,
  as main's own sweep (0006a5e229) did.
- ASYNC220 (9, test fixtures only): `noqa` with the reason, main's convention
  for a Popen that returns at once and whose handle the test drives.
- ruff's safe autofixes plus the narrow unsafe ones (UP006/UP037/RUF100/RUF013/
  PLR0402/RUF059 underscore renames/UP031/UP041/UP017/FURB167/PIE807/RUF007/
  RUF015/RUF022/FURB162), and by hand: four %-formats to f-strings, three
  unused unpacked names, one fixture loop over `.items()`.

No behaviour change. `ruff check .` passes; scripts/check passes; the 412 test
files mirroring every edited file pass (3 tests/pm files that fail only under
that batch's uv lookup pass solo, before and after this commit).
2026-10-10 17:59:08 -07:00
Teknium b1ccce898b Merge origin/main (#38: 70 commits to c57331677d) into feat/unified-gateway-runtime
Conflicts: tests/hermes_cli/test_finite_chat_delegation.py and
tests/tools/test_completed_process_results.py, where main's lint sweep (0006a5e229) added
check=False to subprocess.run calls this branch had rewritten. Kept the branch's test bodies
and carried check=False onto their subprocess.run calls.
2026-10-10 17:47:35 -07:00
Maciej Lisiewski c57fbf4584 ci: fix remaining ASYNCXXX linter errors 2026-10-10 15:20:14 +02:00
Maciej Lisiewski bda3aea496 ci: remove unused variables (linter) 2026-10-10 15:20:14 +02:00
Maciej Lisiewski 7ef490ed3b ci: fix PLC0206, PLC0414 linter errors 2026-10-10 15:20:14 +02:00
Maciej Lisiewski 0006a5e229 ci: fix PLW1509, PLW1510, PLW2101 linter errors 2026-10-10 15:20:13 +02:00
Teknium 408cc0d12e Merge origin/main (#32: 36 commits to cf23a1a5cc) into feat/unified-gateway-runtime
17 conflicts, all from main's lint sweep (ruff UP007/UP017/UP022/UP028/UP031/UP037/UP041,
FURB188/FURB192); no other main commit touches a conflicted file. Resolution: the branch side
of each file plus main's exact autofix (ruff 0.15.10, main's ruff.strict.toml, --fix
--unsafe-fixes). ruff.strict.toml takes main's copy. The pool-retirement live fixture stays
deleted with the pooled backend this branch retires.
2026-10-09 10:24:04 -07:00
Maciej Lisiewski 2ff58849f4 ci: ruff UP037 fixes 2026-10-09 17:34:38 +02:00
Maciej Lisiewski 9eab688b42 ci: ruff UP017 fixes 2026-10-09 17:34:36 +02:00
Maciej Lisiewski aff64c3eeb ci: UP007, UP022, UP028, UP031, UP041 linter fixes 2026-10-09 17:31:52 +02:00
Maciej Lisiewski 32b70e9733 ci: FURB188, FURB192 linter fixes 2026-10-09 17:31:52 +02:00
Teknium 607a23e858 Merge origin/main (#30: 54 commits to 1744a19e0d) into feat/unified-gateway-runtime
8 conflicts, all from main's lint sweep (#135281 "Harder linter fixes" + the RUF series:
implicit-Optional, unused-unpacked-variable, next(...) for [...][0]). No other main commit touches
a conflicted file. Resolution: the branch side of each file plus main's exact autofix
(ruff 0.15.10, main's ruff.strict.toml, --fix --unsafe-fixes) on it. ruff.strict.toml takes main's
copy (the newly enabled rules). tests/cron/test_bot_chat_delivery_child_env.py stays deleted
(1cc9cb9199 retired its lane).
2026-10-08 21:38:54 -07:00
Maciej Lisiewski 4ee5e8e0f3 ci: RUF007 2026-10-08 21:35:58 +02:00
Teknium 65bec86e37 Merge origin/main (#27: 19 commits to 517b5e10fe) into feat/unified-gateway-runtime
45 conflicts, all from #135177 (lefthook + `ruff check --fix` over 2,366 files). No other main commit
touches a conflicted file. Resolution: the branch side of each file, then main's exact autofix
(ruff 0.15.10, `--config ./ruff.strict.toml --fix`) on it — rewrites only (typing generics, noqa
drops, import forms), no import removed. tests/cron/test_bot_chat_cli_home.py stays deleted
(1cc9cb9199 retired its lane).
2026-10-08 12:09:24 -07:00
Maciej Lisiewski d76cce4f11 ci: apply all automatic linter fixes available 2026-10-08 17:22:54 +02:00
Teknium 62a4946e84 fix(desktop): a group-writable private-group profile home attaches without being chmodded back
Desktop refused any group-writable profile home (mode & 0o022) because Node cannot prove the
group private (no NSS/xattrs). The gateway's own policy (home_mode_unsafe) admits group-write
for the owner's private group with no ACL — the umask-002 user-private-group default. The
refusal was classified as a stale gateway, so Desktop re-ran `gateway ensure`, whose home
maintenance chmodded the operator's directory back to 0700 to get through.

Now Node refuses only world-write; a group-writable home is handed to the installed runtime's
Python ticket client (the same helper Windows uses), which applies home_mode_unsafe before
minting. A shared group or a named-user ACL comes back as unsafe_control_permissions and is
reported as "run chmod g-w <home>", never as a stale gateway. Socket, pointer and fallback dir
stay owner-only.

Live (real Electron + gateway, 0775 home set after first-dial maintenance): before, the second
dial logged "forgot stale canonical endpoint ... re-ensuring" and the home ended 0700; after, it
mints with the home still 0775.
2026-10-08 03:42:44 -07:00
Teknium b6bf0183ba refactor(gateway): narrow or log blind excepts; time out eval subprocesses
Code-health ratchet on PR #106742 (BLE001/S110/HX006), no behaviour change:
- narrow per-row JSON skips: hosted_room_attachments.reconcile_room_events
  (ValueError/TypeError/AttributeError/RecursionError), hermes_state_gateway
  routing-entry lookups (ValueError/TypeError/RecursionError)
- approval_context: only the gateway import is optional (ImportError);
  finite_turn_required() is a ContextVar read that cannot fail
- deliberate boundaries keep their behaviour and now log the traceback:
  profile park (exc_info), session-store init (module logger, same
  "gateway.run" name, still WARNING), a2a profile dispatch (local log;
  outbound reply stays redacted), readiness probe and session_search
  profile open (debug), Windows control-pipe thread hand-off (debug; start()
  re-raises), eval harness top level (logger.exception)
- managed_worker.main: traceback goes to the worker's own stderr (the owner
  spawns it with stderr=DEVNULL) via a non-propagating handler, never the
  channel or profile log files; the owner still gets only
  managed_worker_failed
- evals/shared_gateway: timeout=30 on git rev-parse/diff calls

(cherry picked from commit dfc56cdd5ab875bd14b921de71be4a5bc8afc57f)
2026-10-07 10:57:34 -07:00
Teknium ca38632fc0 Merge origin/main (#17: 454 commits to e6757f2029) into feat/unified-gateway-runtime
12 conflicted files, resolved in three lanes:
- gateway/run.py: take the branch split; main's tirith removal and spool-replay
  ordering ported into run_runtime_init.py / run_bootstrap.py.
- config migrations: main keeps v50 (tirith); the branch's bot-chat timeout removal
  becomes v51 (_config_version 51, template + test updated).
- hermes_state_usage.py: main's voice_chat task route threaded through the branch's
  owner transaction; worker.persist usage allowlist accepts task.
- tui_gateway/server.py: branch side; main's edits applied where the code now lives
  (agent_factory, session_registry). Desktop sessions get the catalog toolset on the
  authority path; daemon-run finite CLI turns are not watched turns; lazy session info
  reports nous/welcome on the free tier.
- Desktop/Ink: one branch-child spinner fix (main's route-token check); main's
  uninstall i18n keys kept, pool keys stay removed; voice_turn forwarded and accepted
  by the authority submit (surface_v1.voice_turn).
- chat_ws / PTY keepalive test: main's stricter test kept.
2026-10-07 06:48:09 -07:00
kshitijk4poor 45e44b0766 chore(evals): drop the PR-local cron due-scan eval script
The salvaged fix ships with a real tick() regression test; a standalone eval script under evals/
duplicates it and is not run by CI.
2026-10-07 10:20:18 +05:30
PRELVAS94 86362d8935 fix(cron): keep dispatching when the due scan cannot persist its repairs
_get_due_jobs_locked repairs store-side problems mid-scan (half-paused records
self-disable, completed one-shots are swept, records are normalized) and then
persists them with a bare save_jobs(...) before returning the due list.  Any
store-write failure there — ENOSPC on a full disk, a read-only mount, a
permissions problem — raised out of get_due_jobs(), so cron_tick() aborted and
NO job on that profile ran until a write succeeded.  Live on a full /opt/data:
"Cron tick error for profile at /opt/data: [Errno 28] No space left on device"
from that exact call site, repeating every tick, with every job then reporting
"missed its scheduled time" and re-anchoring.

The repairs are already applied in memory — that is what the scan keys off — so
the persist is a side effect the next tick retries.  Contain the store write and
keep dispatching; a non-store error still propagates.

(cherry picked from commit 76676fb0084f06890f76280f50387325ad2d59c7)
2026-10-07 10:20:18 +05:30
Teknium 1e500ebce2 Merge origin/main (#16: 4,465 commits to 9dcab1e440) into feat/unified-gateway-runtime
99 conflicted files resolved in nine area lanes (Desktop composer, Desktop renderer/shared
client, Electron main, Ink, tui_gateway, gateway, state, CLI/config, cron/tools/CI). The
unified runtime's architecture wins where it deliberately replaced a mechanism (pool,
fallback lanes, claim poller, renderer queue); every orthogonal main change is kept, ported
into the <stem>_<topic> sibling that now owns relocated code.

Notable ports and repairs in this merge:
- tui_gateway/server.py relocated regions: atomic _attach_built_agent (#49852), slash ranking
  registry names, minted-key helpers methods_session.py calls, request_overrides, service tier.
- gateway/run_turn*.py: i18n accessors for the removed reply constants, first-contact
  internal/DM rule, rotated-compression persist, fast-mode tiers, prompt-pin rehydration.
- webhook: route_from_chat_id decodes main's opaque webhook:v2 chat ids (old ids still read).
- ws.py: main's per-connection dispatch queue with authority-first dispatch inside it.
- state: schema event for runtime revision renumbered to 31; worker mirrors updated for
  main's message uid/merge fields, on_contended lease arg, compression columns.
- config: branch's bot_chat_delivery_timeout removal renumbered v47 -> v50.
- Electron: primary-profile pin, redirect header scoping, remote fetchJsonForBackend, SSH
  teardown on profile delete, held-update dial gate restored (lost in earlier merges).
- Desktop: canonical clarify.request mapped to main's questions[] card; replay truncation vs
  snapshot_required; periodic secondary prune tick restored; DELETE already_absent restored.
- cron/tools: diagnostic category on cron failure notices restored; owner_holds_delivery
  ported onto the authority; admitted receipts can no longer be cancelled locally.
2026-10-06 14:24:24 -07:00
Teknium 911da42e9e refactor(security): remove the bundled tirith scanner from core
tirith ran `tirith check` on every terminal command, default-on with fail-open, as a
pinned PM binary auto-downloaded at startup. Measured against the shipped 0.4.2 binary
it caught 14/16 attack commands but flagged 12/24 everyday agent commands
(`echo $((6*7))`, `"$VAR/x.sh"`, `gh api | python3`, LAN curls, `.dev` hosts,
`pip install` via analysis_incomplete), and in cron/-q deny mode each of those is a
hard block. It only ever saw terminal strings (execute_code, writes and the browser
bypass it), fail-open meant no protection wherever the binary could not install
(Windows, Termux, x64 musl), and it produced a steady issue/PR stream (fail_open
semantics four times, install loops filling disks, a SIGSEGV hang, session-approval
scoping, third-party promo text on approval cards).

Removed: tools/tirith_security.py, the tirith branches of the command guard (the
is_tirith tuple, session-only "always" downgrade, import-error/fail-open paths,
unattended tirith pass), the CLI and gateway startup installers, the PM package and
lock pins, the nix runtime dep, security.tirith_* defaults, the CLI warning string
and three tirith tips (gaps refilled from the tail so translations survive), plus
the gateway's "no automated assessor" warning that only existed to point at tirith.

Config v50 drops security.tirith_* and enables nothing in its place. Hermes ships
no tirith plugin; if tirith upstream publishes one, it goes through normal catalog
admission under their name.
2026-10-06 14:22:13 -07:00
teknium1 5fa55d1678 feat(plugins): run third-party plugins in a per-profile plugin host (plugins.isolation: host)
plugins.isolation: host imports every non-bundled plugin in a host process started
on demand per profile with only that profile's environment, and hands plugins a
RemotePluginContext; Hermes registers proxies for tools, hooks, commands, skills and
provider objects (subclasses of the ABC it checks). Re-entrant: handlers may call
ctx back into Hermes while Hermes waits on them. Every user-code import path routes
to the host (general, memory/context-engine/cron, model-provider profiles via a
cached credential-free extraction, dashboard APIs via an ASGI bridge) or refuses.
One table names what cannot cross (live gateway objects); the static audit shown
by `hermes plugins validate/show` reads it. A host that dies is restarted with its
plugins (bounded). Default stays in_process: nothing changes unless opted in.

Why: shared-pool deployments must never run one tenant's plugin Python in an
interpreter holding another tenant's data, without plugins becoming tenant-aware.
2026-10-03 10:15:11 -07:00
Teknium 141cc0dbc8 eval(core-tool-deferral): keep agents' crontab and HOME writes inside the run sandbox (#132314)
The worker isolated HERMES_HOME only. The longrange_backup_pipeline task
("schedule it nightly at 2am") let agents run the operator's real crontab
and write into the real ~/.hermes: a past battery left three nightly cron
jobs and two scripts behind on the host, pointing into deleted run dirs.

sandbox.isolate_host turns on the product's own TERMINAL_HOME_MODE=profile
contract, points the worker's HOME at the same profile home (in-process
~ expansion for write_file), puts a file-backed crontab shim first on PATH
and unsets the systemd user bus. README quickstart drops its /tmp paths.
2026-10-03 09:57:41 -07:00
teknium1 7e53b3ef82 chore(memory): remove bundled honcho provider; install from the plugin catalog 2026-10-02 04:39:28 -07:00