* fix(codex): use truncateUtf16Safe for attempt notification text truncation
.slice(0, 237) on notification text can split surrogate pairs from
CJK characters or emoji in agent output, producing U+FFFD in Codex
desktop notifications.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(codex): cover UTF-16-safe attempt previews
* test(codex): assign notification coverage to a shard
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* feat(codex): scope app-server rate limits to the physical client
Replace the process-global rate-limit cache with a WeakMap keyed by the
physical app-server client, tracking per-limitId revisions. Rolling
account/rateLimits/updated notifications merge sparsely per the protocol
contract (credits/individualLimit/planType survive nulls), and
usage-limit errors only trust a snapshot for auth-profile blocking when
the same client observed a primary update during the failing turn's
startup. Fixes cross-client rate-limit bleed in usage-limit error
messages. A new client-runtime module installs the
account/chatgptAuthTokens/refresh handler and the rate-limit observer
once per physical client, replacing per-start inline handlers in
shared-client, run-attempt, and side-question.
* refactor(codex): split thread/resume subscription safety into thread-resume
Move the thread/resume request out of thread-lifecycle into a dedicated
thread-resume module that retires the exact physical client when resume
acceptance is indeterminate: only a structured RPC rejection proves
Codex holds no subscription, so any other failure abandons the client
instead of returning it to the shared pool. Resume responses naming a
different thread now fail closed (assertCodexThreadResumeSubscription),
and the fresh-thread fallback requires a released subscription unless
the resume was a proven RPC rejection.
* refactor(codex): replace client-factory positional DI with shared-client factory
Delete the lazy positional-argument CodexAppServerClientFactory and use
the options-object factory type exported from shared-client. Callers in
run-attempt, compact, bounded-turn, provider-capabilities, and the
web-search provider now default to getLeasedSharedCodexAppServerClient
directly; the lazy indirection was ineffective because those modules
already import shared-client statically.
* feat(codex): route app-server turn traffic through a keyed turn router
Install one turn router per physical app-server client and replace the
broad per-attempt notification/request fanout with explicit per-thread
routes. Attempt startup reserves the thread route (before thread/resume
on the resume path, so early notifications buffer instead of racing),
run-attempt activates it with receive-time, queued, and request
handlers, arms the route before turn/start, binds the accepted turn id
to flush buffered traffic in wire order, and releases the route on
cleanup. Requests for a pending turn wait for binding instead of being
auto-declined, native turn completion waits use route state instead of
scanning buffered notifications, and correlation readers now match the
canonical v2 wire shapes only (top-level threadId, nested turn.id). The
unscoped response-delta lease-count attribution and its client API are
deleted along with the retired correlation predicates.
* test(codex): reset the shared binding store between thread-lifecycle tests
SQLite bindings are keyed by session identity rather than the per-test
temp dir, so earlier tests leaked resumable threads into fresh-start
expectations. The old silent resume-failure fallback masked the leak;
subscription safety surfaces it.
* test(codex): reset the binding store between delivery-hint iterations
The loop reuses one session identity across iterations, so the previous
iteration's thread would resume against a harness that cannot serve it.
* refactor(codex): raise app-server floor to 0.142 and drop range-compat protocol paths
* refactor(codex): model subagent mirror state as one map
* style(codex): format event-projector
* test(codex): drop unused shared-client test import
* refactor(codex): drop v1-era notification field aliases
* fix(codex): teach models to load deferred native spawn_agent via tool_search
* docs(codex): realign harness config tables
* docs(changelog): note Codex app-server protocol update
* fix(codex): project guardianWarning circuit-breaker notification
Codex emits a `guardianWarning` notification from its rejection
circuit-breaker (e.g. 3 consecutive or 10 total denials in a turn)
right before ending the turn as interrupted. The app-server event
projector had no case for it, so the rejection-limit reason was
silently dropped and the turn degraded to a generic interruption.
Project it on the `codex_app_server.guardian` stream with phase
"warning", alongside the existing autoApprovalReview handling. The
exact v2 param shape is not bundled in OpenClaw's protocol schemas,
so the human-readable reason is read defensively.
Closes#101207
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(codex): route guardianWarning through thread-scoped filter
`guardianWarning` is thread-scoped — codex emits it with only
{message, threadId} (no turnId). The turn-strict pre-switch filter
therefore dropped it before it reached the projection switch, making
the handler added in the previous commit dead code for real traffic.
Correlate it on the thread alone (mirroring hook notifications) so it
reaches the switch, and read the `message` field per the codex schema.
The regression test now sends the real {threadId, message} shape
instead of a synthetic turnId, so it actually exercises the routing.
Refs #101207
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* repro(codex): add real behavior proof for guardianWarning projection
* fix(codex): narrow guardian warning projection
Co-authored-by: 曾文锋0668000834 <zeng.wenfeng@xydigit.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* feat: correlate native search outcomes in audit history
Metadata-only audit ledger for agent runs and tool actions in the shared
state DB: stable event identity, closed action/status/error vocabularies,
one-way-hashed tool-call ids, never-inferred terminal outcomes for native
web-search (explicit completed/failed only; otherwise unknown), bounded
retention, audit.list gateway RPC and openclaw audit CLI. Squashed from
the 82-commit audit stack for replay onto current main.
* feat(audit): add audit.enabled config gate (default on)
The metadata-only audit ledger records by default: an audit trail enabled
only after an incident cannot explain the incident, and the rows are
strictly less sensitive than the transcripts every install already
stores. audit.enabled=false stops new writes at the gateway subscription
seam; audit.list and openclaw audit keep serving existing records until
they expire. Documented in the configuration reference, protocol page,
and CLI reference.
* fix: repair full-matrix CI findings after rebase
- break the dynamic-tools/dynamic-tool-execution import cycle by
extracting resolveCodexToolAbortTerminalReason into a leaf module
- restore main's session-worktree protocol exports lost in the
index.ts auto-merge
- register the audit event writer worker as a knip entry point
- docs table formatting; subagent wait-cancellation test scoped to its
audit intent (outcome + timing) and advanced past main's new
lifecycle-timeout retry grace
Approval wait now fits inside the Codex dynamic-tool watchdog (70s +10s
gateway grace under the 90s kill), approval cards carry proposal id,
skill name, description, file count, and body size (spoof-safe
rendering), and timeouts return a structured pending-not-failed outcome
instead of a bare error. Expired requests cannot execute late; no
auto-apply; generic plugin approvals unchanged.
* fix(ios): restore in-flight chat runs from gateway history
Restore active Apple chat ownership across reconnect, foreground, and sequence-gap recovery using the existing chat.history snapshot. Preserve agent/session scoping and Gateway user-turn identity across Codex and Copilot mirrors, including current offline-cache integration.
* fix(ios): restore in-flight chat runs from gateway history
* feat(crestodian): AI-first conversational onboarding with typed-op guardrails
Interactive `openclaw onboard` (and bare `openclaw` on a fresh install) now
opens the Crestodian conversation: detection-backed first-run proposal
(Claude Code/Codex logins, API keys), persona AI turns for every free-form
message (configless local-runtime fallback, 60s deadline, deterministic
degradation), approval-gated typed operations, chat-hosted channel setup
(`connect <channel>`), config get/schema read ops with secret redaction, and
a post-write validation hook that feeds schema errors back for a self-fix
turn. Adds the additive gateway `crestodian.chat` method so app clients run
the same conversation. Classic wizard stays behind --classic/explicit flags;
non-interactive automation unchanged; `--modern` becomes a deprecated alias
for `openclaw crestodian`.
* feat(macos): Crestodian chat onboarding and importance-ordered permissions
Replace the gateway step-wizard page with a Crestodian chat over the new
crestodian.chat method (works before any model auth exists), sort the
permissions page by importance with no scrolling, drop the redundant manual
refresh, and bump the onboarding version.
* feat(crestodian): run the custodian on the real agent loop with a ring-zero tool
Crestodian conversations now execute through the same embedded agent runner
as regular agents: a persistent agent session with a single construction-gated
`crestodian` tool wrapping the typed operations (read actions free; mutations
require approved=true asserted from explicit user consent, audited, with
post-write config validation fed back into the loop). The engine prefers the
loop (configured models or the Codex app-server fallback) and degrades to the
single-turn planner, then to deterministic commands. Setup approval seeds the
crestodian exec approval so local model harnesses can run; the configless
Codex backend config now enables exec and direct tool loading (it was
dead-on-arrival behind tools.exec.mode=deny and the tool-search index).
* test(crestodian): type the engine mock signatures for the core test lane
* fix(crestodian): map the advertised create_agent tool action
* fix(crestodian): host-verified approval arming and a macOS setup completion gate
Review findings: the model-supplied approved flag alone could authorize
ring-zero mutations (prompt injection / model error), and removing the macOS
wizard gate let users Next past the Crestodian page with nothing configured.
Mutating tool actions now also require host-verified consent (the engine arms
approval only when the user's actual message is an explicit yes), and local
macOS onboarding blocks advancing until setup authored the config, using the
same signal the old step wizard checked.
* fix(crestodian): bind approval to the exact proposed operation and gate dot navigation
A generic yes no longer authorizes arbitrary mutations: denied mutating tool
calls register a canonical operation fingerprint (host-owned, per session),
and an armed turn executes only the identical call, once. The denial message
is arming-aware so the approved turn self-heals in one roundtrip, and the
agent protocol pre-registers proposals. macOS onboarding page dots now honor
the same setup-completion gate as the Next button.
* fix(crestodian): redact sensitive wizard answers, skip logged-out CLIs, gate programmatic advance
Sensitive channel-wizard answers (tokens, passwords) are redacted from the
AI-visible conversation history; setup and the onboarding welcome never pick
or advertise a definitively logged-out CLI as the model; and macOS
handleNext() honors the page gates for programmatic callers (chat handoff)
just like the Next button.
* fix(crestodian): align the onboarding welcome's configured predicate with the app gate
A valid config carrying only a default model (partial/hand-written) now still
gets the first-run proposal instead of the ready guide, so the macOS setup
gate can always be satisfied from the conversation.
* fix(crestodian): armed turns can never mint their own executable proposal
An approval-mismatched call inside an armed turn no longer re-registers and
invites a retry (which let the model swap the approved operation for another
in the same turn); it voids the approval entirely and requires a fresh yes.
Proposals register only in unarmed turns, which the agent protocol already
does when proposing.
* fix(onboard): route any explicit setup flag to the classic wizard
* fix(ci): satisfy new lint rules, tool-display guard, and generated artifacts for crestodian
* chore(i18n): refresh native inventory after permissions copy wrap
* fix(crestodian): harden conversational onboarding
* docs(crestodian): document conversational onboarding
* test(crestodian): type embedded runner mock
* fix(crestodian): close onboarding security gaps
* chore: retrigger ci
Prevents opted-in plugin runtime dependencies from being published with stale
registry pins. The same freshness assertion now guards both npm and ClawHub
release paths, including the managed Codex runtime.
Prepared head SHA: d5d0ecba4b
Reviewed-by: @fuller-stack-dev
Closes#99951
* feat: add session thread management
Squash of codex/thread-management (025aefc3ad1) onto origin/main:
pin/archive/rename sessions via sessions.patch, archived-aware
sessions.list, lifecycle fencing, read-only archived chat, SDK +
Swift protocol support, Control UI session management.
* refactor(ui): minimal session rows with hover-revealed management
Chat picker and sidebar recents share session-row primitives: single-line
rows, relative timestamps, rename/archive/pin revealed on hover or focus,
accent pin badge for pinned rows, and an active-run spinner in the trail
slot. Sidebar floats pinned sessions above recency via the shared
comparator and gains archive/pin actions through the unified sessions-view
patch fallback. Archive eligibility is one shared policy
(canArchiveSessionRow); the sidebar/picker active-run tooltip now uses the
real sessionsView.activeRun locale key.
* fix: align session admission with mailbox-era main
Integration fixes after rebasing onto current main: sessions_list mailbox
test expectations learn the archived/pinned row fields and archived:false
list param; gateway agent admission treats a session as deleted only when
both the requested and canonical alias sets miss it (legacy bare-main
stores and exec-approval followups read under different spellings); cron
persist tests keep a consistent store across claim-guarded persist calls;
the ACP abort hook test asserts abort propagation instead of signal
identity; drop dead lifecycle writes flagged by no-useless-assignment and
fix the promise-executor return in the codex compact test.
* fix(qa): align UI e2e and shard fixtures with redesigned session rows
Sidebar session rows are wrapper divs with an inner link now: update the
navigation browser tests and chat-flow Playwright selectors. Seed a real
per-test session store for the auto-fallback admission guard instead of
depending on leftover host files at /tmp/sessions.json. Teach the
test-projects routing fixture about the suites that newly import the
shared temp-dir helper. Document the Codex thread-format contract for
archivedAt/pinnedAt (flag derived from server-stamped timestamp, epoch ms
here vs Codex epoch seconds) at the type and in the session docs.
* test: route auto-fallback suite through temp-dir helper plans
The auto-fallback suite now imports the shared temp-dir helper for its
seeded session store, so the top-level helper routing fixture must list
it in the auto-reply plan.
Session ingest has normalized legacy assistant string content into
[{ type: "text" }] blocks since #98908, which updated the core
cli-runner session-history analog but missed this extension file.
The suite only runs in full local runs and the dispatch-only
plugin-prerelease lane, so push/PR CI stayed green while local
scripts/pr prepare-gates failed on any OS.
refs #99857
* fix(codex): keep OpenClaw control tools direct
* test(codex): refresh direct-tool prompt snapshots
* fix(codex): keep heartbeat direct only when available
* fix(codex): keep heartbeat tool schema stable
---------
Co-authored-by: Eva <eva@100yen.org>
Co-authored-by: joshavant <830519+joshavant@users.noreply.github.com>
isCodexToolResultError fail-closes any dynamic-tool result whose
details.status is absent from its non-error allowlist. get_goal returns
details.status "found" or "missing" (a successful read of the thread
goal, or its absence), neither of which was in the allowlist, so every
get_goal call was classified as an error: reported to codex as
success: false and persisted on the transcript with isError: true.
Sibling #96856 added the write-side goal statuses (created/updated) and
the accepted spawn status but missed the read-side get_goal statuses.
This adds found/missing alongside them. Genuinely failed statuses stay
fail-closed.
* fix(codex): derive terminal-idle watchdog from effective run timeout
Fixes the early-abort half of #85242. The Codex app-server terminal-idle
watchdog used a hardcoded 30-minute default that was not derived from the
effective run timeout, so a scheduled turn configured with a longer
timeoutSeconds could be aborted early at 30 minutes even with budget left.
resolveCodexTurnTerminalIdleTimeoutMs (now in attempt-timeouts.ts after the
upstream split) accepts the effective run timeout and, with no explicit
override, follows the run budget instead of the 30-minute default:
- explicit override always wins (advanced config / tests)
- otherwise terminal-idle = max(30min floor, run budget), so a longer run is
no longer cut short and existing protection is never shortened
- falls back to the 30min default when no run budget is known
Reuses the existing resolvePositiveIntegerTimeoutMs helper, matching the
neighbouring post-tool resolver. Adds focused unit tests for the derivation.
The diagnostic-wording half of #85242 (naming the terminal-idle watchdog in
the surfaced guidance) is left as a separate follow-up.
* fix(codex): derive terminal-idle watchdog from effective run timeout
* fix(codex): preserve default terminal idle watchdog
---------
Co-authored-by: openclaw-clownfish[bot] <280122609+openclaw-clownfish[bot]@users.noreply.github.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* feat(openai): add GPT-5.6 series support
* docs: refresh map for GPT-5.6
* fix(openai): preserve GPT-5.6 thinking metadata
* fix(codex): sync managed app server version
* fix(codex): sync managed app server version
* fix(openai): account for GPT-5.6 cache writes
---------
Co-authored-by: Peter Steinberger <steipete@golden-gate.local>