* refactor(channels): generalize channel retry runner and retry-after parsing; drop dead configured-state probes
* fix(channels): align retry and metadata contracts
* fix(line): run post-ack webhook processing on its own admitted work root
LINE acks the webhook and dispatches event processing fire-and-forget on
the same async chain. The HTTP request admission that chain inherited is
released as soon as the route handler returns, and a released admission
refuses subordinate queue work - so every LINE inbound agent turn fails
with "GatewayDrainingError: Gateway is draining; new tasks are not
accepted" even though the gateway is healthy. DMs, group mentions, and
postbacks are all affected; the user-visible symptom is the bot replying
"Sorry, I encountered an error processing your message." to everything.
Add runDetachedWebhookWork to the plugin-sdk webhook-request-guards
surface (a thin wrapper over the gateway independent-root continuation,
the same shape core uses in gateway/server/hooks.ts) and route all three
LINE ack-first dispatch sites through it: the gateway monitor handler
(the live path), and the createLineNodeWebhookHandler / Express
middleware handlers (public webhook building blocks an embedder can
register under the gateway). #65375 unified these three into one ack-first
pattern; keeping the detach consistent avoids re-introducing the same
latent defect in the two that are not on the live gateway path today.
The continuation is reserved synchronously while the request is still
admitted, so the detached processing stays accepted and a real restart
drain can wait for it instead of stranding it mid-turn.
Tests pin every layer: the guards suite proves detached post-ack work is
admitted after the request admission is released (and that the inherited
chain without the helper is refused); the monitor lifecycle suite and the
webhook-node suite assert each dispatch site goes through the detached
root. Red/green verified: reverting any dispatch fails its test.
* fix(plugin-sdk): account for runDetachedWebhookWork in public surface budget
* fix(channels): track detached webhook processing
Co-authored-by: 許元豪 <146086744+edenfunf@users.noreply.github.com>
* docs: refresh generated docs map
* chore(plugin-sdk): refresh API baseline
* fix(webhooks): preserve post-ack ordering
* test(plugin-sdk): satisfy detached work lint
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* refactor(plugin-sdk): narrow wildcard barrels to explicit used exports
* refactor(tools): delete dead tool-planning module exposed by barrel narrowing
* fix(plugin-sdk): restore deprecation tag on OpenClawSchemaType alias
* test(agents): drop test for deleted runtime proxy module
* refactor(tools): trim descriptor types to cache consumers
* refactor(deadcode): harvest exports orphaned by barrel narrowing
* refactor(deadcode): harvest exports orphaned by barrel narrowing (rest)
* fix(agents): restore sdk imports and test markers via public predicate
* fix(plugin-sdk): named type re-exports in plugin-entry; trim types barrel precisely
* chore(plugin-sdk): account unmasked deprecated provider types in budgets
* fix(plugins): name star-only type rows for dts bundling
* fix(plugins): restore host-hook surface; unexport internal api compositions
* fix(plugins): named type imports for api composition; restore needed source exports
* fix(plugins): knip-visible type imports for registry surfaces
* test: adapt tests to privatized media and command internals
* fix(qa-lab): re-export snapshot conversation type
* style: format sessions sdk imports
* fix(plugins): restore smoke entry export; pin budgets to exact actuals
* fix(plugins): canonical smoke-entry import; drop orphaned root shims
* fix(plugins): allowlist manifest probe, repoint qa web import, drop dead browser barrels
* fix(plugin-sdk): pin codex auth marker and scaffold provider type
* fix(qa-lab): keep web-facing model-selection shim within boundary rules
* fix(plugin-sdk): preserve merged contracts through narrowed barrels
* chore(plugin-sdk): pin post-rebase surface budgets
Adds a Discord Activities integration so an agent can show a self-contained
HTML widget to Discord users, opened as a sandboxed Activity inside the client.
Off by default: routes, the discord_widget tool, and the launch handler register
only when channels.discord.activities is configured. OAuth identifies the user
and gates on the account allowlist; widget lookup is capability- or
instance-validated; token exchange is rate-limited; widget HTML runs in a
no-network sandboxed iframe.
* refactor(plugin-sdk)!: delete the zero-consumer channel-ingress facade, AccessFacts projections, and dead dispatch aliases
* chore(plugin-sdk): refresh API baseline hash for the facade deletions
* fix(channels): drop dead AccessFacts field writes stranded by the facade deletion
* chore(plugin-sdk): refresh API baseline hash after rebase
* refactor(channels): converge internal group-policy drift onto the scope tree
* chore(plugin-sdk): refresh API baseline hash for scope-key exports
* fix(config): avoid shadowing the scopeKey export in the case-insensitive lookup
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.
Catalog session rows (sidebar context menu + click), the built-in viewer
header, and a new "Open Codex/Claude sessions in" preference can launch the
native CLI (codex resume / claude --resume) in the operator terminal on the
machine that owns the session.
- Gateway-local sessions spawn through the existing terminal launch policy
(sandbox/enabled gates preserved) with the resume command in the session cwd.
- Paired-node sessions run through a new seq-ordered node PTY relay: a
duplex node-host command streams PTY output via node.invoke.progress and
receives keystrokes/resize via a new node.invoke.input event, behind the
unchanged terminal.* client protocol (TerminalSessionManager gains a backend
abstraction; node relay reuses the streaming-invoke controller).
- Owner boundary: each plugin owns its resume command and builds argv from a
validated thread id; the gateway routes node opens through the node command
allowlist and plugin invoke policy (no advertisement-only trust), and nodes
re-verify session eligibility before spawning.
- UI setting catalogOpenTarget + canOpenTerminal capability gate every entry
point; capability requires the owning host to actually have the CLI.
Node PATH is normalized before command-availability probes, Windows .cmd/.bat
shims spawn via ComSpec, and catalog terminal opens reattach persisted tabs
before opening the new tab.
* fix(channels): time-gate progress drafts and defer narration until visible
Progress drafts started immediately on the second work event, so the 5s
initial delay never filtered small fast tasks and Discord showed narrated
status for turns that finish in seconds. The gate is now purely time-based
(explicit startNow paths like approvals/patches are unchanged), and the
progress narrator no longer spends utility-model completions before the
channel draft is visible - notes buffer so the first visible narration has
full context.
Surface: src/channels/streaming.ts gate, src/auto-reply/reply/progress-narrator.ts,
Discord draft-preview wiring; test updates across discord/matrix/msteams/slack.
Refs #105872
* feat(channels): model-preamble status headline for progress drafts
In progress mode the draft's status line is now the model's own typed pre-tool
preamble whenever it is fresh (always on, no new config); utility-model
narration fills the slot only when the model has been quiet for ~20s or emits
no preamble. Preambles never start the draft early - the time gate still owns
the small-vs-big decision - and narration generation is suppressed while a
fresh preamble exists, saving utility calls. Discord and Telegram render the
headline; the opt-in streaming.progress.commentary lane and its receipt
counters are unchanged.
Refs #105872
* refactor(channels): split status-text and telegram preview helpers to honor the LOC ratchet
The LOC ratchet forbids raising baseline entries, so the headline feature's
growth is paid for with clean splits: reasoning/commentary text normalization
moves to src/channels/progress-draft-status-text.ts, Telegram progress preview
rendering to extensions/telegram/src/progress-draft-preview.ts, and small
ownership moves (formatCompactionModelRef, resolveFollowupAbortSignal, shared
CLI preamble gate, Discord preamble item-event handling) shrink the runners
back under their pins. Baseline entries lowered to exact new counts; the
compositor entry is removed (424 lines, under the 500 ceiling).
Refs #105872
* test(auto-reply): add resolveFollowupAbortSignal to the queue mock factory
The explicit ./queue.js vi.mock factory must export every binding prod touches;
the relocated resolveFollowupAbortSignal was missing and failed the whole
followup-runner suite.
Refs #105872
* fix(channels): sanitize preamble headlines and retry skipped narration
Review findings: preamble headlines now strip inline delivery directives and
reject silent-control tokens (NO_REPLY) before rendering, sharing the
commentary sanitizer; rejected input keeps the previous valid headline. The
narrator schedules cancellation-aware retries when a run is skipped for draft
invisibility or preamble freshness, so single long tool calls still narrate
and the ~20s utility filler fires without needing another event. Silent
preambles no longer stamp freshness or add narration notes.
Refs #105872
* fix(channels): bound narrator visibility polling and gate Telegram headline mode on accepted preambles
Visibility retries stop after 30 one-second attempts (turn completion has no
narrator disposal signal) and re-arm on new activity; Telegram derives its
headline-rendering state from the compositor's sanitized headline instead of
raw payload text, so directive-only or NO_REPLY preambles keep structured
tool-line rendering. Docs scope the preamble headline to Discord and Telegram.
Refs #105872
* fix(telegram): read headline state from the compositor at render time
The progressHeadlineActive mirror flag was set after pushPreambleHeadline had
already rendered, so the first valid preamble arriving after the draft opened
rendered through the structured-line path and the headline never appeared
until the next event. The renderer and formatLine now read the compositor's
sanitized hasStatusHeadline directly and the mirror flag is deleted; adds an
ordering regression test. LOC baseline lowered to the exact new count.
Refs #105872
* fix(channels): let the opt-in commentary lane own preambles and scope narration docs to Discord
With streaming.progress.commentary enabled, the status headline would replace
the documented interleaved 💬 lines for any turn with a preamble (the exact
event the lane consumes). The compositor headline now declines when the
commentary lane is enabled, preserving that lane's shipped shape, and docs
state the utility-model filler is Discord-only today (Telegram's headline is
preamble-only; it has no narrator wiring).
Refs #105872
* fix(msteams): cancel the pending progress gate at finalize
With the purely time-based gate, every fast Teams progress turn leaves a
pending 5s start timer; the SDK resets its stream id on close, so a late
timer fired renderInformativeUpdate against the closed stream and posted a
stale working card below the final answer. finalize() now cancels the gate
and the informative renderer refuses to run once final text is queued.
Refs #105872
* docs(channels): record the headline-vs-commentary ownership decision inline
Refs #105872
* style: oxfmt the late review-fix rounds
Four files from the final review-fix commits were never oxfmt-formatted and
failed the shared CI formatting step; baseline lowered for the one-line
telegram shrink.
Refs #105872
* fix(channels): harden progress draft lifecycle
* fix(channels): refresh repeated preamble items
* fix(plugin-sdk): expose progress lifecycle options
* style(auto-reply): simplify narrator reset
* docs(channels): refresh progress draft docs map
* fix(auto-reply): clear queued narrator request context
* fix(discord): cancel delayed progress gate at final
* docs: refresh plugin sdk api baseline
* feat(config): add canonical group-policy scope-tree resolver
* chore(plugin-sdk): refresh API baseline hash for scope-tree exports
* refactor(channels): migrate googlechat, imessage, and whatsapp group policy onto the scope tree
* chore(plugin-sdk): refresh API baseline hash for groups scope-tree builder
* refactor(channels): migrate line, qqbot, and mattermost group policy onto the scope tree
* chore(plugin-sdk): refresh API baseline hash for case-insensitive scope key helper
* chore(plugin-sdk): refresh API baseline hash after rebase onto current main
* chore(plugin-sdk): refresh API baseline hash after rebase
* chore(plugin-sdk): refresh API baseline hash after rebase
* chore(plugin-sdk): refresh API baseline hash after rebase
* refactor(channels): consolidate the flat streaming fallback into the deprecation module
* chore(plugin-sdk): refresh API baseline hash for compat module move
* chore: re-attach CI to the rescoped head
* chore(plugin-sdk): refresh API baseline hash after rebase
* chore(plugin-sdk): refresh API baseline hash after rebase