* 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>
* feat(line): wire base.allowlist config-edit adapter
LINE was the only DM/group channel without a base.allowlist adapter, so
`openclaw allow line` / the /allowlist command replied "does not support"
instead of editing the LINE allowlist. Reuse buildDmGroupAccountAllowlistAdapter
(DM + group + per-group-override scopes), matching Telegram/Signal; entry
normalization flows through the existing lineConfigAdapter.formatAllowFrom.
* fix(allowlist): preserve inherited account entries
* fix(allowlist): preserve all-scope store edits
Co-authored-by: Eden <146086744+edenfunf@users.noreply.github.com>
* test(allowlist): import config type
Co-authored-by: Eden <146086744+edenfunf@users.noreply.github.com>
* fix(allowlist): preserve empty effective overrides
Co-authored-by: Eden <146086744+edenfunf@users.noreply.github.com>
* fix(allowlist): reject group-only store edits
Co-authored-by: Eden <146086744+edenfunf@users.noreply.github.com>
* fix(allowlist): preserve cleared channel overrides
Co-authored-by: Eden <146086744+edenfunf@users.noreply.github.com>
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(embedded-runner): clear tool-search catalog when a run aborts during prep
* fix(embedded-runner): point catalog test at relocated tool-search test api
* perf(ci): scale Vitest workers with runner class and share serial module cache
Compact node-test jobs pinned OPENCLAW_VITEST_MAX_WORKERS=2 regardless of
runner size, so import-bound suites (30-45s of module-graph import per
file) crawled: runner-cli spent 284s on 5s of tests, commands-1 296s on
10s. With serial plans the budget now scales with the class (6 on 8vcpu,
3 on 4vcpu); timing-sensitive groups (tooling, tui-pty, infra-process)
stay pinned to 2 via plan-level env. Serial bins share one Vitest
fs-module-cache path so later plans reuse the first plan's transforms.
The import-bound commands stripes and security suite move to the 8vcpu
class, and packing hints refresh from serial-run measurements (run
29481835688).
* perf(ci): stripe cli-runner suite and pin media-ui worker budget
The agents-core config runs files serially (fileParallelism false guards
shared module state), so raising the worker budget cannot help its
import-heavy cli-runner suite (~35s module import per file, 213s serial).
Stripe it three ways so bins parallelize the imports instead. media-ui
hosts browser locator tests that timed out at 6 workers on the first
validation run; pin it to the proven 2-worker budget. commands-1's hint
drops to its measured 6-worker runtime.
* fix(memory-lancedb): gate auto-recall and auto-capture on per-agent memorySearch.enabled (#103590)
The before_prompt_build auto-recall hook only checked the plugin-level
autoRecall flag, so agents configured with memorySearch.enabled: false
still received <relevant-memories> injected from the shared LanceDB store
- leaking one agent's private memories into another agent's prompts. Gate
both recall injection and agent_end auto-capture on the current agent's
memorySearch.enabled (per-agent entry wins over agents.defaults; unset
means enabled), mirroring core resolveMemorySearchConfig semantics.
* fix(memory-lancedb): normalize agent ids before the memorySearch gate
Review follow-up on #103799: a configured id like 'XiaoHuo' or one with
surrounding whitespace missed the exact-match per-agent override and
inherited the enabled default, leaving the disclosure path active.
Normalize both the hook agent id and configured entry ids with the SDK
normalizeAgentId before comparing.
* fix(memory-lancedb): resolve the per-agent memorySearch gate via resolveAgentConfig
* fix(memory): isolate LanceDB rows by agent
Co-authored-by: Shubhankar Tripathy <reach2shubhankar@gmail.com>
* fix(memory): isolate LanceDB rows by agent
Co-authored-by: Shubhankar Tripathy <reach2shubhankar@gmail.com>
* refactor(memory): keep LanceDB store types private
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(line): avoid exposing media URLs in errors
* test(line): cover media URL redaction paths
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(sessions): complete tail-read windows despite short positional reads
Replace single-shot handle.read() calls in two session tail-read
paths with readFileWindowFully so multibyte-range positional reads
do not silently return incomplete tail data on network filesystems.
- readRecentTranscriptTailLinesAsync: tail-window read for recent
session messages now loops until the requested window fills
- readLastMessagePreviewFromOpenTranscriptAsync: last-message
preview tail read now loops until the requested window fills
The same readFileWindowFully helper was introduced in #108253 and
expanded with a sync variant in #108127 (both by sunlit-deng).
* fix(sessions): complete sync title preview reads
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Ports the unique coverage from #108126 onto the parser landed via #106850:
wrapped 401/403/404 and quota-exhausted 429 stay non-retryable, an
envelope embedded in user text is ignored, and a short-window wrapped
429 retries. Credit @zhanxingxin1998.
* fix: normalize provider keys during model config merge
* fix: normalize source-managed provider keys when reapplying secret markers
Make enforceSourceManagedProviderSecrets canonicalize provider keys with
normalizeProviderId so mixed-case (e.g. "OpenAI") source config still matches
the canonical "openai" generated provider. Without this, the trim-only source
lookup misses, the source SecretRef marker is never reapplied, and resolved
runtime secret values leak into generated models.json.
Adds plan-level regressions in models-config.runtime-source-snapshot.test.ts
covering mixed-case apiKey and header source configs.
* fix: use Array#toSorted in mixed-case provider regression tests
oxlint(no-array-sort) flagged the new test assertions for using
.sort() on Object.keys(...). Switch to .toSorted() to keep the
check-lint shard green.
* style(agents): oxfmt models-config provider merge files
* fix(agents): define provider key collision precedence
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: Peter Steinberger <peter@steipete.me>
* feat(mac): playful hard-hat working mascot during setup and updates
The mascot gets a new .working mood: it dons a safety hard hat, hammers
away with impact sparks while setup work is in flight, and pauses every
few seconds to wipe its brow. Used on the onboarding "Getting things
ready" page and the post-update window instead of the generic thinking
mood. Lives in shared OpenClawKit so iOS can adopt it too.
* chore(i18n): refresh native inventory line for mascot view
channel.test.ts crossed the max-lines lint budget (1002 > 1000) after
#108584; move the mock-free bindings/security/groups describes into
channel.policies.test.ts instead of suppressing.
The canonical schema gate rejects non-STRICT tables, failing every
gateway-suite shard since #108543 added the tombstones table without
STRICT. Align it with every sibling table and regenerate the schema
mirror.
* fix(android): stop killing long chat runs with hardcoded timeouts
Two independent timeouts made any agent turn longer than ~2 minutes fail
when started from the Android app, while the same turn worked over
Telegram (#106863):
- chat.send hardcoded timeoutMs=30000, which the gateway turns into the
server-side run expiry; the maintenance sweep then aborts longer runs
("CLI run aborted" / "Embedded agent failed before reply"). Omit the
override so app runs get the configured default like other channels.
- armPendingRunTimeout declared a timeout after 120s even when the
refreshed history snapshot confirmed the run was still in flight,
showing a spurious error and dropping the optimistic bubble mid-run.
Re-arm the timer in that case; terminal events and the server-side
expiry remain the liveness backstop.
Fixes#106863
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* chore(i18n): sync native inventory after ChatController line shifts
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(android): run ChatController on backgroundScope in reconnect tests
The pending-run watchdog now re-arms while a run is confirmed in flight,
so reconnect scenarios that end with a restored in-flight run keep a
timer coroutine alive. Launch the controller on runTest's
backgroundScope so that timer is cancelled at test end instead of
failing the suite with UncompletedCoroutinesError.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(android): bound pending-run watchdog recovery
Distinguish applied, superseded, and failed history refreshes so recovered runs neither expire during a newer authoritative load nor rearm forever after refresh failure.
Co-authored-by: Léandre Chamberland-Dozois <lchamberland-dozois@hotmail.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>