handle_upload deduped filenames with an exists() check and then wrote with
plain write_bytes — two concurrent uploads of the same name both passed the
check and the last writer silently won. Mirror the #3398 workspace-upload
hardening: O_CREAT|O_EXCL|O_NOFOLLOW anchored open from the session
attachment dir; a raced duplicate now 409s instead of overwriting and a
raced symlink cannot redirect the write.
* refactor(routes): extract the 585-line GET /api/session handler to _handle_session_get
Pure move out of handle_get's if/elif chain into a named module-level handler, matching the pattern the other ~200 routes already use. Body is verbatim (one-level dedent only); AST identity of the moved statements verified; no behavior change — the GET /api/session test slice passes identically before and after.
* test: repoint four source-window tests to _handle_session_get
The moved block's source-window extractors (chat_start synthesiser/read-only
pins, regressions runtime-streaming-state window, issue1436 load-path
markers, security-redaction inspect) now anchor on the extracted handler;
assertions unchanged.
* test: repoint messaging-session loader pin to _handle_session_get
---------
Co-authored-by: nesquena-hermes <nesquena+hermes@gmail.com>
The new normalize test now stubs agent.anthropic_adapter in sys.modules
(CI has no agent package — real import raised ModuleNotFoundError), and the
issue-1013 codex fake ports to the v0.21 _get_transport contract instead of
the deleted _normalize_codex_response method.
hermes-agent v0.21 removed agent.anthropic_adapter.normalize_anthropic_response and the AIAgent._normalize_codex_response method; the sanctioned out-of-band contract is now agent._get_transport(<mode>).normalize_response(...) returning NormalizedResponse (.content). Port the anthropic_messages and codex_responses branches of generate_title_raw_via_agent (api/streaming.py) and _agent_text_completion (api/routes.py); on agent v0.21 those branches currently die on import/attribute errors and silently fall back, degrading title generation and handoff summaries. build_anthropic_kwargs, _anthropic_messages_create, and _run_codex_stream are unchanged in v0.21 and stay as-is.
Review ask on #7335: the +232-line lockfile regeneration was a local uv
side effect swept in by a broad 'git add -A' — this is a JS-only escaping
fix and the lockfile stub is intentional on master.
read_body() silently turned malformed JSON into {} (clients then got a
misleading 'Missing field' 400) and passed non-dict JSON through to handlers
where body.get() raised AttributeError and surfaced as a generic 500.
Raise ValueError instead — handle_post already maps it to a clean 400 — and
give the PATCH/DELETE/PUT dispatchers the same mapping they lacked. Empty
bodies keep the documented {} default.
esc() is HTML-only escaping; the browser decodes attribute entities BEFORE
parsing an inline handler, so onclick="fn('${esc(id)}')" let any id
containing a quote break out of the JS string literal. _kanbanJsArg solved
this for kanban dependencies only; promote it to jsArg() in ui.js and route
every inline-handler argument through it (panels.js cron/kanban/notes/
passkeys/gateway/checkpoints, sessions.js handoff hint, onboarding.js OAuth
device code). Adds a Playwright round-trip proof and a source guard against
reintroducing the esc()-only pattern.