* refactor(imap): consume the core identifier-authentication scale
Use the SDK type and canonical strength comparator instead of the private
SenderStrength vocabulary and index ordering. Bind config values to the SDK
union with a compile-time exhaustiveness check, preserving all config keys.
Classify unproven From ownership, including no-evidence and DMARC temperror
results, as unverified. Only an explicit unverified minimum gains admission;
the default verified floor and asserted minimum keep their behavior. Keep
existing token admission and thrown-authenticator retries unchanged.
Document the remaining mutable token/early-rejection paths and expose only
the canonical comparator through the SDK, with packaged-consumer coverage.
The IMAP introduction has no stable release contract.
* docs(security): record landed sender-auth mitigations in the threat atlas
Record the graded exact-pair identifier-authentication gate and inert mutable
allowFrom audit findings. Mark R-008 implemented in core while retaining
per-channel verified adoption and downstream mappers as remaining work.
Keep the existing atlas structure and Medium residual risk.
* feat(security): warn on inert mutable allowFrom entries
Report configured entries that cannot authorize with name matching disabled,
and preview lockout counts before disabling mutable matching. Distinguish
mutable aliases from exact identifiers with unproven ownership in audit text.
Reuse channel ingress declarations through one SDK classifier without changing
admission policy. Keep Discord audit findings redacted and exclude symbolic
access-group references from mutable-identifier warnings.
Refs #131099.
* test(security): keep entry-authentication audit coverage in its lanes
* fix(network): release captured streams without waiting on their cleanup
End best-effort reader ownership before guarded request cleanup, while preserving explicit cancellation failures after cleanup. Simplify llama download reader ownership without changing transport abort policy.
Co-authored-by: zhang-guiping <zhang.guiping@xydigit.com>
* fix(network): preserve caller-owned response cleanup
Preserve cancellation for partially consumed media responses, type the captured response handoff, and clarify beta stream cleanup ownership.
* fix(tts): settle stream cancellation with request cleanup
Own binary-stream cleanup in the shared helper and remove duplicate provider release wrappers. Preserve explicit cancellation settlement and errors while allowing cleanup to abort retained capture branches.
Co-authored-by: zhang-guiping <zhang.guiping@xydigit.com>
* test(tts): preserve cancellation result tuple types
Keep both cancellation/release call orders while passing an explicit two-result tuple to Promise.allSettled. Preserve the existing outcome and error-identity assertions without casts or non-null assertions.
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Preserve missing-source and intentional parser-null skips while forwarding other read and JSON parse errors to the existing Doctor warning boundaries. Keep plugin import, archive, and persistence behavior unchanged.
Allow low-memory local-memory setups to install only the verified managed server and embedding model while preserving every configured chat route.
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* feat(channels): add identifier authentication kernel
* fix(channels): preserve exact identity provenance
* fix(channels): fail strict dynamic groups closed
* fix(channels): bind plugin allowlists to exact identity fields
Move the Twitch role normalizers into the kernel branch and keep Discord
name and tag entries on their corresponding identity fields. Repair the
same mismatch for Slack name slugs and IRC nick@host entries.
Add regressions at the channel authorization boundaries.
Related: #123782
Co-authored-by: Omar Shahine <10343873+omarshahine@users.noreply.github.com>
* refactor(channels): resolve identifier authentication at intake
Keep raw SDK inputs optional and carry required authentication through
normalized kernel subjects, entries, and state. Resolve static claims at
intake and floor omitted fields in a supplied per-message map to unverified.
Remove the deprecated internal policy wrapper, downstream strength
fallbacks, and unused type exports. Cover the partial-map floor, static
claims, and mutable alias behavior, and document the map contract.
Related: #123782
* fix(channels): retain affected admission contributions
Preserve an authentication effect when other admission contributions were
not evaluated. Prefer any affected result, then any evaluated result,
before checking for uniformly unevaluated contributions.
Cover the mixed case through the existing redacted receipt flow.
Related: #123782
* test(twitch): await monitor delivery before resetting mocks
Make the ingress fixture drain accepted deliveries on stop, matching the
real monitor lifecycle. Await those deliveries directly so prefix and
reply assertions cannot race or leak completions into the next case.
Related: #123782
* fix(feishu): match ambiguous allow entries under both id fields
---------
Co-authored-by: Omar Shahine <10343873+omarshahine@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix: preserve native session ownership during cleanup and attachment
Prepare permitted harness owners for offline session deletion. Reject direct
attachment of parent-controlled Codex children before displacing ownership.
Keep deadline and client authority through binding publication, and release
accepted subscriptions when publication fails without a remaining owner.
Unify conversation create/attach and preserve observational child monitoring.
Verified 950 focused/sibling tests, production and test types, independent
Codex review, and the real built offline CLI capacity reproduction.
* test: avoid shadowing the native binding row
* feat(plugins): surface plugin capability consent in Control UI and CLI
Adds plugins.inspect (declared manifest surface, operator grants, install
provenance/integrity, ClawHub trust), a Control UI consent dialog on install
and external-plugin enable, a server-side acceptance gate persisted on the
install record, artifact-anchored widen diffing, and --accept-capabilities
for non-interactive CLI use.
NOT READY TO LAND: autoreview found critical gaps (see PR notes) — the
declared surface omits 20 of 21 contract families, native plugins always
report zero hooks, several install/enable paths bypass the gate, and the
acknowledgment is not bound to the reviewed surface.
* refactor(plugins): bind capability consent to the reviewed surface
Collapses the consent error payload to the fields the client cannot fetch
(reviewToken, widened, acceptedAt) and pulls identity/declared/grants/source/
trust from plugins.inspect, shrinking the registry-free protocol reader from
395 to 91 lines and removing its divergence from the closed schema.
Acknowledgment now carries the SHA-256 reviewToken of the surface the operator
saw; the server recomputes the final staged artifact's surface and rejects any
mismatch before persisting acceptance. That closes review-then-swap, laundering
of forged acceptance through an unchanged update, and cross-artifact replay.
All 22 manifest contract families are now declared, hashed and diffed, so a
privileged family such as gatewayMethodDispatch can no longer be added without
re-consent. Consent reads the manifest runtime discovery will execute, ambiguous
install ownership fails closed, integrity resolution has one owner and no longer
labels npm SHA-1 shasums as SHA-256, and code plugins disclose that hooks
register at runtime instead of rendering an empty "no hooks" row.
* fix(gateway): register plugins.inspect in method inventories and regenerate protocol
Adds plugins.inspect to the advertised-method inventories (widening the
fixed-size slice windows so older indices stay stable), regenerates the Kotlin
protocol bindings, drops an unused exported type, and replaces two nested
conditional spreads with a plain conditional.
* refactor(plugins): split oversized consent modules and clear lint findings
Extracts the MCP controller out of the plugins page, unchanged-install
reconciliation out of update-installed, and the install lifecycle suite out of
the management-service tests, bringing all three back under the max-lines limit
without suppressions. Also renames a shadowed binding, drops an unnecessary
generic, removes a spread-to-modify in a map, and types catch callbacks as
unknown.
* chore(protocol): regenerate Kotlin bindings after rebase
* feat(plugins): let chat /plugins install review and accept capabilities
The consent gate applies to chat installs too, but the command had no way to
give consent, so external installs dead-ended on a CLI-only flag. Chat now
replies with the plugin's declared capability surface and the exact command to
rerun, and accepts a trailing --accept-capabilities mirroring the existing
--force acknowledgement. ClawHub trust acknowledgement stays CLI-only.
Staged-artifact verification is unchanged: the reviewToken is still checked
against the final artifact before acceptance is recorded.
* refactor(plugins): single-source the declared-surface groups and manifest precedence
The ordered capability group list was defined independently in the consent
engine, the protocol error reader, the CLI formatter and the Control UI, so a
new contract family had to be added in four places with nothing enforcing it.
All four now derive from one canonical list in the protocol schema with a
compile-time exhaustiveness guard.
Native-versus-bundle manifest precedence is centralized in one helper that both
discovery and staged consent call, so the two cannot drift again — that
divergence was a real bug where consent read one manifest and the runtime
executed another.
Also documents that carrying acceptance forward requires pinned artifact
integrity, so integrity-less sources such as local paths ask on every install.
* fix(plugins): enforce reviewed consent across activation flows
Route setup, repair, linked installs, updates, and chat activation through artifact-bound capability consent. Reuse canonical package discovery and recheck staged activation before config publication. Invalidate stale Control UI review requests on reconnect.
Verified focused owner and sibling tests, runtime rebuild, and real isolated CLI/Gateway install, inspect, enable, widening, and stale-token rejection flows.
* test(plugins): cover beta installs through capability consent
* test(plugins): align consent fixtures with staged artifacts
* fix(ui): review staged plugin capabilities once
* test(ui): inline the remaining plugin consent confirmation
* test(plugins): verify consent with deferred install transactions
* refactor(setup): share inference execution plan construction
* test(ui): settle applied config before deferring refresh
* fix(plugins): protect consent provenance and reuse acceptance
* refactor(state): make cron and subagent rows JSON-canonical
* refactor(state): make gateway origin device tokens canonical at v13
The lazy ensure predates the table joining the canonical schema; at the
v13 bump the schema owns creation, so the feature-local DDL, WeakSet
dedupe, and lazy-list entry retire. The legacy-file guard the ensure
carried stays at each call site.
* test: drop obsolete lazy-ensure coverage for origin device tokens
The table is canonical at v13; same-version lazy creation no longer
exists to protect. Origin CRUD, isolation, and rotation coverage remains
in the surviving cases.
* refactor(state): fold installed_plugin_index into config_machine_state
The singleton index row becomes one JSON value under
plugins.installedIndex with its rollback-fencing revision inside the
value; reads, CAS restore, and the lease-held write transactions use
direct Kysely on config_machine_state so the state_leases assertion
stays in-transaction. The v13 migration imports the row and drops the
table; the additive workspace_dir entry folds with it. Doctor guidance,
docker staging, and the e2e probes name the machine-state row.
* refactor(state): merge workspace_attestations into workspace_setup_state
One row per workspace now carries both setup milestones and the
attestation clock: nullable setup columns represent attestation-only
workspaces (replaceWorkspaceAttestation can precede any setup write) and
setupExists derives from a non-null version. The bootstrap-hash FK
repoints to the merged table; migration receipts keep the historical
workspace_attestations discriminator string. The v13 migration grows and
rebuilds the table, merges attestation rows (orphans without a path
alias drop — their hashes re-derive at the next bootstrap attestation),
and the consolidation kind is renamed state-consolidation-v13 to cover
the batch.
* test(state): cover the workspace merge and consolidation fallout
The v12-to-v13 regression seeds merged, attestation-only, and orphan
attestation workspaces; the 13-to-12 downgrade fixture recreates
workspace_attestations and installed_plugin_index from the folded data;
the fold-in migration gates the additive workspace_dir column for
pre-additive rows; the workspace merge now triggers on the setup table's
own shape so stable-era databases without an attestations table still
reshape; the consolidation applied-message covers the batch.
* refactor(state): fold shared auth profile singletons into config_machine_state
The shared-state auth_profile_stores/auth_profile_state rows (fixed key
'shared') become authProfiles.store/authProfiles.state machine-state
values; the agent-DB tables of the same names are untouched. Git-backup
redaction moves from table-drop to the authProfiles. secret prefix with
seeded-secret absence proof; migration receipts keep the historical
table-name discriminators; the shared-auth relocation and receipt
verification project the KV cells back to the receipt-era row shapes so
persisted digests stay byte-compatible. mcp_oauth_stores stays a table —
its multi-key fold is a named follow-up.
* test(state): finish shared-auth fold coverage and annotate boundary casts
Auth seeders and assertions across the e2e/scripts/secrets suites target
the authProfiles machine-state cells; the v12-to-v13 regression proves
payload-byte fidelity, non-shared-row drop, and insert-if-absent
precedence; the downgrade fixture recreates and repopulates both v12
tables. Boundary type assertions in the plugin-index store carry SAFETY
invariants per the ratchet.
* chore: shrink assertion-safety baseline for plugin-index store
* refactor(doctor): delete the dead onboarding-recommendations migration
Its input — the unscoped 'primary' onboarding row — existed only between
9a93a52a8a and 473962b7de, a two-day beta window; no shipped stable
can produce it and the runtime table folded away at v12. The audit
backup list keeps recognizing system-agent.jsonl artifacts because beta
installs that ran that import may still carry its backups.
* docs: sync the 13-to-12 downgrade example with the executable fixture
* style: format the synced downgrade example
* style: drop unused import and duplicate union constituent
* fix(state): keep orphan attestations across the v13 workspace merge
The merged workspace_setup_state required a workspace path, but legacy
orphan hashed-key attestations never recorded one. workspace_path is now
nullable (setup rows still enforce it via CHECK), the v13 migration and
the doctor file import keep orphans with a NULL path that heals on the
next live access, and the 13-to-12 downgrade keeps attestation-owned
hashes. Doctor test seeds move to the folded KV row.
* perf(state): retire unused cron indexes
* fix(state): preserve v13 migration recovery
* fix(state): preserve v12 lazy-table upgrade
* docs(state): document v13 auth relocation
---------
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
* fix(plugins): surface failed slash command availability
Retain manifest command ownership on the active plugin record after
registration rollback. Reuse the canonical alias resolver and existing
availability states to return an authorized, redacted diagnostic for a
failed plugin's declared runtime slash command.
Keep unknown commands and intentionally disabled plugins on their existing
path. Carry the same registry-bound selection through chat dispatch and
plugin-owned binding resolution, including registry-retirement checks.
Add loader-backed regressions and document the operator-visible behavior.
Release-note context: failed plugin slash commands now identify the plugin
and failure reason and point operators to doctor and gateway logs.
Refs #112248.
* fix(plugins): share command invocation normalization
Use one matcher-owned parser for executable commands and manifest-only availability lookup, including exact-first hyphen and underscore candidates. Failed plugins now return their load diagnostic for both spellings without changing executable argument handling.
Extend the loader-backed regression table for both separator directions while retaining the existing fall-through assertions. Both added cases fail before the repair and pass afterward.
* fix(codex): reclaim binding ownership when sessions are deleted
Prepare harness deletion outside the SQLite writer and compare-delete exact companion ownership at the session removal edge, with compensation before commit and native subscription release afterward. Preserve live siblings and upstream native history. Repair proven orphan bindings through the existing Doctor planner under maintenance ownership.
* fix(codex): reuse prepared auth for resumed thread ownership
* docs(plugins): clarify shared migration and auth contracts
* test(codex): use the canonical config type facade
* fix(codex): preserve native threads during manual resume
Record pending attachment intent and validate the immutable native tool catalog
before applying configuration to the selected thread. Require uninterrupted
physical-client ownership through native unsubscribe, resume, and binding commit.
Keep the thread intact on unsupported policy, competing work, or failed proof.
Reuse canonical retained-owner cleanup and preserve unsafe-client retirement.
Cover the real command producer, cold and loaded resumes, competing leases,
lazy-store commit fencing, and the Gateway competing-owner path.
* test: isolate Codex auth and session completion fixtures
* fix(plugins): fail closed when plugin runtime is unavailable
Core fabricated an empty object as `PluginRuntime` for the `cli-metadata`
and `setup-only` registration passes, so any plugin touching `api.runtime`
during `register()` died with an opaque
`TypeError: Cannot read properties of undefined (reading 'openSyncKeyedStore')`
that named neither the contract nor the mode.
Replace those fabrications with one shared fail-closed runtime that throws a
named, actionable error identifying the plugin and registration mode. Symbol
reads stay inert so inspection and reflection cannot trigger the guard.
Also resolve the lightweight `cli-metadata` entry beside the resolved plugin
entry, not only at the package root. Published plugin packages emit it at
`dist/cli-metadata.js`, so the probe always missed and the loader fell back to
executing the full heavy entry for CLI metadata collection.
Documents which registration modes expose a live `api.runtime`; the SDK
reference previously claimed it was injected into every plugin.
* fix(ui): restore sidebar lazy import boundaries
Remove the redundant viewer-facepile dynamic import: sidebar consumers
already register that element through the static graph.
Import pet data and sprite helpers from their owning leaf modules, delete
the registration module's re-export barrel, and make the element itself
the lobster-pet.runtime.ts lazy boundary. Keep shared sprite styles with
the look renderer so standalone pages do not need element registration.
Regenerate the measured boot inventory with the canonical browser probe.
Use Lit's typed lifecycle map to remove the obsolete assertion baseline
entry and keep the now-private facepile variant type local.
The full build has no INEFFECTIVE_DYNAMIC_IMPORT warnings. Relevant UI
coverage passes (503 tests plus 5 Chromium E2E tests); the unchanged plugin
runtime regression suites pass all 58 tests. The broad plugin suite still
reports schema-version, doctor-closure, hook-process, and catalog failures.
Make ask_user use native Telegram controls when one single-select question can be represented directly, while keeping multi-select and batch questions on the text fallback. Add native Other-to-ForceReply input, preserve Gateway-owned option ordering across Telegram, Slack, and Discord, and prevent later progress from overwriting the delivered question.
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
The bundled imap plugin landed in #130230 without regenerating the plugin
inventory docs, so pnpm plugins:inventory:check failed on main and the plugin
was absent from the reference index. Regenerate, and register its operator
guide in PLUGIN_DOC_ALIASES so the generated reference page links to
/automation/imap (same mechanism codex and firecrawl use).
* feat(approvals): typed approval scope summaries on channel cards
Approval owners can attach a closed ApprovalScope union (message-send,
payment, external-post) describing an action's blast radius. The gateway
sanitizes it once at the producer boundary, the core view model renders a
Scope metadata row so Slack/Discord/Google Chat cards show it unchanged,
shared text builders cover Telegram/WhatsApp/Signal/iMessage/Matrix, and
the durable presentation carries it additively for operator surfaces.
Scope is display-only, never authorization; missing scope keeps today's
cards.
* fix(approvals): emit native ApprovalScope union and clamp recipient previews
Name the three scope variants as registered protocol schemas so the Swift
generator emits the ApprovalScope discriminated union the presentation
structs reference, and commit the regenerated GatewayModels.swift. Clamp
recipient previews to the declared recipientCount at the sanitize boundary
so a count of 1 with 2 previews can no longer render inconsistently.
Addresses both ClawSweeper findings on #130116.
* refactor(approvals): extract text sanitizer to break the exec-approvals import cycle
check:architecture flagged approval-scope joining the exec-approvals SCC
through exec-approval-command-display. Move the self-contained display
sanitizer into a leaf module (exec-approval-text-sanitize) with no
exec-approvals imports and migrate all sanitize importers; command-display
keeps only the payload-typed command/preview resolver.
* chore(plugin-sdk): ratchet public surface budgets down after sanitizer extraction
The approval display sanitizers left the publicly reachable SDK graph when
they moved to the exec-approval-text-sanitize leaf: exports 4343 -> 4338,
callable exports 2582 -> 2578. Shrink-only budget pin.
* feat(a2a): add A2A v1.0 agent-interop channel plugin
Adds `a2a` as a bundled, default-off channel plugin so A2A-compliant agents
(Hermes Agent, LangChain, CrewAI, Google ADK) can discover an OpenClaw gateway
and send it tasks, and so OpenClaw can address configured peers.
Inbound HTTP (`/.well-known/agent-card.json`, `/a2a/v1`) runs through the normal
channel ingress path, so peer messages inherit allowlist admission, provenance
framing, and session routing. The agent reply returns through the channel
delivery callback and resolves the A2A task with its artifact, correlated per
(peer, contextId) FIFO so concurrent sends cannot cross-talk.
Wire format follows the canonical v1.0.0 spec sources (a2aproject/A2A
specification/a2a.proto): PascalCase SendMessage/GetTask/CancelTask,
supportedInterfaces[] rather than the 0.3-era top-level url/protocolVersion,
member-presence Part oneof, TASK_STATE_*/ROLE_* enums, no Task.kind. The 0.3
dotted method names are accepted as explicit compat aliases because shipped
Hermes-generation peers still send them.
Security: per-peer bearer tokens compared in constant time, per-peer sliding
window rate limit, 1 MiB body cap, 64 KiB inbound text cap, tasks scoped to
their owning peer, outbound redirect: "error", and no inbound-controlled target
URLs. With no peers configured the channel stays unconfigured and registers no
routes.
Live-verified against a real gateway on an isolated state dir with a mock
provider: 31/31 proofs, including the agent reply round-tripping into the task
artifact. That run caught two defects fixed here:
- The agent card read `cfg.agents.list` directly, so gateways configured with
the canonical `agents.entries` roster advertised zero skills. Now uses
listAgentIds/resolveAgentConfig, which read either roster shape.
- `returnImmediately` dispatches inherited the HTTP request's released work-
admission root and every async task failed as GatewayDrainingError. Now
reserves an independent root via runDetachedWebhookWork, matching sms/line/zalo.
* fix(a2a): route outbound sends through the SSRF guard and land setup metadata
CI on the first push surfaced four real defects that local `check:changed` did
not cover (it runs no oxlint or typecheck lane):
- Outbound peer sends used raw `fetch()`, tripping the channel/plugin
no-raw-fetch boundary. They now go through `fetchWithSsrFGuard` with
`maxRedirects: 0`, so A2A egress carries the same SSRF policy as every other
plugin call and a redirect cannot re-target a delivered task.
- The channel declared an empty setup contract while its package manifest
declared none, so the bundled-channel shape contract failed. The wizard now
collects `advertisedUrl` plus a first peer name/token pair, which is the
minimum that leaves A2A actually configured (it stays unconfigured until one
peer credential exists), and the package manifest mirrors that metadata.
- Six lint errors in the test files: base-to-string on `BodyInit` bodies, an
unused mock, and a shadowed `options` parameter. Request bodies now go through
a typed reader that asserts the serialized-string contract before parsing.
- A card assertion referenced `capabilities` without declaring it on the local
response type, failing test typecheck.
Outbound redirect handling moves from `redirect: "error"` to the guard's
`manual` inspection; the adapter tests assert the new shape.
* fix(a2a): drop route replaceExisting, accept configWrites, register runtime api
CI after the interop run surfaced four more gates:
- `registerPluginHttpRoute` used `replaceExisting: true`, tripping the
GHSA-RQP8-Q22P-5J9Q rule: a duplicate path can silently replace another
account's handler. A2A owns fixed global paths on a single account, so a
duplicate registration means a stale or conflicting owner. It now fails loudly
through `throwOnFailure` instead.
- The channel schema rejected `channels.a2a.configWrites`, which every bundled
channel must accept; added to the zod schema, the manifest schema, and the
config type, then regenerated the channel metadata.
- The new `input` assertion in the setup adapter needed a `// SAFETY:` line for
the assertion ratchet.
- `extensions/a2a/runtime-api.ts` needed registering in the runtime-api
classification list, like every other bundled channel barrel.
Also repairs a pre-existing incomplete `vi.mock` factory for `../infra/fs-safe.js`
in the agent-delete suites. Both pass in isolation, but the shared-worker lane
lets a sibling importer reach `readLocalFileSafely`, which the partial factory
never exported; adding this channel changed shard composition and surfaced it.
The factory now spreads the real module and overrides only `movePathToTrash`.
* chore(a2a): refresh config baseline for the configWrites surface
* chore(tests): adopt main's fs-safe mock repair for the agent-delete suites
Main landed the same importOriginal spread for the shared-worker mock-defeat
while this branch carried an equivalent fix; take main's version verbatim so the
two do not conflict.
* fix(a2a): isolate peer sessions and stop faking task cancellation
Addresses both ClawSweeper P1 findings.
Peer sessions: inbound routing never passed `dmScope`, so it fell back to
`session.dmScope ?? "main"` and every authenticated peer converged on
`agent:main:main` under default config. Untrusted remote content was joining the
operator's own session, and peers shared conversation history. A2A now pins
`per-account-channel-peer`; the peer id already embeds the A2A contextId, so each
peer+context pair gets its own session regardless of global session config.
Cancellation: `CancelTask` marked the task `TASK_STATE_CANCELED` and discarded
the late reply, but the dispatched agent run kept going and could still use
tools. The peer was told the work stopped when it had not. There is no
plugin-facing seam to abort a live run, so cancellation is refused with -32004
instead of acknowledged, and the store's cancel path plus its canceled-delivery
tombstone are deleted rather than left as a lying terminal state. Documented as
an explicit limitation.
Live-verified on a real gateway: 34/34 proofs, including three distinct
`agent:main:a2a:default:direct:<peer>:<context>` sessions with zero traffic in
`agent:main:main`, and both cancel spellings refused while the task stays
WORKING. Re-ran the official a2a-sdk 1.1.2 Docker interop afterwards: 9/9.
The detached Skill Workshop experience review rebuilt its system prompt and tool catalog from a different context than the foreground turn, so every review missed the prompt cache. Native harnesses (embedded, Codex, Copilot) now hand the review the same foreground prompt context via buildEmbeddedForegroundPromptContext; the review reuses the foreground prefix and gates execution to skill_workshop while keeping the catalog identical. Reviews without a foreground prompt (CLI hook contexts) are skipped.
* fix(cli-runner): drop stock watchdog defaults from CLI backend descriptors (#125045)
The claude-cli and gemini-cli backend descriptors spread
CLI_FRESH/RESUME_WATCHDOG_DEFAULTS into config.reliability.watchdog.
Those copies are byte-identical to the fallback pickWatchdogProfile
already uses, so shipping them only makes `configured` always truthy,
which permanently disables the promotion gate (!configured) and pins
resumed cron/explicit-timeout turns to the 180s resume no-output ceiling
instead of the 600s fresh ceiling.
Removing the blocks lets `configured` be undefined, restoring the
promotion gate. No user-config path is affected: reliability.watchdog
is not exposed in the config schema, the descriptor is the sole source,
and pickWatchdogProfile is the only runtime reader. The codex backend
never shipped the block, so this aligns the three backends.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(cli-runner): add descriptor-through-resolver watchdog proof (#125045)
Drive the real shipped Anthropic/Gemini CLI backend descriptor config through
the production resolveCliNoOutputTimeoutMs resolver to prove the resumed-cron
promotion is live post-fix. Pre-fix the descriptor shipped byte-identical
resume defaults, so configured was truthy and the !configured promotion gate
was dead, pinning resumed cron turns to the 180s resume ceiling (180000).
Post-fix the block is gone and the resolver returns the promoted fresh budget
(480000 = 600000 * 0.8).
resolveCliNoOutputTimeoutMs is re-exported through the test-only
openclaw/plugin-sdk/test-fixtures subpath (excluded from published dist), so
extension tests can drive the real descriptor through the real resolver without
crossing the extension/core boundary.
Co-Authored-By: Claude <noreply@anthropic.com>
* test(anthropic): split watchdog promotion proof into its own file
Move the descriptor-shape and descriptor→resolver real-behavior proof
tests out of cli-shared.test.ts into cli-watchdog-promotion.test.ts.
cli-shared.test.ts exceeded the extensions max-lines budget (1000
non-blank/non-comment lines) once the proof tests landed; the focused
file keeps the same test names and coverage while staying under the
budget. No production change.
Co-Authored-By: Claude <noreply@anthropic.com>
* fix(cli-runner): restore inherited resume watchdog promotion
Validate bundled plugin descriptors through their public setup entries and keep custom watchdog contracts intact.
Co-authored-by: ruel225 <ruel225@users.noreply.github.com>
* chore(cli-runner): align conflicting provider owner with main
Co-authored-by: ruel225 <ruel225@users.noreply.github.com>
* fix(cli-runner): complete inherited resume watchdog promotion
Co-authored-by: ruel225 <ruel225@users.noreply.github.com>
* test(cli): keep watchdog regressions in their owning plugins
Keep Anthropic and Google descriptor assertions inside their plugin-owned registration suites, preserve generic resumed/custom watchdog coverage in its core owner, and remove the cross-plugin core test rejected by architecture CI.
Co-authored-by: ruel225 <ruel225@users.noreply.github.com>
---------
Co-authored-by: ruel225 <ruel225@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>