* fix(config): semantic fingerprint for the Codex catalog dependency (#7540)
* fix(#7556): guard the recursive codex-cache strip against RecursionError
Codex found a valid-but-deeply-nested models_cache.json crashes
_codex_models_cache_fingerprint with RecursionError (the recursive strip ran
outside the fallback try), 500ing /api/models. Move _strip_volatile_codex_cache_fields
inside the existing encode try/except so a transform failure degrades to the
stat-based fingerprint. +1 mutation-proven regression (forces the strip to raise;
fails with RecursionError if the guard is reverted).
* docs(changelog): stamp #7556 (semantic fingerprint for Codex catalog dependency)
---------
Co-authored-by: webtecnica <webtecnica@gmail.com>
Co-authored-by: nesquena-hermes <nesquena-hermes@users.noreply.github.com>
Co-authored-by: n <a@n>
Follow-up to #7551: the merged test file carried an unused `route_approvals as ra`
import and two unused `entry =` assignments (advisory lint gate was red, though not
a required check). Drop them; the two `entry` bindings that are asserted on are kept.
Co-authored-by: nesquena-hermes <nesquena-hermes@users.noreply.github.com>
* fix(approvals): mint actionable ids for raw id-less local pending entries
A raw id-less approval entry in _pending (agent-side _pending_result drops
{command, pattern_key, pattern_keys, description} verbatim when no gateway
notifier is registered for the session) is unactionable by contract: the
frontend owner-capture requires an approval_id, so the approval card can
neither be approved nor dismissed - the 1.5s poll re-serves it forever.
reconcile_gateway_pending_mirror_locked already mints gwlocal:<token> ids
for id-less _gateway_queues producers; extend the same contract to raw
non-mirror _pending entries with a stable gwlocal-mirrorless:<uuid> id
minted once onto the stored entry dict, so the id persists across polls
and frontend dismiss markers stick.
The exact-id respond path needs no further change: once the entry carries
an id, _resolve_approval_legacy pops it (also clearing the stale
stream-pointer 409 guard), stale explicit ids still fail closed (#527),
and the yolo Skip-all release drains it.
* docs(changelog): stamp #7510 (actionable ids for raw id-less pending approvals)
---------
Co-authored-by: CharlesMcquade <6466275+CharlesMcquade@users.noreply.github.com>
Co-authored-by: n <a@n>
Co-authored-by: nesquena-hermes <nesquena-hermes@users.noreply.github.com>
* fix(api): scan to first complete exchange for title regeneration (#7543)
_first_exchange_snippets() aborted at a second consecutive user message
before collecting assistant text, so transcripts opening with queued user
rows (channel-backed imports) hit the missing_exchange rejection and the
deterministic fallback got persisted with HTTP 200 — invisible in the UI
and unrecoverable by retrying.
Keep scanning until the first complete user+assistant pair instead, and
fall back to the last complete exchange when the opening user message
yields no user text (privacy scrubbing) instead of failing with
empty_user_message.
Tests: tests/test_issue7543_title_first_exchange.py (8 cases, incl. the
issue-pinned transcript shape and walker-mocked discriminator tests for
both fixes).
* test(api): drop local-machine notes and sys.path hack from 7543 test module
- Docstring described the change as a local-only uncommitted hotfix with
machine-specific paths — wrong for an upstream PR and unsafe to publish.
- Drop the defensive REPO_ROOT sys.path insert; the repo conftest already
puts the package root on sys.path (matches the neighboring test modules).
* fix(api): drop unreachable latest-exchange fallback from title regeneration
Greptile flagged the mocked-walker fallback test (P2) as fabricating
internal state, and an exhaustive enumeration of realistic transcript
shapes (5460 combinations, lengths 1-6) confirms it: both walkers share
the same text extraction and the forward walker scans every message, so
'first walker empty while latest walker yields a pair' is unreachable
from real transcripts. The fallback branch was dead code.
Remove it together with its two walker-mocked tests; the fixed first-
exchange walker alone resolves the reported issue shape. Replace the
mocked tests with an observable E2E error-path test through the real
walkers, and make the aux mock guard-faithful (empty assistant_text ->
missing_exchange per the generate_title_raw_via_aux contract) so the
fail-before proof actually exercises the endpoint contract.
* fix(#7543): scope first-exchange scan-past to the manual-regen path only
The full suite caught that broadening the SHARED _first_exchange_snippets also
activated the automatic in-stream background-title path for consecutive-user-row
transcripts, moving stream teardown from the synchronous stream_end branch to the
background-title-thread branch (test_issue3929 emits_done regressed: stream_end
was no longer in the synchronous queue). #7543 is specifically about the MANUAL
'Regenerate title' endpoint, so gate the scan-past behavior behind an opt-in
scan_past_consecutive_users flag (default False = master behavior) and pass True
only from generate_session_title_for_session. Auto in-stream path is now
byte-identical to master; added a default-behavior regression guard test.
* docs(changelog): stamp #7545 (manual title regen for queued-user transcripts)
---------
Co-authored-by: Raylands <Raylands@users.noreply.github.com>
Co-authored-by: nesquena-hermes <nesquena-hermes@users.noreply.github.com>
Co-authored-by: n <a@n>
* fix(cron): restore messaging platforms in the delivery picker
The Agent moved _KNOWN_DELIVERY_PLATFORMS from cron.scheduler to
cron.scheduler_delivery. _handle_cron_delivery_options imported the old
path inside a bare except and fell back to an empty frozenset, so the
import error was swallowed and GET /api/crons/delivery-options silently
returned only local and origin — telegram, discord, slack, feishu and
every other messaging platform disappeared from the cron delivery picker
with no error surfaced anywhere.
Resolve the authority across both module paths (current first, legacy
fallback) so the picker keeps working across Agent versions, and add a
regression test that pins the resolution order and cross-checks the
endpoint output against the resolved authority.
* test(cron): skip the authority-resolution test where the Agent is absent
CI runs agent-free (tests/browser_smoke.py), so neither cron.scheduler_delivery
nor cron.scheduler is importable there and the authority resolves empty. The
new regression test asserted non-empty and therefore failed shard 3 on both
Python versions. Skip instead: with no cron package there is no authority to
resolve and nothing meaningful to assert. The test still fails loudly wherever
the Agent IS installed, which is where a future relocation would bite.
* test(cron): register the new test in _AGENT_DEPENDENT_TESTS
Use the project's existing skip mechanism (tests/conftest.py) rather than an
ad-hoc inline pytest.skip, matching the five sibling delivery-options tests.
CI runs agent-free, so there is no cron package and no authority to resolve
there; with the Agent installed the test still asserts loudly, which is the
environment where a future relocation would actually bite.
Verified both directions: 6/6 pass with the Agent present, and forcing
AGENT_MODULES_AVAILABLE=False skips all 6 (was 5) as CI will.
---------
Co-authored-by: nesquena-hermes <nesquena-hermes@users.noreply.github.com>
* rebase #7351 onto master (bind turn workspace as agent session cwd)
Co-authored-by: samfoy <samfoy@users.noreply.github.com>
* fix test: use deployed agent.runtime_cwd API (_SESSION_CWD.get) not nonexistent _session_cwd_override
The two failing assertions referenced agent.runtime_cwd._session_cwd_override(), a
helper not present in the installed Agent (which exposes _SESSION_CWD, resolve_agent_cwd,
set/clear_session_cwd). The production binder is correct and verified: binding workspace=/tmp
sets _SESSION_CWD.get()=='/tmp' and resolve_agent_cwd()=='/tmp', reset restores _UNSET.
Line 343 was redundant with the surrounding assertions; line 386 now reads _SESSION_CWD.get().
Co-authored-by: samfoy <samfoy@users.noreply.github.com>
* docs(changelog): stamp #7351 turn workspace cwd binding
---------
Co-authored-by: n <a@n>
Co-authored-by: samfoy <samfoy@users.noreply.github.com>
* rebase #7160 onto master (report unverified Agent updates, no auto-restart)
Co-authored-by: hrdwdmrbl <hrdwdmrbl@users.noreply.github.com>
* harden agent-update marker read: no-follow, fstat regular-file, bounded (fixes gate CORE)
The Agent update marker is attacker-adjacent shared state. _read_live_agent_update()
used Path.read_text(), which follows symlinks, blocks forever on a FIFO, and reads an
unbounded regular file — a rejected/stale-runtime request could hang or OOM. Now opens
O_RDONLY|O_NONBLOCK|O_NOFOLLOW, fstat-verifies a small regular file, and reads a bounded
max, classifying anything else as 'unknown' (fail-closed). Adds FIFO/oversized/symlink/
happy-path regression tests.
Co-authored-by: hrdwdmrbl <hrdwdmrbl@users.noreply.github.com>
* fix marker-read portability: gate os.open fast-path on O_NONBLOCK+O_NOFOLLOW
os.O_NONBLOCK is Unix-only; accessing it unconditionally raised AttributeError
on native Windows, breaking every stale-revision barrier (chat/compression/etc).
Now both flags resolve via getattr and the os.open() fast path is taken only
when BOTH are available (_MARKER_SAFE_OPEN_AVAILABLE); otherwise fall back to an
lstat-only classification (absent vs unknown) so a fallback platform can never
symlink-traverse or crash. Adds a Windows-fallback regression test.
Co-authored-by: hrdwdmrbl <hrdwdmrbl@users.noreply.github.com>
* docs(changelog): stamp #7160 stale-runtime report + hardened marker read
---------
Co-authored-by: n <a@n>
Co-authored-by: hrdwdmrbl <hrdwdmrbl@users.noreply.github.com>
* fix(streaming): lock-disciplined STREAMS reads via peek_stream()
Writers pop STREAMS under STREAMS_LOCK, but four hot reads used bare
STREAMS.get(): the streaming worker start, the gateway run start, and the two
SSE attach paths in routes.py. Each is individually saved today by GIL-atomic
dict.get plus a None-guard, but the check-then-act windows (get → register,
get → subscribe) race teardown pops, and the bare pattern invites unprotected
copies. peek_stream() centralizes the locked read; all four sites migrate with
no behavioral change.
* test: dual-patch config.STREAMS alongside routes.STREAMS in SSE seam tests
peek_stream() reads the canonical api.config.STREAMS registry; tests that
inject a fake registry by patching only the routes-level re-export left the
helper reading the real (empty) registry and hung the SSE loop to the
pytest-timeout ceiling. Mirror the test_issue6623 pattern: patch both
bindings.
* docs(changelog): stamp #7339 lock-disciplined STREAMS reads
---------
Co-authored-by: Sebastian B <sb@wmc.sh>
Co-authored-by: n <a@n>