Transport proof for the migrated ingress path: offset-after-spool-ack,
webhook redelivery on write failure, update_id/lane mapping, and the
authorization-gated supersede suite including paired-DM, owner, topic-override
and other-bot-command cases (#108656).
Both transports now enqueue durably and pump the shared drain: polling keeps
offset-after-committed-enqueue, webhook keeps 200-after-spool-write. Deletes
the reply fence, private claim-owner, retry policy, and per-transport
claim/replay/watchdog loops. Dispatch outcomes propagate: failed-retryable
releases for replay instead of tombstoning. Supersede policy stays
Telegram-owned, authorization-gated with pairing-store and owner identities
via the same resolver as normal ingress; room_event ambient pending remains
supersedable by a later user turn (#108656).
Replace the bare onTurnAdopted callback and queuedFollowupLifecycle with one
turnAdoptionLifecycle surface (onSettled guaranteed via finally; adoption-loss
aborts queued steering turns without transcript replay), threaded through the
turn kernel and agent runner, exposed to plugins via
runtime.state.openChannelIngressDrain and the channel-outbound SDK barrel,
with surface budgets re-measured against the narrowed baselines (#108656).
Core drain modules over the existing channel ingress queue: claim recovery
via process-instance liveness (no same-process claim stealing), per-lane
serialization, complete-at-adoption with bounded claim-token-fenced tombstone
retry that never surrenders ownership on failure, retry/dead-letter
disposition with attempt floor and age gate, claim-refresh heartbeat,
pre-adoption stall watchdog, typed IngressAdoptionLostError for late adoption
after guillotine/supersede, and predicate-gated pre-adoption supersede that
tombstones. Contract tests encode the lifecycle invariants relocated from the
Telegram spool machinery (#108656).
Mechanical: manager, combined facade, install path, lifecycle, config
loading, and resolver types move to focused modules; the runtime facade
re-exports its prior surface so importers are unchanged. Deletes dead
tools-barrel re-exports flagged by check-dependencies. No behavior
change.
Path-borne session tokens leak verbatim through fetch/undici transport
error strings; exact-value registration of the whole resolved URL closes
the last credential egress surface.
Requester-scoped servers never enter harness-native MCP config: Codex
thread and CLI user-MCP projections partition them out (fail-closed,
static-partition fingerprints stay sender-independent so shared threads
never rotate). Codex delivers them as per-attempt dynamic tools through
a scoped-only runtime path on the same core manager, with a
session-stable advertised catalog so authed and unauthed senders share
one spec surface; unauthed calls get a clean not-connected tool error.
New SDK seam materializeRequesterScopedMcpToolsForHarnessRun lets any
future harness consume the same contract.
Plugins can register api.registerMcpServerConnectionResolver({serverName,
resolve}) to bind an MCP server's transport (url/headers) to the trusted
sender of each message. Server identity stays static; only the connection
is per-requester. Runtimes are cached per (sessionId, channel, accountId,
senderId); runs without a trusted requester fail closed (cron, subagent,
heartbeat). Resolved credentials never enter fingerprints or logs. Static
servers keep the existing session-scoped lifecycle byte-for-byte.
Enables per-user hosted MCP sessions (e.g. per-user OAuth email/calendar)
in multi-sender channels like Slack.
Refs #106229
A failed, refused, superseded, or thrown restart emission could leave the
reversible restart-signal admission fence closed forever: concurrent emitters
could overwrite the live rollback lease with a dead stand-in, the fenced body
had no try/finally, and the outer catch swallowed errors precisely because the
stuck fence made isGatewayRestartDraining() true. The gateway then rejected
every new task with GatewayDrainingError - silently - until an operator
restarted the process.
beginGatewayRestartSignalAdmission now returns null instead of stand-in
leases (single fence owner), emitPreparedGatewayRestart reopens the fence on
every non-delivery path via try/finally while preserving it whenever a queued
SIGUSR1 is unconsumed, refused-signal cleanup force-clears orphaned fences,
and admission close/reopen transitions are logged with their reason. The
self-contained SQLite restart-intent persistence moves to restart-intent.ts
to keep restart.ts within the LOC ratchet.
Fixes#107322
The retention bound, claim lease default, and liveness options type were
exported only for tests, which fails the dead-export gate. Tests pin the
policy literals locally instead.
Refs #107246
Telegram update ids are global across chats, but the update tracker rejected
any id at or below its single high-water mark. When one lane was delayed
while other lanes completed newer ids, the delayed durable-spool replay was
rejected as accepted-watermark and tombstoned without dispatch - silent
message loss. The tracker now rejects only restored-offset ids and exact ids
it already accepted, with the accepted-id set pruned at the persisted offset
plus a bounded retention window; the startup-only floor-replay special case
is deleted.
Refs #107246
Spool claim owners now encode pid:starttime:uuid, and live-owner detection
requires the pid to exist with a matching process start time. Bare
process.kill(pid, 0) treated Linux thread IDs and recycled PIDs of the new
gateway as a live foreign owner, wedging a chat lane behind a dead claim for
the full 30-minute lease after a restart. Legacy pid:uuid and explicit-x
owners keep existence-only lease protection so rolling upgrades and
no-starttime platforms cannot double-dispatch a live worker's claim.
Refs #107246
A gateway-tool restart parks sentinel/continuation hooks in
pendingRestartEmitHooks, but emitPreparedGatewayRestartUnderAdmission only
drained them when emitting hookless. When the config-reload deferral won the
emission race with its own emitHooks, the restart sentinel was never written:
no post-restart notice, no continuation turn, stale pendingRestartSessionKey
surviving in-process restarts, and orphaned hooks never rejected.
Run the caller preflight first, then drain parked hooks with the original
replace/reject loop whose tail re-read also captures hooks accepted while the
caller's beforeEmit awaited; release session ownership only once the slot is
settled and no awaits remain before emission. Reject every prepared hook set
on non-emitted outcomes and error-isolate afterEmitFailed callbacks. The
empty-slot path stays await-free so mid-flight intent/deferral timing is
unchanged, and emission attempts remain single-flight behind the restart-signal
admission fence (now documented).
Fixes#106138
agents.list[].model was agent-tunable while agents.defaults.model was
refused — the same capability gated by config shape (partially reverts
the defaults-spelling removal from #90145; token/auth/exec paths stay
protected). The bare refusal string also caused agents to misread the
injection boundary as an authorization failure and argue with the owner;
it now states the contract, the remedy, and the agent-tunable paths.
Guard moved to gateway-config-guard.ts (gateway-tool.ts 652->390).
Closes#106059
Out-of-run compaction (budget preflight, /compact, subagent announce)
forced harness selection to the core runtime whenever a session had no
bound/planned/explicit harness, so ChatGPT-OAuth-only sessions on the
implicit Codex runtime failed auth against openai/openai-responses and
subagent completion announcements were dropped after retries.
Leave the selection override unset in both compaction paths so prepared
OAuth auth can select the credential-owning harness (as live runs do),
and ensure the policy-selected harness plugin is loaded before
selection. Native transcript compaction stays gated on the resolved
harness only.
Fixes#90925
Omitted final on a confirmed message-tool-only source reply keeps main's terminate-on-delivery semantics and records a completed marker; only explicit final=false defers termination. Project the Codex-only final schema property in place on attempt-fresh tools instead of adding a public plugin-SDK clone export, resolving the SDK surface budget failure.
5e9bc0916f switched two SettingsProTabSections accessibility labels
from .accessibilityLabel(Text(title)) to .accessibilityLabel(title);
title is LocalizedStringKey so lookup stays localized, but the wrapper
contract in apple-app-i18n.ts still required the old spelling and the
tooling shard only runs on PRs, so main broke silently.
Direct iOS/Android commits on main shifted source line numbers without
running the path-filtered native-i18n job, so `native:i18n:check` fails
on any PR that touches a native path. Metadata-only resync; entry count
unchanged (4200).
Native command turns keep #104144 source-keyed admission, but a turn that
continues into a full agent turn (/steer fallback, /goal, /learn, unhandled
body) now moves its reservation and lifecycle lease to the target session
before running. Concurrent target inbounds see the owner and queue/steer
instead of double-admitting and splitting the session into two conversation
families; steering also targets the registered run owner instead of a stale
source-keyed reservation.
Fixes#104844
An unprobeable api-key choice left its flag/env literals unchecked while CI
stayed green (ClawSweeper finding). The probe now supplies a real agent dir
and placeholder preflight opts (sentinel still bound to the declared
optionKey only, preserving the key-correctness proof), so every api-key
method must reach resolveApiKey — this immediately exercised
cloudflare-ai-gateway's account/gateway preflight path.
Loading built plugin dists pulls large module graphs into the shared vitest
worker cache and broke co-resident vi.mock unit tests (memory-host-sdk
embeddings, checks-node-compact-large-2). An explicit dynamic import of the
surface loader trips the lane classifier's dynamic-import rule, moving the
file to the plugins project where dist loading is the norm.
Silent skip on missing runtime registration recreated the drift class the
test guards (ClawSweeper finding); capability-only plugins now must register
zero text providers to be exempt.
context_overflow and transient (timeout/server_error/overloaded) FailoverError
reasons now classify structurally like billing/rate_limit already do, instead
of relying on message text that may lack overflow or HTTP-status tokens. A
typed transient failure without a leading status token now takes the single
transient retry; typed context overflow suppresses the generic failure reply.
Part of #104219 (row 12).
gateway-startup-plugin-ids duplicated the built-in model-API set byte-for-byte
with nothing enforcing sync; provider-config-owner now owns it. New parity
test proves bundled provider manifests and runtime provider.auth declare the
same optionKey/cliFlag/envVar/method literals, guarding against silent
wizard-vs-runtime auth drift. Part of #104219 (row 12).
Drops the last non-doctor whole-store resolveSessionStoreEntry read in the
reply pipeline; exact working-set key plus accessor disk read match the
sibling refresh sites in runPreparedReply. Part of #104219 (seam 8).
The latest stable package exposes both names via the deprecated
openclaw/plugin-sdk/infra-runtime subpath. Compat now lives in the barrel
itself with the old substring semantics and a removal plan; core runtime no
longer uses them. Also converts the chat mapping switch to an if-chain for
switch-exhaustiveness lint.
FailoverReason timeout is the retryable-transient bucket and deliberately
swallows generic 5xx, so it cannot drive the user-facing timeout badge.
Rate-limit/overload/context mapping stays on the canonical reason; the
timeout badge now requires isTimeoutError. HTTP 500 badges stay unknown.
Deletes the naive substring detectErrorKind from infra/errors; the chat error
badge now maps resolveFailoverReasonFromError into the protocol errorKind set,
with refusal kept as a chat-local stop-reason check. Fixes drift where a Groq
TPM 413 showed context_length instead of rate_limit and quota/CJK/socket
errors showed unknown.
Part of #104219 (seam 5, slice 1).
One RUN_STALE_TAKEOVER_MS constant and one resolveRunStaleThresholdMs in
diagnostic-run-activity.ts replace the hand-paired constants and duplicated
blocked-tool-floor derivations in the reply and embedded run registries.
Thresholds are byte-identical before and after; zero behavior change.
Part of #104219 (seam 4).
Run errors can expose hostile getters; classification must not replace the
original failure or suppress its terminal event. Matches the containment
contract of isProviderTimeoutError in the same module.
One shared resolveCliToolTerminalReason owns the timed_out/cancelled/failed
decision for one-shot and live CLI runners; abort-signal lifecycle fields are
authoritative, error shape is the fallback. Deletes both local derivations.
Fixes live-path drift where a timeout abort delivered as a generic AbortError
was reported as cancelled instead of timed_out.
Part of #104219 (seam 3).
* fix(mcp): redact secrets in /mcp show replies
Prevent owner /mcp show from dumping raw headers/env credentials into
group chats. Mark MCP env values sensitive, restore redaction sentinels
on set so show→set cannot overwrite real secrets.
Fixes#103053
* test(mcp): e2e redaction of /mcp show secrets on live gateway
Spin up an ephemeral gateway process, chat.send /mcp show with live
credential-bearing config, and prove secrets never leave the reply path.
Also prove show→set sentinel restore on disk via chat and CLI.