Skip to content

Stage 387 / v0.51.94 — Release BR — 10-PR full sweep batch#2606

Merged
nesquena-hermes merged 24 commits into
masterfrom
stage-387
May 19, 2026
Merged

Stage 387 / v0.51.94 — Release BR — 10-PR full sweep batch#2606
nesquena-hermes merged 24 commits into
masterfrom
stage-387

Conversation

@nesquena-hermes
Copy link
Copy Markdown
Collaborator

Stage 387 / v0.51.94 — Release BR — 10-PR full sweep batch

Ten contributor PRs across streaming/state.db reconciliation followups, runtime-adapter Slice 4b, folder-zip download, partial-marker dedup, browser timeouts, composer-draft and auto-compression polish, approval-queue persistence, and indexed-context metadata.

PRs in this batch

Fixed

Added

Verification

Pre-Opus 7-point gate — green

  • JS syntax (panels/sessions/messages/ui/i18n/boot/workspace) — clean
  • Python ast.parse over all api/*.py + tests/*.py — clean
  • Merge marker scan — none
  • CHANGELOG TBD placeholders — none
  • No CJK escape literals
  • No Docker surface touched
  • No new module with concurrent primitives

Tests

  • Pytest: 6051 passed, 6 skipped, 3 xpassed, 8 subtests passed in 139.11s+33 tests vs v0.51.93
  • Browser API sanity (port 8789): 11/11 PASS

Opus advisor — GO (no inline followups)

Verified each of five self-verify concerns with file:line citations:

  1. fix: reconcile session metadata counts #2604 × fix(webui): refresh active session on external sidecar updates #2605 merge composition_summary_message_count = len(_all_msgs) falls back to _metadata_message_count only when merged count is 0 (legacy session). Both tests pass against the merged logic. ✅
  2. fix: dedupe tool-only partial recovery markers #2593 dedup scope — Walks backward from _cancel_marker_idx to most recent role=='user', scans only messages[start:end]. Multi-turn duplicate retry pattern stays safe (turn boundaries anchor the search). ✅
  3. fix(streaming): finish auto-compression card after rotation #2603 compressed listener guard relaxationeventMatchesCurrent requires currentSid to match one of eventSid/new_session_id/continuation_session_id. A→B switch scenario: B doesn't match any of A's identifiers → event correctly rejected. ✅
  4. feat(runtime): add runner adapter facade #2599 RunnerRuntimeAdapter facade — Zero callers in production paths (grep -rn RunnerRuntimeAdapter api/ returns only the definition). Facade-only as advertised. ✅
  5. feat(workspace): download folder as zip via /api/folder/download #2566 folder-zip CSRF/IDOR — Auth-gated via check_auth before handle_get. CSRF intentionally GET-exempt (safe-method semantics, same as /api/file/raw). No Access-Control-Allow-Origin, so cross-origin JS can't read response. Content-Disposition: attachment blocks inline. safe_resolve + per-file symlink resolution check. Exposure shape byte-for-byte identical to existing /api/file/raw baseline. ✅

Single follow-up to track (NOT a blocker for v0.51.94): bjb2's promised PR to add defense-in-depth (cookie-bound token or non-cookie auth) for both /api/file/raw and /api/folder/download.

Triage actions taken this sweep (not in the batch)

Triage carryover unchanged

Notes

bjb2 and others added 24 commits May 18, 2026 21:40
Adds a "Download Folder" item to the workspace file-tree right-click
menu and a GET /api/folder/download endpoint that streams the
directory as a zip with Content-Disposition: attachment.

Configurable caps:
  HERMES_WEBUI_FOLDER_ZIP_MAX_MB    (default 1024)
  HERMES_WEBUI_FOLDER_ZIP_MAX_FILES (default 50000)

Pre-flights the walk so cap-exceeded returns 413 + JSON BEFORE any
zip bytes are sent. Symlinks resolving outside the workspace are
skipped. Mirrors the existing _handle_file_raw shape (session_id
resolution, safe_resolve, RFC 5987 filename via
_content_disposition_value). Stdlib zipfile only; no new dependencies.

Tests: 11 static-inspection tests matching the style of
tests/test_issue1867_upload_size_preflight.py. All passing on
Python 3.11/3.12/3.13.
CI parity tests enforce that every key in the English locale block exists
in zh, ja, ko, ru, and es. The PR introducing download_folder added it to
en only, which broke the 5 hard-parity test files. Adds the English
fallback to all 10 non-en blocks (it/ja/ru/es/de/zh/zh-Hant/pt/ko/fr) with
the project's // TODO: translate marker so translators can refine later.

Tests: tests/test_chinese_locale.py, test_japanese_locale.py,
test_korean_locale.py, test_russian_locale.py, test_spanish_locale.py —
26/26 passing locally.
Per reviewer note: because the zip streams straight into handler.wfile
(no io.BytesIO buffering), peak memory is bounded by zipfile's per-file
read buffer, not the HERMES_WEBUI_FOLDER_ZIP_MAX_MB cap. Adds a comment
so the next reader doesn't have to trace it to learn the cap's actual
shape.
…pending is empty

During active streaming, dangerous-command approvals go through the
gateway path and are stored in _gateway_queues as _ApprovalEntry
objects, not in _pending. The _resolve_approval_legacy helper only
looked at _pending, so 'Allow for this session' never called
approve_session() — the user clicked Allow, the card vanished, but
the next dangerous command asked again.

Now when _pending has no matching entry, the helper peeks into
_gateway_queues to extract pattern_keys, calls approve_session(),
and marks found_target=True so resolve_gateway_approval also fires.

This commit is re-scoped to peek-only (no agent_session_key round-trip,
no state_db metadata changes).

Includes:
- Import + fallback for _gateway_queues
- Null-safe key filtering in all_keys
- Source-contract test (static) + functional test with
  @requires_agent_modules skip marker for CI
- All comments and docstrings in English
Drop the redundant 'if gw_data else []' guard — gw_data is already
guaranteed to be a dict by the 'or {}' fallback above.

Add a one-line comment explaining the peek-without-pop race window:
a concurrent resolver may pop a different gateway entry, but
approve_session is idempotent over the session key set so the
outcome is the same regardless.
# Conflicts:
#	CHANGELOG.md
# Conflicts:
#	CHANGELOG.md
# Conflicts:
#	api/routes.py
@nesquena-hermes nesquena-hermes merged commit c8896ac into master May 19, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

5 participants