Commit graph

7187 commits

Author SHA1 Message Date
Rod Boev
4cbc70c778 fix(#4812): satisfy replay boundary lint gate 2026-07-10 10:51:34 -04:00
Rod Boev
956c276362 fix(#4812): close session SSE replay recovery gaps 2026-07-10 10:46:47 -04:00
Frank Song
4c2400261a Merge remote-tracking branch 'origin/master' into paperclip/pr-5732 2026-07-10 21:21:47 +08:00
igorroncevic
dea5e8a96d fix: fail closed on incomplete OpenRouter pricing 2026-07-10 15:18:39 +02:00
nesquena-hermes
d7c3c2b734
Release: repair stale session provider binding at chat start (#5873, #5731) (#5881)
* fix(#5731): repair poisoned session provider at chat start

* fix(#5731): fail safe on incomplete catalog ownership

* changelog: session provider-repair on stale binding (#5873, #5731)

---------

Co-authored-by: Rod Boev <rod.boev@gmail.com>
Co-authored-by: t <a@b>
2026-07-10 06:11:34 -07:00
Rod Boev
da885659bd fix(streaming): satisfy session SSE lint gate 2026-07-10 07:37:16 -04:00
Rod Boev
b2a9ca28ad fix(streaming): harden session SSE replay (#4812) 2026-07-10 07:31:00 -04:00
Frank Song
c641a9d37c Keep streaming terminal events after no-pause success 2026-07-10 14:43:41 +08:00
Frank Song
a61586bd7f Preserve latest session on wakeup pause clear 2026-07-10 13:20:40 +08:00
Frank Song
92ea69c7c3 Require fingerprint evidence for wakeup credential recovery 2026-07-10 12:23:02 +08:00
Frank Song
8eabf63049 Lock streaming wakeup pause success clear 2026-07-10 12:01:24 +08:00
Frank Song
e604b76720 Merge remote-tracking branch 'origin/master' into paperclip/pr-5732 2026-07-10 11:39:50 +08:00
Frank Song
920b5e8864 Preserve wakeup pause on cancelled stale credential failure 2026-07-10 11:39:31 +08:00
nesquena-hermes
de0e6eb452
Release: centralize auxiliary-model task catalog (#5865) (#5870)
Auxiliary-model task definitions were maintained in BOTH Python and JS and could
drift (the retired session_search task lingered in one). Centralize to a single
backend catalog that panels.js renders+saves from exclusively; purge the retired
slot on reset while preserving other custom bindings; old configs still load.

Gate-pass: Codex SAFE (only session_search removed, 11 other tasks + defaults
unchanged, 22 keys match across 15 locales, legacy migrate preserves custom
bindings, executable DOM harness rendered all rows). 53 own + 887 regression green.

Co-authored-by: t <a@b>
Co-authored-by: starship-s <starship-s@users.noreply.github.com>
2026-07-09 20:31:54 -07:00
Frank Song
bd083c8b54 Merge remote-tracking branch 'origin/master' into paperclip/pr-5732 2026-07-10 11:19:07 +08:00
nesquena-hermes
9c1e77de48
Release: fix Experimental channel version display on stable-tagged installs (#5864, #5862) (#5868)
* fix(updates): scope installed-version fallback to webui + verified current_sha (#5864 gate fixes)

Gate (Codex) found 2 CORE in the #5862/#5864 experimental-version-display fix:
1. api/updates.py:947 — the installed-version fallback ran in shared _check_repo_release
   for BOTH repos, injecting WEBUI_VERSION (v0.52.0, not an Agent ref) into Agent payloads.
   Now scoped to name=='webui'.
2. api/updates.py:996 — current_sha reused current_version_display, but WEBUI_VERSION can be
   vX.Y.Z-dirty-<hash>/-N-g<sha>/bare-SHA/unknown (not git refs) -> broken /compare link +
   lost update-summary subjects. current_sha now derives from a git-VERIFIED exact-match tag
   (channel-neutral) or None; current_version keeps the real display string.

+2 regression tests (agent-repo-no-inject, dirty-HEAD-verified-sha). 21/21 update-channel green.

* changelog: #5864 experimental-channel version display fix (#5862)

---------

Co-authored-by: t <a@b>
2026-07-09 20:18:03 -07:00
Frank Song
fc262a4aec Fix named-profile wakeup credential recovery 2026-07-10 11:14:08 +08:00
Frank Song
9d5a3c45c8 Merge remote-tracking branch 'origin/master' into paperclip/pr-5732 2026-07-10 10:33:46 +08:00
nesquena-hermes
803b081506
Release: lazy-render collapsed worklog rows w/ post-process + disclosure preservation (#5860, #5839) (#5866)
* fix(#5839): post-process + restore disclosure on lazily-materialized worklog rows

Gate (live Chromium) found the deferred-render path skipped two things the eager
path does: (1) materialized Markdown rows bypassed postProcessRenderedMessages ->
lost syntax highlight / copy button / mermaid / katex / trees; (2) detail-disclosure
(open tool/thinking card + scroll) was lost across rebuilds because restore ran
before deferred rows existed. _materializeDeferredWorklogRows now rAF-schedules
_postProcessWithAnchorSuppression + re-applies stashed disclosure state; the rebuild
restore pass stashes the captured state on still-deferred groups. +2 regression tests.

* changelog: #5860 lazy-worklog frontend fix (#5839, #4633)

---------

Co-authored-by: t <a@b>
2026-07-09 19:25:11 -07:00
Frank Song
63dda7fe2d Merge remote-tracking branch 'origin/master' into paperclip/pr-5732
# Conflicts:
#	api/models.py
2026-07-10 10:15:21 +08:00
nesquena-hermes
e4342a65cf
Release: split anchor_activity_scenes after messages to fix #4633 server memory growth (#5858, #5854) (#5863)
Reasoning-heavy sessions accumulate 250-480KB of anchor_activity_scenes serialized
BEFORE the messages array, so the 64KB metadata-prefix read always overflowed ->
full-parse fallback on every read -> memory growth. Move scene data AFTER messages;
metadata prefix now ~2KB, modern reads skip the full parse. Old-layout files still
load (one authoritative parse then cached); round-trip preserves scenes exactly.

Independent review: nesquena APPROVED (end-to-end, on head). Gate-pass: Codex SAFE
(933KB legacy sidecar loads clean + round-trip verified), 17 own + 2945 regression green.

Co-authored-by: t <a@b>
Co-authored-by: nesquena-hermes <nesquena-hermes@users.noreply.github.com>
2026-07-09 19:05:54 -07:00
Frank Song
d7e7fb3013 Merge remote-tracking branch 'origin/master' into paperclip/pr-5732 2026-07-10 07:08:53 +08:00
Frank Song
67b001e57c Fix wakeup pause recovery races 2026-07-10 07:08:36 +08:00
nesquena-hermes
e0afdcb546
Release: write Office upload sidecars (#5734, #540) (#5859)
On .docx/.xlsx/.pptx upload, write a best-effort <name>.md sidecar with extracted
text (reuses api/office_documents.py — no new dep). Path-contained (O_NOFOLLOW
openat-walk within workspace), collision-safe (O_CREAT|O_EXCL never overwrites an
existing .md), best-effort (sidecar failure leaves the Office file intact, partial
cleanup on failure). Nathan concept-approved.

Gate-pass: Codex SAFE (containment + exclusivity + best-effort all verified), 27 own
+ 1443 upload/workspace regression tests green.

Co-authored-by: t <a@b>
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-07-09 15:57:21 -07:00
Frank Song
2ce9801088 Merge remote-tracking branch 'origin/master' into paperclip/pr-5732 2026-07-10 06:36:26 +08:00
nesquena-hermes
34342b9f4d
fix(security): trusted-proxy-verified local-origin gate (fixes #5764 terminal-gate bypass) (#5857)
* fix(security): trusted-proxy-verified local-origin gate (maintainer fix of #5764)

Rebuild _onboarding_request_is_local with a single, symmetric trust model that
closes the embedded-terminal local-gate bypass AND fixes the direct-LAN lockout
regression from the contributor's #5764 attempt (bounced with these findings):

1. SPOOF CLOSED: a forwarded client IP is honored ONLY when the un-spoofable raw
   socket peer is a trusted proxy (loopback, or HERMES_WEBUI_TRUSTED_PROXY_CIDRS).
   A public direct client sending X-Forwarded-For: 127.0.0.1 is judged on its raw
   public peer -> DENY. New _raw_peer_is_trusted_proxy + _forwarded_client_ip_from
   _trusted_proxy consume the FULL XFF chain (get_all, repeated headers), walk it
   right-to-left skipping trusted hops, and fail closed on empty/blank/garbage.
2. NO LAN LOCKOUT: a direct loopback/LAN client with no proxy header stays local
   (onboarding, first-password/passkey, passwordless terminal keep working).
3. COMPAT: TRUST_FORWARDED_FOR=1 stays the opt-in that consults the chain; the new
   TRUSTED_PROXY_CIDRS only WIDENS trust to a non-loopback proxy (loopback always
   trusted). Shipped private-peer-with-forwarded-header deny nuance preserved.

Updated the one existing test that encoded the vulnerable behavior; added a
21-case adversarial truth-table regression (tests/test_cvd3_terminal_local_origin_gate.py).
66 gate tests + 720 auth/terminal regression pass.

Co-authored-by: Hinotoi-agent <Hinotoi-agent@users.noreply.github.com>

* fix(security): family-aware trusted-proxy membership (IPv4-mapped-IPv6)

Codex re-gate CORE: a mapped-IPv6 address (::ffff:10.9.9.9) never matched an
IPv4 CIDR allowlist via 'addr in net' -> (1) legit proxy treated as untrusted =
lockout; (2) mapped trusted HOP in the XFF chain mis-returned as client -> a
preceding public client admitted to the terminal gate. New _ip_in_networks
helper checks addr AND addr.ipv4_mapped; both the peer check and the chain-walk
_is_trusted_hop route through it. +3 mapped-IPv6 regression cases (incl the
public-client-hidden-behind-mapped-trusted-hop security case).

---------

Co-authored-by: t <a@b>
Co-authored-by: Hinotoi-agent <Hinotoi-agent@users.noreply.github.com>
2026-07-09 15:33:16 -07:00
Frank Song
d1aab0cb5e Merge remote-tracking branch 'origin/master' into paperclip/pr-5732 2026-07-10 05:44:02 +08:00
Frank Song
4bbce7bc45 Persist restored wakeup pause after streaming cancel 2026-07-10 05:43:44 +08:00
nesquena-hermes
1a64d7d313
Release: fix workspace image previews + downloads under subpath mounts (#5830) (#5856)
_workspaceRouteForPath now resolves app-relative /api/... routes against document.baseURI
so the direct consumers (previewImg.src, media/pdf/html frame src, download href,
window.open) work under a subpath mount instead of 404ing at the server root. Root-mount
deployment unaffected (identical final URLs); api() callers not double-prefixed.

Gate-pass: Codex SAFE (no root-mount regression, all 6 consumers verified), 825 workspace
regression tests + browser-smoke green. Nathan-approved.

Co-authored-by: t <a@b>
Co-authored-by: tomtong2015 <tomtong2015@users.noreply.github.com>
2026-07-09 14:38:53 -07:00
Frank Song
d9e45f1d07 Merge remote-tracking branch 'origin/master' into paperclip/pr-5732 2026-07-10 05:27:46 +08:00
nesquena-hermes
a0067b0b24
Release: classify external_agent imports as CLI to match client render (#5846, #5831) (#5855)
Fixes the #5831 brick: "WebUI sessions (N)" chip showed a count but an empty list.
Server is_cli_session_row classified read-only external_agent imports (Claude Code,
Codex) with a real title as non-CLI (counted under webui_session_count), while the
client _isCliSession filed them in the CLI bucket -> mismatch. Server now classifies
external_agent/external-agent as CLI, matching the client. WebUI/messaging/subagent
unaffected (their exclusions precede the new branch); external_agent imports stay read-only.

Self-built (nesquena-hermes); independent nesquena review APPROVED on this head (17:28);
gate-pass (Codex SAFE, no over-capture, caller audit clean), 14 own + 180 regression green.

Co-authored-by: t <a@b>
Co-authored-by: nesquena-hermes <nesquena-hermes@users.noreply.github.com>
2026-07-09 14:20:20 -07:00
nesquena-hermes
f449ff21c6
Release: hard-prune post-compression tool context (#5667) (#5852)
WebUI hard token-cap on protected-tail tool-result payloads in the MODEL CONTEXT
only, applied once after a confirmed compression event, to prevent post-compression
context blowup. Deep-copy content-replace only (no message removal/reorder → no
orphaned tool_result); visible transcript (s.messages) untouched.

Deep-review re-gate: Codex SAFE (pairing-safety + context-only + idempotent verified
by reproduction), 65 own + 992 regression tests green. Crown-jewel streaming path.

Co-authored-by: t <a@b>
Co-authored-by: franksong2702 <franksong2702@users.noreply.github.com>
2026-07-09 13:41:31 -07:00
nesquena-hermes
46720a39dd
Release: Copy conversation link copies an openable URL (#5478) (#5851)
Was copying the internal session:// reference (not browser-openable); now copies
an absolute origin + /session/<id> URL (verified single-origin, no doubling —
_sessionUrlForSid returns a relative path, the caller prepends origin once),
tracking params stripped. Product/UX parity with major chat apps (Anchor C).

Nathan-approved. 17 copy-link regression tests green.

Co-authored-by: t <a@b>
Co-authored-by: djsavvy <djsavvy@users.noreply.github.com>
2026-07-09 13:24:45 -07:00
Frank Song
819e9d629d Merge remote-tracking branch 'origin/master' into paperclip/pr-5732 2026-07-10 04:03:57 +08:00
Frank Song
4efab593fe Preserve wakeup pause on cancelled gateway recovery 2026-07-10 04:03:40 +08:00
nesquena-hermes
8cc2d6f881
Release: model-picker Selected badge + Transparent Stream dupe-prefix suppression (#5757, #5758) (#5850)
Two visible render PRs, gate-passed (deep-review round), Nathan-approved to ship.

- #5757 (rodboev): mark active model with a Selected badge + open its provider group (#5748).
  Screenshot-verified (badge clear + correctly placed on active row).
- #5758 (rodboev): suppress the live-token prose row that is a strict prefix of the final
  answer (#5749). Provenance-gated (live-prose:* token rows after last tool row only) so it
  can't eat legitimate narration; reproduction test proves the dupe is gone.

Codex SAFE + full regression green (1576 render tests). 18 own tests pass together.

Co-authored-by: t <a@b>
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-07-09 12:51:18 -07:00
Frank Song
4af5334ebb Merge remote-tracking branch 'origin/master' into paperclip/pr-5732 2026-07-10 03:33:07 +08:00
nesquena-hermes
2b457f1646
Release: cap StreamChannel offline replay buffer (#5788) (#5849)
Bounded deque(maxlen) drop-oldest so a disconnect-without-cancel can't grow the
per-turn offline replay buffer unbounded (per-abandoned-turn OOM risk, #4633 family).
Dropped frames recovered via run-journal last_event_id replay path.

Deep-review gate: Codex SAFE (journal-recovery verified end-to-end), 20 own + 1106
regression tests green. Crown-jewel streaming path.

Co-authored-by: t <a@b>
Co-authored-by: ai-ag2026 <ai-ag2026@users.noreply.github.com>
2026-07-09 12:26:05 -07:00
Frank Song
2547ec98d1 Merge remote-tracking branch 'origin/master' into paperclip/pr-5732 2026-07-10 02:02:04 +08:00
nesquena-hermes
d6e5dc302d
Release Batch D: terminal resource-leak fix + fsync atomic writers (#5835, #5843) (#5848)
Phase 2 backend reliability/data-integrity batch. Each byte-verified vs PR head,
Codex SAFE, 20 own + 355 regression tests pass, disjoint files.

- #5835 (ai-ag2026): stop leaking fd + reader thread + shell on client/shell exit (#4633 family)
- #5843 (ai-ag2026): fsync atomic JSON writers (oauth/passkeys/discoverability) + thread-safe temp

Co-authored-by: t <a@b>
Co-authored-by: ai-ag2026 <ai-ag2026@users.noreply.github.com>
2026-07-09 10:54:06 -07:00
Frank Song
fe656a48ce Merge remote-tracking branch 'origin/master' into paperclip/pr-5732 2026-07-10 01:42:11 +08:00
nesquena-hermes
7a1a122e6e
Release Batch C: hidden-tab dashboard poll + SSE write-deadline + rate-limit map bound (#5840, #5841, #5842) (#5847)
Phase 2 low-risk backend/perf batch (ai-ag2026 reliability cluster). Each byte-verified
vs PR head, Codex SAFE, 33 own + 720 regression tests pass, disjoint routes.py regions.

- #5840: skip dashboard status poll while tab hidden (perf; forced polls unaffected)
- #5841: arm write deadline on session-events invalidation SSE (stuck-client teardown)
- #5842: bound client rate-limit maps by sweeping stale keys (long-uptime memory)

Co-authored-by: t <a@b>
Co-authored-by: ai-ag2026 <ai-ag2026@users.noreply.github.com>
2026-07-09 10:38:47 -07:00
Frank Song
a9ba8d8368 Merge remote-tracking branch 'origin/master' into paperclip/pr-5732 2026-07-10 01:07:49 +08:00
nesquena-hermes
1e275d04ed
Release: queue gateway-owned steer fallback (#5823) (#5845)
Steering a live gateway-owned run had no process-local agent in SESSION_AGENT_CACHE,
so it errored with "No agent cached" and dropped the typed text. Now detects the live
gateway ACTIVE_RUNS stream and queues the steer as the next turn (model/provider/profile
snapshotted first), updates the queue badge, clears the draft, shows a toast — without
cancelling the stream. Gateway sibling of the shipped #5597 fallback.

Codex SAFE + Fable SHIP-UX; byte-identical to PR head; 66 steer/busy-input tests pass;
Nathan visual-approved (desktop + mobile screenshots).

Co-authored-by: t <a@b>
Co-authored-by: rodboev <rodboev@users.noreply.github.com>
2026-07-09 10:04:21 -07:00
Frank Song
3a0c00c880 Merge remote-tracking branch 'origin/master' into paperclip/pr-5732 2026-07-10 00:22:56 +08:00
nesquena-hermes
91606a15e0
Release Batch B: OPTIONS preflight framing + session-index bytes-parse (#5828, #5834) (#5838)
Phase 1 low-risk backend batch (on top of Batch A). Both byte-verified vs PR head,
Codex SAFE, 64 own + 749 regression tests pass, coexists with Batch A's #5833.

- #5828 (ai-ag2026): frame CORS OPTIONS preflight with Content-Length: 0 (keep-alive hang fix)
- #5834 (ai-ag2026): parse session _index.json from bytes not decoded str (all 10 sites)

Co-authored-by: t <a@b>
Co-authored-by: ai-ag2026 <ai-ag2026@users.noreply.github.com>
2026-07-09 09:19:14 -07:00
Frank Song
61d32671ed Merge remote-tracking branch 'origin/master' into paperclip/pr-5732 2026-07-10 00:07:14 +08:00
nesquena-hermes
0d3ee7de9f
Release Batch A: worktree cli-shadow + atomic settings.json + state.db read-only (#5832, #5829, #5833) (#5837)
Phase 1 low-risk backend reliability batch. Three independent contributor fixes
on distinct files, each byte-identical to its PR head, Codex SAFE, 691+16 tests pass.

- #5832 (silent-reader-cn): importlib-load agent cli.py by absolute path so a
  shadowing cli/ namespace package can't brick worktree creation
- #5829 (ai-ag2026): atomic settings.json writes (temp+fsync+os.replace, mode-preserving)
- #5833 (ai-ag2026): open the remaining 8 state.db read projections read-only

Co-authored-by: t <a@b>
Co-authored-by: silent-reader-cn <silent-reader-cn@users.noreply.github.com>
Co-authored-by: ai-ag2026 <ai-ag2026@users.noreply.github.com>
2026-07-09 09:04:59 -07:00
Frank Song
de9d3306af Merge remote-tracking branch 'origin/master' into paperclip/pr-5732 2026-07-09 16:22:06 +08:00
nesquena-hermes
819949fd8c
Merge pull request #5825 from nesquena/release/stage-5789
Release: gateway SSE terminal idle-timeout, honor Stop (#5789)
2026-07-09 01:10:12 -07:00