The OpenRouter /models catalog read in fetchOpenRouterModels hardened only
the error/early-return path (dbd5689 cancels the body when res.bodyUsed is
false), but the success branch still buffered the whole body with an
unbounded `await res.json()`. The response is a provider-controlled,
runtime-fetched body, so a faulty or hostile provider can stream an
effectively unbounded JSON document and exhaust process memory before the
parse completes; the finally-cancel is a no-op once .json() has drained.
Read the success body through the canonical byte-cap reader
(readResponseWithLimit) under a 4 MiB ceiling before JSON.parse, cancelling
the stream on overflow and bounding idle stalls with the call's existing
timeout. This is the symmetric success-path counterpart to the bounded-stream
hardening landed in #95103 (pricing catalog) and #95108 (Anthropic error
streams), reusing the same helper rather than a new abstraction.
* fix(agents): bound OpenRouter model catalog response reads
The runtime OpenRouter model-capability detector fetched the full
/models catalog with an unbounded `await response.json()`, so a
compromised or misbehaving endpoint could stream an arbitrarily large
body and force the process to buffer the whole payload before parsing.
Read the body through the shared bounded reader instead, capping it at
16 MiB (matching the sibling pricing-cache endpoint hardened in #95103)
and cancelling the stream on overflow. This mirrors the symmetric
bound-stream fixes in #95103 and #95108.
Adds coverage that an oversized streamed catalog is cancelled instead of
buffered and that an under-cap chunked body still reassembles, parses,
and round-trips through the SQLite cache on a fresh import.
* fix(agents): avoid OpenRouter refetch after capped catalog miss
---------
Co-authored-by: sallyom <somalley@redhat.com>
Make QA Profile Evidence failure handling explicit for direct and reusable callers. Direct manual runs still fail on non-zero QA profiles by default, while maturity scorecard reusable calls can collect failed QA evidence for parent rendering. Verified with actionlint, diff check, Testbox changed gate, PR CI, and CodeQL.
* plugins: clarify allowlist warning when entries don't match discovered ids
When plugins.allow contains entries that do not match any discovered
plugin id (for example a channel id like feishu instead of the real
plugin id openclaw-lark), stop emitting the misleading "plugins.allow
is empty" warning. Emit a specific mismatch warning that lists the
unknown allow entries alongside the discovered plugin ids and points
users at the plugin id rather than a channel id or npm package name.
Refs #68352
* plugins: treat bundled plugin ids as valid allow entries
Codex P2 on #68389: warnWhenAllowlistIsOpen computed allowHasMatch
against the auto-discoverable (workspace + global) subset only, so
a legitimate bundled-only allowlist like plugins.allow=['telegram']
would trip the new mismatch warning whenever any non-bundled plugin
happened to be discoverable alongside it.
Compare allow entries to every discovered plugin id (bundled +
workspace + global) for both the short-circuit and the unmatched-
entries computation. The warning text stays scoped to non-bundled
auto-discoverable plugins; we just stop flagging bundled ids as
'does not match any discovered plugin ids'. Add a regression test
that covers the bundled-only allowlist + non-bundled workspace
plugin combination.
Refs #68352
* chore: drop release-owned CHANGELOG entry (AGENTS.md: changelog is release-generated)
* plugins: clarify allowlist warning when entries do not match plugin ids
---------
Co-authored-by: Sean Sun <lyfuci11@gmail.com>
Co-authored-by: openclaw-clownfish[bot] <280122609+openclaw-clownfish[bot]@users.noreply.github.com>
Restores Android CI to a publicly installable SDK platform and keeps Gradle compileSdk aligned with the workflow install/cache key. Rolls back the API-37-only AndroidX core slice until Android 37 is available to hosted CI, while preserving the unrelated Kotlin dependency bump.
Verification:
- Google SDK repository index check: android-36 exists; android-37/android-37.0 do not.
- git diff --check
- Testbox changed gate: tbx_01kvs3r1bc925pxya94zey23c8
- PR CI: 68 successful, 12 skipped, 0 failing, 0 pending; Android build/play and both Android unit-test lanes passed.
`chunkDiscordText` reserved closing-fence space from the post-line fence state
(`nextOpenFence`), but a flush during a line's segment loop appends the closing
fence based on the still-open `openFence`, which is only advanced after the
line. On a line that closes a fence yet carries trailing text, `reserveChars`
was 0 while `flush()` still appended a `` ``` ``, producing a chunk of
`maxChars + 4` (e.g. 2004 > 2000) that Discord rejects with HTTP 400.
Reserve against `nextOpenFence ?? openFence` so whichever fence a flush can
close is accounted for, keeping a fence-closing line's chunk within `maxChars`.
Co-authored-by: ly-wang19 <ly-wang19@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Change "optional capability" to "optional capabilities" to better match the plural "plugins" in the same sentence.
No functional changes. Documentation only.
Overview surfaces counted any job whose lastRunStatus was "error" as a
current failure, so an intentionally disabled job that previously failed
kept inflating the top-level "failed cron" badge and attention callout.
Add a shared isCronJobActiveFailure predicate that gates the error status
on enabled, matching the adjacent overdue filter, and use it in both the
overview card and the attention items list. Historical status stays
visible in detail views via resolveCronJobLastRunStatus.
* fix(memory-wiki): preserve human notes block on source re-ingest
Re-ingesting an existing source regenerated the page with an empty
wrote inside the human-managed markers. This broke the documented
contract that human note blocks are preserved, and diverged from the
synthesis and chatgpt-import writers that already preserve the block.
When a source page already exists, read it and re-inject its human Notes
block before writing. The block is located by scanning past the fenced
the content, then taking the first human start marker and the last end
marker, so the whole Notes block is preserved verbatim even when the
source content or the note text contains the markers or Markdown
headings. The same preservation is applied to writeImportedSourcePage so
the bridge and unsafe-local source-update writers keep notes too. New
page creation is unchanged.
Adds regressions for plain re-ingest, marker text in source content,
marker text inside the note, a heading inside the note, and an imported
source page update.
* fix(memory-wiki): preserve notes on CRLF source pages
Gate the QQ Bot symlink-media helper regression test on actual file-symlink capability, so environments that cannot create file symlinks skip that specific test while capable hosts still run it.
Validation:
- Windows Vitest proof in the PR body: `extensions/qqbot/src/engine/utils/file-utils.test.ts` passed with 4 tests passed and 1 symlink test skipped when file symlinks were unavailable.
- Current CI is clean at `cb7d5a162e24f7ec5be6985e97b2b74ae45b20f9`, including the refreshed Real behavior proof run `27992101343`.
Co-authored-by: Aniruddha Adak <aniruddhaadak80@users.noreply.github.com>
Co-authored-by: openclaw-clownfish[bot] <280122609+openclaw-clownfish[bot]@users.noreply.github.com>