* fix(codex): bound no-engine continuity projections to half the context window
A degraded or absent context engine sends fresh-thread continuity through
projectContextEngineAssemblyForCodex with the whole-window projection cap
((window - 20k) x 4 chars), so a large uncompacted transcript renders into
a single turn/start input consuming up to 90% of the model context window.
That turn fills its own native thread, the next turn's token fuse rotates
it, and the following fresh thread re-projects the transcript again -
observed as 11 near-window turn inputs on cold threads in one day
(openclaw/openclaw#125254).
Continuity projections now use a dedicated cap that reserves half the
context token budget, so the fresh thread keeps headroom for later turns
and the existing delta-resume path can actually engage. The active-engine
projection path keeps its whole-window cap unchanged.
Related: #125254
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JKtoZgXWnaAH8rmLiSydpN
* fix(codex): size continuity projections from real token cost, not the optimistic estimate
The continuity cap reserved half the context window in tokens but converted
that budget to characters with APPROX_RENDERED_CHARS_PER_TOKEN = 4, so at a
258,400-token window it permitted 516,800 chars. A live projection measured
703,134 chars for 226,146 input tokens, meaning that cap really costs about
166k tokens (~64% of the window), not the intended 129.2k.
Codex reports input tokens only after a turn and bounds turn input by
characters, so the projection cannot be sized in verified tokens before it is
sent. Convert the continuity budget at a conservative 3 chars/token instead,
which holds the reserved half in real tokens at the densest ratio observed.
Related: #125254
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JKtoZgXWnaAH8rmLiSydpN
* fix(codex): scope the continuity sizing claim to the density it was measured at
The half-window claim was stated as a guarantee, but the 3 chars/token
conversion rests on one observed projection. Input that tokenizes more densely
(CJK, base64, minified code) still exceeds the reserved half, so the constant is
renamed to CONTINUITY_EMPIRICAL_CHARS_PER_TOKEN and its comment says plainly
that it is an empirical floor rather than a bound.
The invariant test is narrowed to the measured density, and a companion test
pins the break-even ratio at 3 chars/token so the limitation is visible in the
suite instead of implied. Choosing between a guaranteed worst-case bound and
this empirical cap is a maintainer-owned tradeoff, left open on the PR.
Related: #125254
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JKtoZgXWnaAH8rmLiSydpN
* feat(codex): size continuity projections from the session's observed token density
Each completed Codex turn now records a calibration sample on the thread
binding: prompt chars actually sent vs the provider-reported input token cost
(uncached + cache read + cache write). The no-engine continuity cap converts
its half-window token budget at that observed ratio instead of a fixed
chars-per-token guess, so capChars / ratio stays at the reserved budget for
any content density - CJK, base64, and minified code included. The sample is
captured before startup rotation so a rotated-away thread's density still
sizes the fresh thread's projection; without a sample the empirical 3
chars/token default applies, and degenerate samples clamp to [0.5, 4].
Related: #125254
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JKtoZgXWnaAH8rmLiSydpN
* fix(codex): make continuity calibration monotone - samples only tighten the cap
Red-team finding: a loose sample (up to 4 chars/token) followed by denser
content could size the cap past the empirical default, and stale or
non-continuity samples persist on the binding. Clamping the calibrated ratio
at the empirical default makes every such failure mode degrade to the
uncalibrated behavior instead of past it, and the invariant test asserts
monotonicity across poisoned samples directly.
Related: #125254
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JKtoZgXWnaAH8rmLiSydpN
* fix(codex): record continuity calibration only from continuity projections
ClawSweeper P2: calibration ran after every successful turn filtered only by
prompt size, so a dense direct or active-engine prompt could persist a sample
whose density later shrinks continuity history it never measured. The
no-engine continuity appliers now mark the prompt state, finalize gates the
sample on that marker, and a cross-mode regression proves a large direct
prompt records nothing.
Related: #125254
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JKtoZgXWnaAH8rmLiSydpN
---------
Co-authored-by: Marvinthebored <262704729+Marvinthebored@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* feat(agents): unify agent status into a durable progress_card
Replace the write-only update_plan to-do tool and the fragmented plan
rendering with one durable status artifact per session: progress_card
({plan?, markdown?}, replace-on-write, 8 KiB markdown / 50-step caps).
Cards persist in a lazy-additive session_progress_cards table in the
per-agent DB (no schema-version bump), broadcast progressCard.changed,
and render from the store with exactly one live placement per view
(session rail when visible, else the composer-adjacent bar); transcripts
collapse to one-line receipts, and the sidebar hovercard shows other
sessions' cards inline (markdown + <progress>, DOMPurify allowlist, no
iframes). The three stream-derived plan renderers and their dedup
heuristics are deleted.
Codex runs disable the native plan tool per thread
(tools.update_plan.enabled=false) and receive progress_card via the
dynamic-tool bridge; compaction restore now reinjects the card (steps +
bounded markdown). Card writes still emit the legacy plan stream event so
native apps and channels keep working until their per-platform
migrations. Policy names map update_plan -> progress_card; the shipped
tools.updatePlan=false kill switch is honored.
Net -277 production LOC; -480 test LOC.
* test(agents): regenerate Codex prompt snapshots for update_plan thread-config disable
* chore(protocol): allowlist progressCard.changed for native apps pending card migration
* fix(ci): repair progress card integration checks
* fix(codex): canonicalize native progress cards
* test(gateway): reconcile progress card method order
* test(codex): stabilize native approval fixture
Show an explicit waiting acknowledgment when sessions_yield ends an otherwise-silent interactive turn, while keeping private resume context out of channel delivery and preserving existing visible replies.
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* fix(agents): report interrupted tool outcomes honestly
Prevent restart recovery from claiming that an interrupted tool call completed when no successful matching result was recorded.
* fix(agents): keep ambiguous recovery restart-safe
Classify failed replay-unsafe tool results at the shared restart-recovery owner so interrupted side effects remain unavailable until their external state is verified.
* fix(agents): distinguish missing tool outcomes
Carry the existing missing_tool_result fact into projected Codex transcripts so restart recovery restricts only genuinely unknown outcomes, while confirmed failures remain retryable.
* refactor(gateway,ui): one bounded display projection; delete marker strip sites
Persisted transcripts are marker-free since the write-boundary projection
(#124793), the historical migration (#124888), and TTS facts (#124913), so
display surfaces stop compensating. sessions.list.lastMessagePreview and its
siblings (sessions.preview/describe, TUI picker, sessions_list tool, MCP) now
share one bounded role-aware projection (240 chars, tool/system/thinking and
suppressed control replies excluded, directive-only rows fall through). The
web reply chip reads the typed openclawDelivery fact instead of parsing text;
chat.history preserves the field to the UI. Post-hoc display strips are
deleted across web/TUI/MCP/sessions-list; live streaming cleaners stay.
Stale gateway-protocol preview comments corrected; no schema change.
Assertion-safety baseline pruned for shrunk files (sanctioned direction).
Production net -173, tests net -137. Fixes the sidebar [[reply_to_current]]
preview leak and the empty-code-pill overstrip of quoted markers.
* fix(agents): preserve restart recovery transcript reads
* refactor(gateway): remove obsolete transcript exports
* fix(gateway): normalize injected delivery directives
* fix(ci): scope projection and recovery checks
* chore(ci): shrink plugin SDK surface budgets
* test: deflake loaded side question and worker checks
* test: align display projection CI fixtures
* style: format display projection fixture
* fix: distinguish image inspection from delivery
Clarify that image-reading tools only load private model context, prefer native Codex image inspection when available, and preserve explicit outbound tool media across Claude CLI loopback runs.
Co-authored-by: pash <pash@openai.com>
* fix: propagate CLI vision capability
* chore: refresh CI after main fix
---------
Co-authored-by: pash <pash@openai.com>
* feat(ui): surface approvals passively and redesign the approval card
Approvals no longer auto-open the centered modal: the queue is reachable
only through the sidebar attention chip, while the owning session shows
the inline card and other sessions surface via the session-row shield
icon, agent badges, and the chip. Deletes the inline-vs-modal exclusion
machinery (modalApprovalQueue, inlineApprovalId, forceShowAll) and makes
modal dismissal close the view instead of denying the active request.
Card: severity now drives the accent color instead of a table row,
plugin/agent render as header chips, the session key moves behind a
collapsed Details disclosure (modal only), and low-value exec rows
(Resolved/Security/Ask) collapse into the same disclosure. The codex
app-server bridge stops duplicating the session key into description
text; the envelope already carries it.
* fix(ui): gate settings Escape on the approval dialog's recorded open state
ClawSweeper caught that shouldIgnoreSettingsEscape still inferred an open
approval dialog from queue non-emptiness; with passive approvals a pending
queue no longer implies a visible dialog, so settings would swallow Escape.
The exec-approval element now records dialogOpen as a fact and the guard
reads it.
* fix(agents): allow required-preflight native Codex compaction
Required reply-preflight compaction on a Codex app-server-backed session
returns the intentional `ok: true, compacted: false` "codex app-server owns
automatic compaction" no-op because the preflight caller never passes
`allowNonManualNativeRequest`. The reply/preflight path then misclassifies
that successful skip as a failure and throws, dropping the user's turn with
"Context is too large and auto-compaction could not recover this turn." The
equivalent CLI path was fixed by #88207; this is the second, unpatched caller.
Route required-preflight through the existing private
`compactAfterContextEngine` harness capability (which already passes
`allowNonManualNativeRequest: true`) by adding a typed
`nativeCompactionRequest: "required_preflight" | "after_context_engine"`
origin on `maybeCompactAgentHarnessSession` and the Codex compact bridge.
The non-manual skip guard is bypassed for preflight, so Codex actually
compacts the thread.
A binding change between the initial read and the native request is a
stale-binding race, not a benign skip. For `required_preflight` (and the
non-manual CLI path) it now surfaces as the canonical recoverable
`stale_thread_binding` failure so the queued harness falls back to the
context engine instead of treating an uncompacted `ok: true` result as a
completed turn. A genuine post-context-engine request may still skip,
because the context engine has already compacted. Required-preflight is also
the one scoped exception to the model-locked terminal rule: missing or stale
Codex thread bindings recover via the shared context-engine fallback while
the persisted harness lock stays intact; other locked failures remain
terminal.
Rebased onto main after #120740 restructured the guarded native compaction
block; the recoverable-binding semantics are reintroduced on the new
structure and scoped by `nativeCompactionRequest` so #120740's
post-context-engine skip behavior is preserved.
Closes#119971.
* test(evidence): commit inspectable required-preflight live proof scripts for #119971
Adds the two live codex app-server proof scripts (binding-race +
locked-preflight) so the redacted terminal traces in the PR body are
inspectable on the exact head. Both drive the real codex binary and real
maybeCompactCodexAppServerSession with nativeCompactionRequest:
"required_preflight"; neither runs in CI (no codex binary).
* fix(agents): scope locked-preflight compaction fallback to Codex
Restrict the required-preflight model-lock exception to the Codex harness
so missing/stale thread bindings in other locked native harnesses (e.g.
Copilot) stay terminal instead of escaping the persisted model-lock
boundary via context-engine fallback. Add a model-locked Copilot
required-preflight regression covering both missing and stale thread
bindings.
* fix(codex): require native preflight compaction
* chore(plugin-sdk): account for native compaction exports
* test(codex): use complete cron authority fixtures
* chore(lint): shrink compaction assertion baseline
* fix(lint): honor root boundary timeout
* fix(lint): extend package boundary timeout
* fix(plugins): verify native compaction owner
---------
Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com>
* fix(ui): surface hidden-pane steer terminal failures globally
Three terminal branches in steer-lifecycle.ts (transport null result,
failed queue-row restore, failed queue-row removal) still gated their
error on itemStillVisible, so a steer that failed after the operator
navigated away parked the error on the queue row with no visible
outcome — the exact invariant #124473 introduced
surfaceChatDeliveryFailure() to protect.
Route all three through the canonical helper and delete the divergent
visibility-only branches. Regression test fails pre-fix
(stash-verified): steer transport failure with the pane hidden now
surfaces the session-named global toast.
* fix(logging): demote per-turn gateway log noise to debug
Live campaign evidence showed three lines dominating operator logs at
info level with no per-turn diagnostic value:
- 'tool policy removed N tool(s)': the policy pipeline runs on every
turn, so this repeated 42x in one session. Demote to debug and delete
the now-dead toolPolicyAuditLogLevel/auditLogLevel plumbing that only
existed to lower diagnostic probes to the level that is now the
default (net -13 production LOC).
- 'codex app-server one-shot cleanup checked shared client retirement':
routine per-attempt teardown detail; demote to debug.
- 'codex trajectory capture requires the SQLite host recorder': static
config condition warned per attempt; warn once per process.
Skipped: the [model-fetch] info carve-out in model-transport-debug.ts is
a named contract (docs/logging.md, #89648) — always-info by design.
* fix(codex): drop test-only trajectory warn-once reset export
Knip's production unused-export gate rejects
resetCodexTrajectoryRecorderWarningForTest — it was a test-only seam in
production code. Reset the process-wide warn-once flag via
vi.resetModules() + fresh dynamic import in the test instead.
* test(cron): wait for backoff re-arm instead of fixed sleep
The 0ms retry timer arms only after async watcher-state persistence, so
'await delay(5)' races it on loaded CI workers (flaked on
checks-node-compact-large-2: spawn called 1 time, expected 2). Replace
both fixed-sleep re-arm waits with vi.waitFor on the spawn count. The
remaining delay(5) guards a negative no-further-spawn assertion after
cancel, where a bounded sleep is the correct shape.
* fix(codex): scope trajectory recorder warn dedupe to session
ClawSweeper P2: the host recorder factory returns null for per-session
target-mapping conflicts, not only static config, so a process-wide
warn-once flag silenced a later distinct session's recorder loss. Warn
once per session (bounded set, cleared past 64 entries) so retries stay
quiet but each newly affected session records its loss. Regression
covers a later distinct session still warning.
* test(codex): split the session-catalog suite along module seams
Replace the 4.8k-line grandfathered session-catalog.test.ts with seven
seam-anchored suites plus shared fixtures, and remove its max-lines
suppression and baseline entry. The broad openclaw/plugin-sdk/node-host
vi.mock becomes a narrow session-catalog-pty.runtime.ts boundary so the
non-isolated extension-codex worker no longer re-instantiates the
plugin-sdk graph once per split file (the crash PR #124178 measured).
Test bodies are verbatim; AST parity across the split checked out
96/96 identical.
AI-assisted (Codex worker under maintainer review).
* test(codex): carry caller origin in the cron-authority capability fixture
PR #118579 widened the in-process CronCreatorAuthorityCapability run
scope with a contractually required callerOrigin, and the new transcript
tool wiring reads it during turn startup. The hand-built fixture in
run-attempt.configured-mcp.test.ts predated the field, so
bindActiveOperatorTurnAuthority threw and runCodexAppServerAttempt
rejected while five tests awaited turn/start - 120s timeouts, then a
worker teardown crash. #118579's CI never ran the extension-codex lane
(cross-lane classification gap), so main's codex lane was latently red.
Bisected to 8668aeb9698; fixture now mints the local-operator origin the
helper's name promises.
AI-assisted (maintainer-diagnosed, Codex-era fixture repair).
* test(codex): keep catalog fixture internals private
* refactor(session-catalog): unify adoption coordination
* refactor(codex): split session catalog modules
* fix(anthropic): narrow adopted history for import
The coordinator migration hoisted history to an optional outer binding;
afterCreate needs the definite array from the create scope.
Also drop the retired session-catalog max-lines ratchet entry.
Keep routine Codex commands from pausing for approval in default and team YOLO sessions. The default exec policy omitted its full mode, so a generic tool hook could promote approvalPolicy from never to untrusted.
* fix(codex): keep prior finals when a coda follows without tool work
Live persist last-won a single assistant slot, so a trailing final_answer
replaced the real summary. Keep explicit finals after the last native tool;
phase-less coordination text stays last-wins.
* fix(codex): record the native-work barrier from item notifications
turn/completed.items is a last_agent_message Summary, so result-time
tool scans cannot see intervening work. Invalidate persistable finals
when native tools arrive, drop trailing silent tokens, and let a later
unphased message replace earlier finals.
* fix(codex): make the persistable-text barrier once-per-handoff
Delayed native completions must not move the barrier past an answer that
arrived while that tool was already active. Dynamic tool calls are the
same handoff. If no audible text exists after the barrier, recover the
last persistable answer instead of dropping it.
* fix(codex): keep a post-handoff silent final authoritative
A tool after an audible final starts a new answer segment. If that
segment is NO_REPLY, do not revive the pre-tool text. Recover a prior
answer only when the post-handoff segment is empty.
* fix(codex): classify silent finals with the delivery payload predicate
Token-only matching missed JSON and reasoning-wrapped NO_REPLY
payloads, so a trailing control envelope could join into the visible
answer. Use the shared payload-level predicate.
* fix(codex): honor unphased replacement and sleep handoffs
A later unphased item must invalidate older finals even when a later
final skips it, and sleep is a native answer barrier.
* feat(gateway): add remote-exec cloud placements
* feat(codex): run cloud turns through remote exec
* fix(sandbox): quote ssh_config path directives containing whitespace
Crabbox lease keys default to ~/Library/Application Support/... on macOS;
unquoted IdentityFile/UserKnownHostsFile/CertificateFile arguments tokenize
on the space and fail as 'extra arguments'. Found via live remote-exec
cloud-worker proof.
* test: consolidate gateway maintenance schedule coverage
* fix(ci): invalidate plugin sdk declarations on state changes