Plugin install, replacement, and uninstall clear process memos through
registerPluginMetadataProcessMemoLifecycleClear, but four executable-
authority caches never registered, so retired plugin callbacks kept
executing after the registry moved on:
- createConfigScopedPromiseLoader (document/web-content extractor lists)
now self-registers its clear at the factory, so no caller can leak
resolved plugin callbacks past a lifecycle change.
- Provider policy surface maps (bundled + external, including cached
negative entries) clear on lifecycle changes.
- Public surface loader now drops module exports, loader closures, and
native require cache entries, not just resolved locations.
- SDK facade loader registers the same clear for facade exports and
loader state; imported-plugin history is preserved as diagnostics.
The tracked-roots + native-require eviction pattern from provider
discovery is extracted into clearPluginModuleLoaderLifecycleCache and
reused by provider discovery, doctor contracts, the public surface
loader, and the facade loader, removing two near-copies.
Regression tests fail pre-fix: replaced or uninstalled plugin callbacks
must not run after clearPluginMetadataLifecycleCaches, proven down to
on-disk artifact replacement through the native require chain.
* fix(auth): keep a retired auth JSON from stranding a migrated store
Runtime failed closed with AUTH_PROFILE_MIGRATION_REQUIRED whenever a retired
credential file was present, even when the canonical SQLite store already held
the agent's profiles. One leftover auth.json therefore made a fully migrated
install unusable, and the gateway lifecycle preflight refused start/restart on
top of it, so every channel and provider stayed offline until Doctor ran.
A legacy file is now only fatal when the canonical store cannot serve
credentials. Doctor's importer never overwrites a usable stored credential, so
a file sitting beside a populated store is unarchived bytes, not pending
migration: runtime logs a one-time warning and keeps serving. An empty store
with a credential file still fails closed and never falls through to
environment auth. Startup degrades that owner to configured-unavailable
instead of refusing to boot, which lets the lifecycle preflight go away.
* refactor(secrets): retire the auth-profiles.json vocabulary
Auth profiles moved to SQLite, but operator-facing surfaces still named the
retired JSON file. The duplicate-agentDir error told operators to copy
auth-profiles.json to share credentials, which does nothing and lands the
second agent in a migration-required state; `openclaw migrate plan codex`
reported a target file that is never created; and the secrets picker labelled
candidates with a filename that no longer exists.
Renames the SecretTargetConfigFile discriminator to "auth-profile-store" and
corrects the operator-facing text, the migrate plan target, and the docs that
described the file as a live target. Genuine legacy-filename uses in doctor,
the security fixer, and migration fixtures are unchanged.
Also deletes resolveSecretPlanTargetByPath and ResolvedSecretPlanTarget from
the plugin SDK. They have no callers in core, plugins, or tests, and the
symbols are absent from the latest stable tag, so they carry no compatibility
obligation and are removed rather than deprecated. Their inline parameter type
was the only thing putting the retired filename on the public SDK surface.
* improve(wizard): warn about device-code phishing
The device-code prompt only warned against sharing the code, and only when an
expiry was known. Device-code phishing works the other way around: the attacker
starts the login and gets the victim to enter the attacker's code. Codes
delivered over a chat channel are the risky case and carry no expiry hint, so
the warning is now unconditional and covers received codes, matching the Codex
CLI prompt.
Also documents the Codex auth handoff: a subscription profile is installed as
in-memory external auth rather than persisted, and token refresh is inverted
so the refresh token stays in OpenClaw's store.
* fix(test): make transcript read-failure injection order-independent
server.sessions.compaction-read-errors.test.ts injected its failures with
mockRejectedValueOnce, which fails the NEXT call to loadTranscriptEvents
globally. Under --isolate=false a shard shares one worker, so any sibling
transcript read could consume the one-shot rejection before the compaction RPC
issued its own; compaction then ran against the real reader and returned ok,
failing three assertions. This shard was already red on main; a prior repair
fixed the mock's initialization order but left the call-order dependency.
Key the injection on the seeded sessionId instead, so unrelated readers cannot
consume it and the re-read case counts only its own session's reads.
Also updates two expectations invalidated by this branch: the duplicate-agentDir
remediation text, and the plugin SDK export ratchet, shrunk by the two retired
secret-plan exports.
The memory CLI resolved --agent by returning the caller's string verbatim,
so an id that is not configured produced a confident empty result:
`memory status` rendered a panel for it, `memory index` fabricated a
workspace-<id> path, and `memory search` reported No matches. A typo read
as an empty memory rather than a nonexistent agent, while hooks, status
--usage, capability, migrate, and session targets already rejected unknown
ids.
Consolidate that duplicated check into resolveConfiguredAgentId beside the
agent roster owner, reuse it at the matching core sites, and route memory
to it through the existing memory-core host-runtime facade so no new
plugin SDK surface is added.
The canonical hint uses formatCliCommand rather than a literal: under a
profile or container the bare command is wrong, so consolidating on a
literal would have regressed the hooks and migrate hints and left the
status, capability, and session-target hints unrunnable.
The OpenAI ChatGPT auth profile identity was derived in two drifted copies:
the plugin-sdk helper (used by Codex/Hermes import migrations) fell back to
bare JWT sub and leaked the workspace accountId into the user subject, while
the openai extension copy (used by login/refresh) composed the OIDC-stable
iss|sub pair and honored the credential email fallback.
Fold the extension's chain into the canonical SDK helper (credential-email
fallback, iss|sub before bare sub, no workspace-id subject), delete the
extension copy, and move all extension call sites onto
openclaw/plugin-sdk/provider-auth. Cross-checked against Codex OSS
(codex-rs/login/src/token_data.rs): chatgpt_account_id is workspace identity,
never user identity. No doctor migration: the divergent fallback branch only
fires for tokens lacking every user-id claim and email, which real ChatGPT
tokens do not produce, so no shipped install holds divergent keys from it.
* fix: surface Codex input prompts across runtimes
Codex structured input now reaches bounded Gateway questions in native and ACP runs, with exact turn ownership, explicit unsupported outcomes, and cancellation fencing. Consume the published ACPX elicitation support.
* chore: align elicitation helpers with current guards
Use protocol-specific helper names required by current main and update the reservation regression to the generalized input owner.
* fix: formalize structured input SDK surface
Expose one documented, frozen agent-harness structured-input contract with runtime and subpath coverage, replacing the accidental function-property API.
* fix: satisfy elicitation architecture gates
Register the real-process ACPX fixture as an executable test root and move shared structured-input types into the boundary leaf to keep Knip and Madge clean.
* fix: remove structured input lint suppression
Preserve the rejected control and invisible-character ranges with an explicit code-point check so the production suppression inventory stays closed.
The cloud machine picker described each class in prose ("Cheap smoke checks
and small repos"), which was the widest thing in the row, got ellipsized, and
did not say what the operator actually picks on. Machine options now carry the
class shape and the picker renders "32 vCPU · 64 GB" instead.
Crabbox reports per-provider class shapes from `providers --json`; the plugin
reads that catalog once per lifecycle and attaches cpu/memoryGb to each option.
A missing binary, failed command, unparseable output, absent classes, or an
unmatched provider all degrade to label-only rows, so an older Crabbox keeps
working.
`description` was never released, so it is removed rather than deprecated, and
listMachineOptions becomes async for the catalog read. Deletes the duplicate
machine-option projector in the environments server method.
* fix(copilot): add OpenClaw prompt guidance
Copilot append-mode system messages included credential safety, workspace bootstrap, and extra context but omitted OpenClaw delegation and reply-delivery policy.
Build guidance from the final policy-filtered tool surface so visible delegated work, Skill Workshop, and source replies follow the same behavior as Codex.
* fix(copilot): break prompt guidance import cycle
The CI architecture gate detected a cycle through attempt-config and prompt-guidance. Isolate raw-run mode detection in a leaf module.
* fix(gateway): harden hook admission and shutdown
Flush structured 413 and 408 hook responses before closing oversized or timed-out request sockets, and reject deferred wake requests that target an explicit session.
Direct Gateway close now marks startup and readiness as draining immediately and attempts every HTTP listener shutdown before reporting an incomplete close.
* test(gateway): model request destruction in fuzzing
Give the HTTP request fuzz double the IncomingMessage destroy contract exercised after 413 and 408 responses finish.
* fix(gateway): preserve response and shutdown bounds
Route installed webhook body-limit failures through the shared response-first close owner and require complete Feishu 413/408 responses before terminal connection closure.
Retain the current-main grace bound for agent-harness disposal so one stuck harness cannot block listener teardown.
* test(telegram): model response-close request cleanup
Keep the slow-body request alive through response finish, then assert cleanup when the mock response transport closes.
The `## Delegation` guidance added in #125691 lived only in
buildAgentSystemPrompt, so Codex-runtime agents never received it: the
Codex harness builds its own developer instructions in
extensions/codex/src/app-server/thread-prompt.ts and imports nothing
from the system-prompt builders. Live A/B on gpt-5.6-luna had the native
runtime answer "spawn a visible session" while the Codex runtime
answered "spawn a hidden subagent".
Move the policy into src/agents/delegation-guidance.ts, owning both the
main-session mode resolver and the section text, and export it through
the agent-harness plugin SDK barrel that the Codex harness already uses.
The hidden-delegation vocabulary is injected by each runtime, so core
never names a plugin-owned tool: native passes `sessions_spawn`, Codex
passes native `spawn_agent`. Visible sessions stay `sessions_spawn`
with visible=true on both runtimes because Codex-native children are
never OpenClaw sessions.
Also narrows the Codex line that told the model to use `sessions_spawn`
only for OpenClaw/ACP delegation; it now scopes that to internal
legwork, so user-facing deliverables still route to a visible session.
* fix: honor inherited model pins over channel defaults
Child sessions now keep parent model pins consistently across reply, status, harness prediction, and agentCommand.
* test: align agent command model selection fixtures
Keep concurrent override and threaded parent-selection coverage aligned with canonical model normalization and runtime snapshot access.
* fix(ci): stop codex lane cold-graph hangs
The side-question domain-policy test loaded the complete agent-harness tool graph inside a one-second readiness race, making the serial non-isolated Codex shard fail or stay silent under cold imports. Build the test's web_search marker and real web_fetch tool from the narrow implementation, then synchronize on turn startup before issuing the tool call. Cap each Codex test process at 12 files so CI gets bounded time-to-first-output as defense in depth.\n\nRefs #125839
* fix(test): keep codex web fetch fixture on sdk boundary
Load the real web_fetch factory on demand through the existing local-only plugin test runtime. This preserves the narrow cold-graph fix without letting a bundled plugin test reach into core internals.
Reuse the canonical realtime audio duration and OpenAI-compatible wire-format helpers while removing redundant state transitions and truncate payload construction. The production diff is net-negative.
Provider interruption policy and lifecycle ownership stay local to each provider; this does not introduce shared mutable assistant tracking or change behavior.
* fix(auth): preserve WHAM classifications and failure recording
WHAM 401/403 state now drives accurate re-auth guidance, while inline hook failures are contained after persistence so recorded failures cannot escape or be masked.
* docs(plugin-sdk): define auth cooldown classifications
document the additive cooldown diagnostic contract and cover its canonical public-SDK projection.
* fix(auth): keep WHAM diagnostics source-compatible
keep cooldownReason canonical, persist exact WHAM diagnostics in optional cooldownClassification, and preserve operator guidance plus failure-hook containment.
* fix(auth): keep failover on canonical cooldown reasons
ensure optional WHAM diagnostics never drive scheduling and discard mismatched persisted reason/classification pairs.
* fix(setup): refresh Codex registry with staged install
* fix(macos): verify inference before onboarding handoff
* fix(setup): use native Codex home for subscription auth
* fix(codex): honor attempt-scoped setup config
* fix(macos): align onboarding handoff with reopen
* fix(setup): await prepared model convergence
* fix(ui): avoid false auth state for empty catalog
* fix(setup): scope catalog convergence to Codex gateway
* fix(setup): publish the committed runtime catalog
* fix(models): project configured static runtime models
* fix(codex): expose app-server model catalog
* fix(models): preserve Codex auth across reloads
* fix(ci): align Codex onboarding checks
* test(ui): stabilize dock suppression environment
* fix(codex): honor discovery config in app-server model catalog
The manifest documents discovery.enabled (bundled fallback list) and
discovery.timeoutMs (default 2500ms) for model discovery; the new catalog
path used the generic 60s request timeout and ignored the enable gate.
Also drop the test-only listModels injection seam in favor of vi.mock.
* fix(setup): refuse prepared Codex auth over an explicit remote transport
configureCodexCliPreparedAuth silently rewrote an explicitly configured
websocket/unix app-server to local stdio (keeping a dangling url), moving
the credential boundary onto this host. Fail setup with actionable
guidance instead; also surface the root cause when the prepared model
catalog refresh fails after activation.
* refactor(agents): one canonical model-catalog identity key
Three near-identical key helpers existed (models-list-result,
models-list-configured-static, harness/model-catalog). Export
resolveModelCatalogIdentityKey from the route-policy owner, collapse the
duplicate dedupe loops into dedupeByKey, make donor enrichment Map-based,
and inline the one-off harness-augment wrapper.
* fix(macos): restore custodian handoff for fresh activations
Landing every finish on the plain dashboard stranded the custodian
first-run flow (memory import, channels, permissions, hatch). Fresh
activations now hand off to custodian onboarding; live-verified
pre-existing setups reopen the normal dashboard, matching the removed
already-configured shortcut. Tests pin the destination per path.
Also isolate the post-startup Codex login test from developer machines:
ambient OPENAI_API_KEY and a real Codex login made it assert-fail.
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Fix xAI barge-in truncation that reported 3760 ms after only 3700 ms of decoded audio.
Share exact audio-duration math across OpenAI, xAI, and Google while keeping provider-specific rounding at each caller.
* fix(voice-call): survive gateway in-process restart and stop CLI dead-ends
The gateway's in-process restart (SIGUSR1 config reload) reuses the cached
plugin registry, so service stop/start run on the same retained voice-call
registration. Generation fencing from #120289 treated that restart as a stale
actor: stop retired the generation forever, the next start silently bailed,
and every voicecall.* RPC answered UNAVAILABLE "runtime generation is
retired" while the webhook never rebound.
- Registrations now hold a replaceable generation: service start after stop
mints a fresh generation, takes over a running slot owned by a retired
predecessor, and reports start failures to service health instead of
silently returning.
- The voicecall CLI classifies gateway failures with typed guards instead of
message substrings: standalone/store fallback only when the gateway is
genuinely absent; reachable-but-failed (request errors, auth, timeout)
exits with actionable text; a standalone webhook port collision explains
that a running Gateway probably owns the port instead of raw EADDRINUSE.
- Plugin SDK gateway-runtime exports structural isGatewayTransportError /
isGatewayClientRequestError guards (+2 documented surface budget).
- Regression coverage: same-registration stop/start restart, retired-owner
takeover, typed CLI fallback classification, and a real token-auth gateway
server routing voicecall.status through callGatewayFromCli.
* refactor(voice-call): split CLI modules and dedupe gateway fallbacks
Collapse the four duplicated gateway-or-runtime command blocks (speak, dtmf,
end, continue fallback) into one generic runGatewayManagerCommand helper —
the continue command owns its legacy-method fallback and operation polling
via a gatewayCall closure, so the helper carries no per-command policy.
Smoke reuses the shared initiateVoiceCall path instead of a bespoke
fallback.
Split the 988-line cli.ts into concept modules (cli-gateway-call,
cli-call-log, cli-command-io) and drop its grandfathered max-lines
suppression plus the now-stale max-lines and assertion-safety baseline
entries (shrink-only ratchet maintenance).
Behavior-frozen: stdout/exit semantics unchanged; net -2 production LOC.
* fix(voice-call): redact gateway URLs in CLI operational errors
ClawSweeper P1: the operational-error formatter interpolated the raw
connectionDetails.url, so a configured gateway URL with userinfo or query
tokens would print credentials into terminal output. Redact the composed
message once with the canonical net-policy redactor (also covers
remote-controlled close-reason text), exported through the plugin SDK
gateway-runtime subpath (+1 documented surface budget). Regression test
covers a credential-bearing URL in both the URL and message fields.
* 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