* fix(channels): validate headless channel setup
* docs(channels): document headless provisioning
* fix(channels): repair setup metadata typing
* chore(channels): regenerate official channel catalog for env metadata
* fix(slack): keep mode-conditional env contract plugin-owned
Static --use-env declaration keeps only the unconditional SLACK_BOT_TOKEN;
socket-vs-HTTP conditional requirements (app token, signing secret) stay in
Slack's own setup validation so HTTP mode no longer demands an irrelevant
SLACK_APP_TOKEN.
* chore(sdk): regenerate api baselines and catalog after rebase
* fix(slack): align manifest env declaration with runtime contract
* chore(sdk): regenerate api baselines after rebase
* chore(sdk): regenerate api baselines after rebase
* chore(sdk): regenerate api baselines after rebase
* refactor: consolidate coercion ownership
Centralize four canonical coercion helpers, migrate exact core and plugin duplicates through narrow Plugin SDK facades, and enforce declaration and plugin-normalization ownership boundaries.
The sweep adds eight focused SDK exports while deleting more production and tooling code than it adds. User-visible behavior is unchanged except for safer equivalent object and UI parsing at existing boundaries.
* fix: guard integer option ownership
Register resolveIntegerOption with the canonical function owner and extend the declaration-guard fixture so future local duplicates fail validation.
* fix: keep integer helpers on numeric facade
Remove the unshipped duplicate string-coerce exports and route every affected plugin consumer through the existing number-runtime contract.
* fix: point numeric coercion to number runtime
Make boundary and declaration diagnostics recommend the canonical numeric facade, with failing-before coverage for both guidance paths.
Unset heartbeat.target now resolves "owner": elected heartbeat notifications deliver to the operator's DM resolved from commands.ownerAllowFrom or the channel allowFrom (first concrete entry; wildcards and channel-scoped wildcards excluded; configured owners exhausted across channels before any channel-local fallback). Delivery requires the channel's own classifier to positively prove a direct destination — every bundled messaging plugin now ships an inferTargetChatType contract — and unproven or group-shaped destinations fail closed to the visible no-route state. The first implicitly-routed delivery carries a one-line self-explanation naming the target: "none" opt-out. Explicit target "last" remains as the follow-the-conversation opt-in. Refines the unreleased #121892 default before it ships; refs #121880.
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
A final reply whose platform send was accepted but whose response was lost
previously ended in silence. Custody that stays unknown after a claimed send
now records durable pendingDeliveryNotice debt; the next same-route turn
delivers one "could not confirm delivery" notice and acknowledges it into the
transcript. Restart recovery completes ambiguous sessions with the same debt
instead of a fire-and-forget notice; the debt survives reset and rollover, and
suppressed notice sends retain it instead of faking delivery. Permanent typed
no-send rejections settle as terminal suppression (no replay, no false
notice); retryable ones restore prepared custody for safe replay. Google Chat
media-only rejections use the typed no-send contract; Telegram native-command
replies join pending-final custody.
Fixes#80362
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Native approval delivery and resolution now stay bound to the originating or explicitly targeted channel account. Unbound requests fail closed across multiple eligible accounts; trusted reviewer-less SDK callers remain compatible.
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
* perf(doctor): keep bundled doctor contract closures dependency-light
Doctor contract enumeration cold-loads each plugin's doctor-contract-api
closure via jiti, so a static value import of openclaw/plugin-sdk/runtime-doctor
pulled the state-db/kysely graph (~4.3s per closure) into
listPluginDoctorLegacyConfigRules / listPluginDoctorStateMigrationEntries.
- migrate all light doctor-contract closures (66 files) to the
dependency-light openclaw/plugin-sdk/runtime-doctor-migrations subpath
- voice-call: load detect/repairOpenClawStateDatabaseSchema* lazily inside
the migration bodies; keep only a type-only static runtime-doctor import
- matrix: split pure credential record shapes/normalizers into
credentials-state.ts so the doctor closure no longer imports the sync
plugin-state store through credentials-read
- guard: doctor-contract-closure-guard.test.ts now forbids static value
imports of runtime-doctor in closures alongside agent-runtime
* fix(matrix): keep credential revocation record type module-local
Knip production scan flags the export as consumer-less; the type is only
referenced by the exported union and revocation guard signature.
* fix(googlechat): align doctor policy ownership with canonical account configuration
* test(googlechat): align generated channel metadata and typed doctor fixtures
An unparseable webhookUrl resolves to no path, and the monitor bails before it
creates ingress or registers the HTTP route. The gateway still published the
default path, and because this channel never sets connected, health evaluation
read healthy for the lifetime of the process while inbound delivery was dead.
Make resolveGoogleChatWebhookPath return string | null and have the gateway
publish lifecycle: blocked with lastError instead of a path. The status store
patch-merges, so the blocked branch clears webhookPath explicitly.
* refactor(ingress): consolidate standard raw-event monitors
* fix(ingress): preserve monitor callback typing
* fix(ingress): keep standard profile within line limits
* chore(plugin-sdk): regenerate API baseline for shared ingress profile
Reply routing can hand sendGoogleChatMessage a `thread` that is not a
valid `spaces/{space}/threads/{thread}` resource name — a bare id, a
`spaces/{space}/messages/{message}` name, or a thread from a different
(or wrongly-cased) space. The Chat API rejects the whole request with
`400 INVALID_ARGUMENT`, so the reply is never delivered even though the
agent already produced it (and did any side effects).
Guard the send so the `thread` field and the messageReplyOption fallback
are applied only when the thread is a well-formed name belonging to the
target space; otherwise post to the space as a new thread. Revives the
approach of #28153 (auto-closed as stale) and addresses the failure mode
behind #64313.
The size guard in readGoogleAuthResponseBytes inspects content-length and
throws before it reaches response.body.getReader(), so an oversized auth
response leaves the stream untouched and the finally block releases the
dispatcher without cancelling it.
This is the same shape #111290 just fixed in api.ts, applied to the auth
transport's own guard site.
* feat(channels): add channel-owned setup contracts
* test(channels): align legacy setup fixtures
* chore(channels): regenerate config and SDK baselines after rebase
* fix(update): run fresh doctor after current-process core changes
* fix(channels): align add pre-scan with execution precedence
* style(cli): format channels-cli test additions
* fix(channels): restore option-before-positional channel resolution via metadata arity scan
* fix(channels): keep help flags out of metadata arity escalation
* test(update): mock fresh post-update doctor in current-process suites
* style: format review fixes and correct entrypoint mock type
* fix(channels): register only modern contract options for dual-publishing plugins
* test(update): align downgrade suites with fresh-doctor child invocation
* docs(channels): record empty-contract and input-forwarding invariants
* fix(line): keep the shipped --token switch as a channel access token alias
* fix(signal): stop treating exact cross-family loopback endpoints as bind-aligned
* chore(config): regenerate docs config baselines after second rebase
* style: format rebased channels add tests
* fix(channels): enforce field-key and flag-name agreement in setup contracts
* fix(signal): detect container endpoints for bare --http-url setup
* fix(signal): ignore unconfigured accounts in transport collision checks
* fix(channels): validate negated setup flags in contract and normalizer
* fix(signal): preserve existing transport kind when setup detection is unreachable
* style(signal): use direct boolean check in collision guard
* style(signal): type test config literals
* docs(update): record two-read design of fresh-doctor validation gate
* fix(channels): satisfy post-rebase architecture gates
* docs: refresh channel setup map
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>