Re-lands the reverted #121507 on the current custody contracts. The reply dispatcher now owns direct-send pending-final custody: claim before provider I/O, terminal settlement for delivered/suppressed/failed outcomes, proven no-send stays replayable, ambiguous evidence fails closed — so Gateway restarts can no longer duplicate an already-accepted final reply.
Proof: ClawSweeper local review clean, exact-head ci-gate green, live Telegram E2E (one turn, one final, no duplicates).
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* fix(codex): report harness context window as session contextTokens
Codex app-server reports model_context_window per turn. Carry it through the projector into the run result meta so session rows show the real window instead of the catalog's standard-tier input cap (272k vs 1M for gpt-5.6 models).
* improve(ui): compact chat context popover
Inline stat rows replace boxed tiles; zero-value cost rows and the whole cost section when empty are omitted; provider/model provenance lines are removed because the footer already shows the model; and the popover is narrowed to 300px.
* refactor(codex): split attempt-result assembly out of event projector
* fix(codex): seed attempt context window from startup binding
App-server v2 turn/started omits the core model_context_window, so thread/tokenUsage/updated is the only live carrier. Seed usage-less attempts from the retained startup binding rollout/session window so session metadata cannot regress to the catalog fallback.
* fix(codex): prefer native startup context window
Persisted session contextTokens has no source provenance and may contain the catalog fallback. Keep the minimum window for the conservative rotation fuse, but seed the projector from the native rollout when it is available.
* chore(plugin-sdk): regenerate api baseline (new format)
* revert(gateway): "prevent restart replay after final delivery" (broke 5 CI jobs)
Record channel delivery custody before recipient-visible sends so restart recovery cannot duplicate accepted finals.
Ambiguous legacy markers now fail closed with a visible interruption instead of blind replay.
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* perf(plugins): close the last kysely closure chains and guard reachability transitively
Follow-up to #120698/#120811/#120882: the closure guard's enumerated barrel
bans cannot catch new heavy edges, and two closures still statically reached
kysely on main.
- guard: add a transitive kysely-reachability test that walks static value
imports from every doctor-contract and legacy-setup closure through plugin,
plugin-sdk, and relative core graphs, failing with the full import chain;
type-only and lazy dynamic imports stay allowed
- llm-task/model refs: manifest-model-id-normalization reads snapshots
through a registration-slot runtime bridge (snapshot modules register at
eval; require fallback covers cold processes) and
current-plugin-metadata-state moves its process-scoped facts onto a
globalThis singleton so dual module instances share published state
- telegram: split thread-bindings-store.ts (pure record shapes + legacy-file
readers) out of the acp-runtime-heavy manager, delete the consumer-less
testing export, move the pure bot-user-id token parse to
token-fingerprint.ts, and lazy-import token.js in the async update-offset
detector
llm-task enumeration drops to ~0.8s/157 modules cold; every closure is now
statically kysely-free and stays that way by construction.
* fix(telegram): repoint the native-command menu state at the token-fingerprint parser