Compare commits

..

295 Commits

Author SHA1 Message Date
NianJiu
1bfa2787b5 fix(exec): resume agent turn for native chat exec approvals (#93949)
* fix(exec): resume agent turn for native chat exec approvals (issue #93918)

Extend the inline approval-pending path that PR #85239 added for webchat to
every bundled chat channel that ships an `approval-handler.runtime`
adapter (Telegram, Discord, Slack, Signal, WhatsApp, iMessage, Matrix,
Google Chat, QQ Bot, plus webchat). When the originating turn can be
approved in the same chat, the gateway resolves the approval in place and
the agent waits inline for the command output instead of terminating the
run on the "approval-pending" tool result.

Before this fix, native chat approvals landed in the fire-and-forget
`sendExecApprovalFollowup` path. The followup either failed silently
against the agent dispatch and fell through to a direct delivery to the
operator, or never reached the agent at all; either way the model never
saw an "Exec running / Exec finished / Exec denied" event. The operator
had to send a follow-up message to recover the turn, and a new approval
was minted because the original run had already ended.

The change:

- Introduces `NATIVE_APPROVAL_CHANNELS` and `isNativeApprovalChannel`
  in `src/utils/message-channel-constants.ts`, listing the channels that
  ship a native chat approval client. `webchat` is included so the
  single-channel check inside `shouldAwaitGatewayApprovalInline` can
  move from "this one id" to "any native approval client".
- Replaces the `INTERNAL_MESSAGE_CHANNEL` equality check in
  `shouldAwaitGatewayApprovalInline` with `isNativeApprovalChannel`,
  preserving the `approvalFollowupMode` opt-out and the existing
  `unavailableReason === null` gate.
- Adds unit tests asserting inline resolution and inline denial for
  every native approval channel, plus a regression test that
  non-native channels (e.g. feishu) and explicit `approvalFollowupMode`
  settings still take the fire-and-forget path.
- Adds a `NATIVE_APPROVAL_CHANNELS` test in
  `src/utils/message-channel.test.ts` to lock the membership and the
  negative cases.

Refs https://github.com/openclaw/openclaw/issues/93918

* fix(lint): restore InternalMessageChannel type export lost during rebase

Rebase on upstream/main dropped the InternalMessageChannel type alias
from message-channel-constants.ts, breaking the plugin-sdk boundary
.dts check ('has no exported member named InternalMessageChannel').
message-channel.ts was also re-importing the type only to re-export
it, triggering the oxlint no-unused-vars rule.

- Re-add 'export type InternalMessageChannel = typeof INTERNAL_MESSAGE_CHANNEL'
  in message-channel-constants.ts so the public re-export is valid.
- Drop the redundant 'type InternalMessageChannel' from the local
  import in message-channel.ts; the value-side import is what the
  file body actually needs.

* test(exec): align native approval routing expectations
2026-06-18 16:41:04 -04:00
Vincent Koc
e385f6663a fix(live): validate docker setup timeouts 2026-06-18 22:39:43 +02:00
Vincent Koc
76bdb025d6 refactor(cli): remove stale path policy helpers 2026-06-19 04:37:29 +08:00
Vincent Koc
d2e36a176d fix(e2e): validate live plugin tool limits 2026-06-18 22:30:59 +02:00
Josh Lehman
b637414871 fix: add plugin host session cleanup seam (#93733)
* fix: add plugin host session cleanup seam (clawdbot-d02.1.9.1.38)

* test: update session accessor writer ratchet
2026-06-18 13:29:39 -07:00
Vincent Koc
aa39600793 refactor(agents): remove stale tool helpers 2026-06-19 04:28:18 +08:00
Vincent Koc
61b116d597 fix(e2e): validate plugin update timeout seconds 2026-06-18 22:23:23 +02:00
Vincent Koc
033162f209 refactor(channels): remove stale discovery helpers 2026-06-19 04:19:22 +08:00
Dallin Romney
dfd8a2220b test(scripts): route temp-dir helper importers for store.sqlite and status.scan tests (#94681) 2026-06-18 13:15:40 -07:00
Vincent Koc
74ad4f592a fix(ci): route Windows proof away from Linux Testbox 2026-06-18 22:09:58 +02:00
Vincent Koc
a14b1e05e5 refactor(channels): remove stale helper exports 2026-06-19 04:07:36 +08:00
Vincent Koc
8b63a3d551 fix(e2e): validate openwebui docker timeouts 2026-06-18 22:02:43 +02:00
Vincent Koc
f4e9a6e047 refactor(channels): remove stale bundled channel helpers 2026-06-19 03:52:49 +08:00
Vincent Koc
2ae84f75ef fix(e2e): reject invalid mcp code-mode docker ports 2026-06-18 21:47:18 +02:00
Vincent Koc
dca17477dc refactor(media): remove stale completion wake wrappers 2026-06-19 03:43:39 +08:00
Vincent Koc
7f1fa65399 fix(e2e): reject declared oversized probe bodies 2026-06-18 21:37:07 +02:00
Vincent Koc
b6a06f0e49 refactor(sandbox): remove stale bridge helpers 2026-06-19 03:34:44 +08:00
Vincent Koc
9501d4dec2 fix(e2e): reject invalid openwebui docker ports 2026-06-18 21:31:46 +02:00
Vincent Koc
d9397e5b9b fix(e2e): cancel stalled kitchen sink response streams 2026-06-18 21:27:08 +02:00
Vincent Koc
d9d7766a41 fix(scripts): ignore loose package content length headers 2026-06-18 21:23:41 +02:00
Vincent Koc
0771ac8563 refactor(core): remove unused helper wrappers 2026-06-19 03:22:07 +08:00
Vincent Koc
906174bff1 fix(scripts): ignore loose audit content length headers 2026-06-18 21:20:26 +02:00
Colin Johnson
c677424edb qa-lab: add evidence artifact gallery (#94283)
* qa-lab: add evidence artifact gallery

* qa-lab: harden evidence gallery artifacts

* qa-lab: share evidence gallery view types

* qa-lab: disable evidence preview caching

* refactor(qa-lab): resolve evidence artifacts once and trim gallery render/test duplication

* fix(qa-lab): build evidence model before sending /api/evidence success headers

---------

Co-authored-by: Dallin Romney <dallinromney@gmail.com>
2026-06-18 12:17:46 -07:00
Vincent Koc
ea4ddb2eb5 fix(e2e): ignore loose rpc content length headers 2026-06-18 21:14:22 +02:00
Vincent Koc
f19c5f6b2f fix(release): bound cross-os loopback port probes 2026-06-18 21:11:46 +02:00
Vincent Koc
1e83f42a64 refactor(agents): remove unused Claude CLI credential writers 2026-06-19 03:10:14 +08:00
Vincent Koc
4c9b4c32ef fix(scripts): ignore loose content length headers 2026-06-18 21:05:16 +02:00
Vincent Koc
4fc5cf4579 fix(scripts): reject unsafe startup memory timeouts 2026-06-18 21:00:03 +02:00
Vincent Koc
8f8162704d refactor(gateway): remove stale restart sentinel wake predicate 2026-06-19 02:58:16 +08:00
Vincent Koc
f381dca15b fix(e2e): reject loose docker stats CPU samples 2026-06-18 20:57:55 +02:00
Vincent Koc
9ab9469d04 fix(e2e): reject unsafe bundled runtime limits 2026-06-18 20:54:56 +02:00
Vincent Koc
a2f5ac82d5 fix(e2e): reject loose credential numeric limits 2026-06-18 20:51:36 +02:00
Vincent Koc
7b7e40cb0e refactor(gateway): remove duplicate artifact collector 2026-06-19 02:50:22 +08:00
Vincent Koc
e1c2926628 fix(e2e): reject loose telegram credential limits 2026-06-18 20:47:52 +02:00
Vincent Koc
cebe5cb94a fix(e2e): reject invalid client gateway ports 2026-06-18 20:46:11 +02:00
Vincent Koc
0b14724c87 refactor(gateway): remove unused connection auth facade 2026-06-19 02:37:30 +08:00
Vincent Koc
e879a67bf7 refactor(gateway): remove unused skills reload predicate 2026-06-19 02:34:48 +08:00
Vincent Koc
53bb55e023 fix(e2e): reject invalid config writer ports 2026-06-18 20:33:31 +02:00
Vincent Koc
317919ec52 fix(e2e): reject invalid mock fixture ports 2026-06-18 20:28:20 +02:00
Vincent Koc
1f71e92297 refactor(gateway): remove deprecated attachment builder 2026-06-19 02:25:02 +08:00
Vincent Koc
d2e847e8cf fix(e2e): reject invalid telegram proof ports 2026-06-18 20:23:01 +02:00
Vincent Koc
720c0ab372 refactor(gateway): remove unused scoped call wrapper 2026-06-19 02:16:40 +08:00
Vincent Koc
70e39da00f fix(e2e): reject invalid parallels smoke ports 2026-06-18 20:15:33 +02:00
Josh Lehman
9eb6e6d326 refactor(clawdbot-d02.1.9.1.37): add session maintenance seam (#93737) 2026-06-18 11:12:49 -07:00
Vincent Koc
9de9562cb7 fix(git-hooks): avoid precommit dependency hydration 2026-06-18 20:09:48 +02:00
Vincent Koc
6b25ccc4b1 refactor(gateway): remove unused checkpoint wrappers 2026-06-19 02:04:21 +08:00
Peter Lee
111018984c fix(openai-embedding): preserve openai/ prefix for non-native base URLs (#92135)
* fix(openai-embedding): preserve openai/ prefix for non-native base URLs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(openai-embedding): normalize model before maxInputTokens lookup so qualified models retain token cap

* fix(openai-embedding): use semantic hostname check for native OpenAI URL detection

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 14:03:41 -04:00
Vincent Koc
7c24de5c87 docs(changelog): refresh 2026.6.9 notes 2026-06-19 02:00:41 +08:00
Vincent Koc
3125cdacb5 fix(e2e): bound bundled runtime smoke ports 2026-06-18 19:54:19 +02:00
Vincent Koc
8151a547c5 chore(plugin-sdk): refresh API baseline hash 2026-06-18 19:54:11 +02:00
Vincent Koc
940feee71b refactor(gateway): remove unused pending work ack path 2026-06-19 01:49:44 +08:00
Vincent Koc
46d359237e fix(qa): reject invalid qa lab ports 2026-06-18 19:44:50 +02:00
Vincent Koc
ae655345c4 refactor(gateway): remove unused local interface wrapper 2026-06-19 01:42:03 +08:00
Vincent Koc
a48e5091cb fix(plugins): make StepFun npm-only 2026-06-19 01:38:11 +08:00
Vincent Koc
e9229ab77e refactor(tui): remove unused OSC8 wrapper 2026-06-19 01:33:13 +08:00
Vincent Koc
65e77b82f5 fix(e2e): reject invalid kitchen sink rpc ports 2026-06-18 19:29:39 +02:00
Vincent Koc
2c7fe6a39c test(sqlite): use shared temp directory helper 2026-06-19 01:27:49 +08:00
Vincent Koc
7ef85bfb1d fix(crabbox): refresh stale changed-gate git metadata 2026-06-18 19:23:59 +02:00
Vincent Koc
361320cd9f refactor(text): remove unused final tag predicate 2026-06-19 01:21:31 +08:00
Vincent Koc
65eb2ccd4a chore(ci): align rebased sqlite landing budgets 2026-06-19 01:20:06 +08:00
Vincent Koc
b6cb3d2901 fix(backup): exclude memory reindex shadow databases 2026-06-19 01:20:06 +08:00
Vincent Koc
a9cc2697d3 test(memory): align metadata repair expectation 2026-06-19 01:20:06 +08:00
Vincent Koc
4eb0ddab00 chore(ci): refresh rebased config baseline 2026-06-19 01:20:06 +08:00
Vincent Koc
60335533a9 chore(ci): refresh sqlite landing baselines 2026-06-19 01:20:06 +08:00
Vincent Koc
681e5a0f8c fix(memory): preserve schema helper compatibility 2026-06-19 01:20:06 +08:00
Vincent Koc
d074c0d970 fix(memory): detect shipped status indexes 2026-06-19 01:20:06 +08:00
Vincent Koc
39c9fd8592 fix(proxy): align managed proxy fixture settings 2026-06-19 01:20:06 +08:00
Vincent Koc
1d6b4ccfef fix(proxy): preserve mode-specific blob types 2026-06-19 01:20:06 +08:00
Vincent Koc
b7b452d531 fix(proxy): preserve migration compatibility 2026-06-19 01:20:06 +08:00
Vincent Koc
d37de2a39a fix(proxy): preserve capture store compatibility 2026-06-19 01:20:06 +08:00
Vincent Koc
6e6bd5633f fix(memory): rebuild migrated search indexes 2026-06-19 01:20:06 +08:00
Josh Lehman
28fb5b019a fix(memory): migrate source primary key shape 2026-06-19 01:20:06 +08:00
Josh Lehman
9cfd1cd287 fix(memory): key index sources by path and source 2026-06-19 01:20:06 +08:00
Josh Lehman
21163a704b fix(memory): make file index publication atomic 2026-06-19 01:20:06 +08:00
Vincent Koc
d16d2460b8 fix(ci): align sqlite refactor checks 2026-06-19 01:20:06 +08:00
Vincent Koc
cc80128222 test(memory): type legacy vector migration fixture 2026-06-19 01:20:06 +08:00
Vincent Koc
9b66e291ae test(doctor): align migration harness 2026-06-19 01:20:06 +08:00
Vincent Koc
7485dd6492 fix(sqlite): migrate legacy memory and proxy state 2026-06-19 01:20:06 +08:00
Vincent Koc
8413984e87 chore(sqlite): align ownership guardrails and docs 2026-06-19 01:20:06 +08:00
Vincent Koc
0876ed59e0 refactor(proxy): store captures in shared state database 2026-06-19 01:20:06 +08:00
Vincent Koc
88bc08c124 refactor(memory): canonicalize agent database tables 2026-06-19 01:20:06 +08:00
Vincent Koc
acdcf37f87 chore(memory): simplify publish error 2026-06-19 01:20:06 +08:00
Vincent Koc
d64db0fc3d fix(memory): load vector extension before publish 2026-06-19 01:20:06 +08:00
Vincent Koc
25e68e9fa7 fix(memory): reject stale reindex publishes 2026-06-19 01:20:06 +08:00
Vincent Koc
1e30af71bd fix(package): exclude sqlite test runtime 2026-06-19 01:20:06 +08:00
Vincent Koc
1db7c41bf2 fix(sdk): keep sqlite lifecycle helpers private 2026-06-19 01:20:06 +08:00
Vincent Koc
a7fada4b61 fix(memory): invalidate stale shadow indexes 2026-06-19 01:20:06 +08:00
Vincent Koc
2ef2258804 fix(matrix): retain legacy root scoring 2026-06-19 01:20:06 +08:00
Vincent Koc
8b7269d197 fix(sqlite): preserve migrations and reindex safety 2026-06-19 01:20:06 +08:00
Vincent Koc
f324f7e281 refactor(memory): use the per-agent sqlite database 2026-06-19 01:20:06 +08:00
Vincent Koc
1a3ded761e refactor(matrix): move storage metadata into sqlite 2026-06-19 01:20:06 +08:00
Daniil Karpenko
0922ee9955 [codex] Fix Telegram rich local Markdown link hrefs (#94118)
Merged via squash.

Prepared head SHA: 42c738978f
Co-authored-by: dankarization <26330178+dankarization@users.noreply.github.com>
Co-authored-by: obviyus <22031114+obviyus@users.noreply.github.com>
Reviewed-by: @obviyus
2026-06-18 22:45:39 +05:30
Vincent Koc
3d6840bc59 refactor(browser): remove unused localhost proxy wrapper 2026-06-19 01:13:45 +08:00
Vincent Koc
8b34b8537a fix(crabbox): bootstrap bun on aws macos 2026-06-18 19:06:31 +02:00
Vincent Koc
4e0376255f fix(onboard): reject deprecated external auth choices 2026-06-19 01:05:52 +08:00
Vincent Koc
0710eebec2 fix(plugins): avoid catalog lookup allocations 2026-06-19 01:05:52 +08:00
Vincent Koc
a4c27323bf fix(plugins): align external provider types 2026-06-19 01:05:52 +08:00
Vincent Koc
21728777df feat(plugins): externalize official provider batch 2026-06-19 01:05:52 +08:00
Vincent Koc
350aa7c3be refactor(extensions): remove unused helper facades 2026-06-19 01:05:26 +08:00
Monkey-wusky
4929cbdb04 fix(telegram): set richMessages default to false explicitly in schema (#94048)
Merged via squash.

Prepared head SHA: 4af05ac54a
Co-authored-by: Monkey-wusky <66244686+Monkey-wusky@users.noreply.github.com>
Co-authored-by: obviyus <22031114+obviyus@users.noreply.github.com>
Reviewed-by: @obviyus
2026-06-18 22:28:30 +05:30
Vincent Koc
2c499756ad test(ci): stabilize timeout-sensitive shards
Stabilize timeout-sensitive hosted QA by removing wedged synthetic Codex run-attempt integrations while keeping narrower dynamic-tool and thread-start coverage. Refresh root and Discord shrinkwraps after the current undici security floor landed on main.\n\nProof: git diff --check origin/main...HEAD; node scripts/pre-commit/pnpm-audit-prod.mjs --audit-level=high; node scripts/generate-npm-shrinkwrap.mjs --all --check; CI run 27774571070; Plugin Prerelease run 27774571273.
2026-06-19 00:51:07 +08:00
Vincent Koc
9546b19a59 refactor(browser): remove unused CDP DOM helpers 2026-06-19 00:44:26 +08:00
Vincent Koc
1eaace70e3 refactor(extensions): remove unused helper exports 2026-06-19 00:33:04 +08:00
Vincent Koc
4560597c4b refactor(providers): remove unused catalog metadata exports 2026-06-19 00:24:41 +08:00
Vincent Koc
beea31a6b5 feat(firecrawl): add keyless scrape support (#94551)
Merged under maintainer approval while exact-head CI was queue-bound.

Co-authored-by: Developers Digest <124798203+developersdigest@users.noreply.github.com>
Co-authored-by: Vincent Koc <vincentkoc@users.noreply.github.com>
2026-06-19 00:23:41 +08:00
Vincent Koc
fa4f1abb29 fix(cron): release cancelled embedded lanes 2026-06-19 00:20:49 +08:00
王泽琛0668001337
bf9c59b6a4 fix(cron): prevent lane timeout during long tool execution
The lane timeout sliding window expires during long-running tool
execution (e.g. exec commands >5min) because noteLaneTaskProgress()
is never called during tool execution. Add a periodic 30s interval
that calls noteLaneTaskProgress() while the embedded attempt runs,
keeping the lane alive until the attempt completes.

Closes: openclaw/openclaw#94033
Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-19 00:20:49 +08:00
Vincent Koc
1c18a7d6a3 refactor(agents): remove unused runtime tool-result truncation wrapper 2026-06-19 00:10:41 +08:00
Vincent Koc
508594a620 test(ci): stabilize MCP catalog timeout coverage 2026-06-19 00:07:25 +08:00
Vincent Koc
2ff1dfdebe refactor(agents): remove unused runtime transcript rotation wrapper 2026-06-19 00:00:48 +08:00
Vincent Koc
81eaa88ce5 refactor(agents): remove stale live model switch map 2026-06-18 23:53:18 +08:00
joshavant
8ab36e4308 android: mark manual mic as microphone foreground service 2026-06-18 17:49:08 +02:00
Vincent Koc
ba779d8367 refactor(agents): remove unused thinking recovery sanitizer 2026-06-18 23:45:47 +08:00
Vincent Koc
ed389a87e9 refactor(agents): remove unused runner utility wrappers 2026-06-18 23:39:15 +08:00
clawsweeper[bot]
73aab30989 fix(cron): retry isolated setup timeouts (#94588)
Summary:
- Merged fix(cron): retry isolated setup timeouts after ClawSweeper review.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(cron): retry isolated setup timeouts

Validation:
- ClawSweeper review passed for head 6b0ee52fcb.
- Required merge gates passed before the squash merge.

Prepared head SHA: 6b0ee52fcb
Review: https://github.com/openclaw/openclaw/pull/94588#issuecomment-4743572807

Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: Aaron Eden <21063939+aaroneden@users.noreply.github.com>
Approved-by: takhoffman
2026-06-18 15:34:30 +00:00
Vincent Koc
967fb8d917 refactor(agents): remove unused transcript state helpers 2026-06-18 23:29:52 +08:00
joshavant
961ce49f97 android: use absolute play metadata paths 2026-06-18 17:17:19 +02:00
Vincent Koc
092e9f69ab refactor(agents): remove unused tool result size helper 2026-06-18 23:16:53 +08:00
Vincent Koc
c8fc2fedee refactor(status): remove unused gateway probe exports 2026-06-18 23:07:58 +08:00
joshavant
1fc97cf05d android: update chat screenshot copy 2026-06-18 16:56:46 +02:00
Vincent Koc
61210b015f refactor(models): remove model row registry alias 2026-06-18 22:54:51 +08:00
Vincent Koc
ab25e340f9 refactor(commands): remove unused sandbox counters 2026-06-18 22:47:35 +08:00
Vincent Koc
af3acf0626 fix(gateway): honor WSL env in status hints 2026-06-18 16:43:58 +02:00
Vincent Koc
64db900bc4 test(gateway): assert codex approval resolver 2026-06-18 16:36:13 +02:00
Vincent Koc
8d808de80a test(gateway): accept app-server approval proof 2026-06-18 16:36:13 +02:00
Vincent Koc
49d03d24a5 test(gateway): align approval and update proof 2026-06-18 16:36:13 +02:00
Vincent Koc
890055d421 fix(gateway): keep approval waits visible across backend calls 2026-06-18 16:36:13 +02:00
Vincent Koc
eb07ba5117 fix(doctor): honor WSL env overrides in hints 2026-06-18 16:35:55 +02:00
Vincent Koc
bad21faf62 refactor(copilot): remove unused usage total helper 2026-06-18 22:29:45 +08:00
Vincent Koc
3f4c04f4df fix(windows): prefer native arm64 portable git 2026-06-18 16:25:39 +02:00
Shakker
54a2af32c9 test: route live cache trace env 2026-06-18 15:19:21 +01:00
Shakker
49f10a140b fix: isolate model catalog state env 2026-06-18 15:18:11 +01:00
Vincent Koc
22affb874d fix(android): validate screenshot locale path 2026-06-18 16:17:02 +02:00
Shakker
ad550ae3dc test: route runtime policy env gates 2026-06-18 15:14:46 +01:00
Shakker
7bdea54a82 fix: scope cli runner env helpers 2026-06-18 15:14:09 +01:00
Vincent Koc
779e096bcc docs(release): backfill complete contribution records 2026-06-18 22:11:49 +08:00
Shakker
6ed2189a49 test: route bundle mcp env setup 2026-06-18 15:10:16 +01:00
Shakker
9cfc37b997 fix: scope bootstrap home override 2026-06-18 15:08:30 +01:00
Shakker
55750f7c6d test: route security floor env setup 2026-06-18 15:06:13 +01:00
Shakker
c0539a4060 fix: simplify approval env restore 2026-06-18 15:03:50 +01:00
Shakker
e40396bfc1 test: route gateway approval env setup 2026-06-18 15:03:07 +01:00
Vincent Koc
0e61c1112f refactor(agents): remove unused cache store wrapper 2026-06-18 22:02:32 +08:00
Shakker
32906798d2 fix: reuse oauth env wrapper 2026-06-18 14:59:25 +01:00
Josh Lehman
fd8e3bf652 refactor: add restart recovery lifecycle seam (#93735) 2026-06-18 06:57:24 -07:00
Shakker
459ba6e07a test: route codex refresh env setup 2026-06-18 14:57:06 +01:00
joshavant
967f0e0a30 android: boot emulator for release screenshots 2026-06-18 15:55:32 +02:00
Vincent Koc
e84b719c99 fix(test): preserve testbox docker build fallback 2026-06-18 15:34:46 +02:00
Vincent Koc
44500daaa0 refactor(copilot): remove dormant bridge scaffolding 2026-06-18 21:32:57 +08:00
Peter Steinberger
7cca3d4618 fix(feishu): preserve replies before error finals (#94428)
Merged via squash.

Prepared head SHA: f86d6bddb5
Co-authored-by: steipete <steipete+steipete@users.noreply.github.com>
Reviewed-by: @steipete
2026-06-18 15:31:17 +02:00
Peter Steinberger
9a571399bb fix(agents): preserve active compaction retries (#94421)
Merged via squash.

Prepared head SHA: 70a27bda48
Co-authored-by: steipete <steipete+steipete@users.noreply.github.com>
Reviewed-by: @steipete
2026-06-18 15:30:48 +02:00
huangjianxiong
4ec9d4a2b5 fix(infra): probe 127.0.0.1 in ensurePortAvailable to detect IPv4-only occupants (#94394)
Merged via squash.

Prepared head SHA: 65bbefde8c
Co-authored-by: Pandah97 <80405497+Pandah97@users.noreply.github.com>
Co-authored-by: steipete <steipete+steipete@users.noreply.github.com>
Reviewed-by: @steipete
2026-06-18 15:29:58 +02:00
Vincent Koc
3f965012bf test(tooling): isolate changelog gate testbox mode 2026-06-18 15:18:54 +02:00
Vincent Koc
aad0559de0 refactor(test): dedupe bundled plugin record fixtures 2026-06-18 21:10:40 +08:00
Peter Steinberger
712e45daf1 fix(ci): stabilize update run gates (#94580)
Merged via squash.

Prepared head SHA: 93e784351b
Co-authored-by: steipete <steipete+steipete@users.noreply.github.com>
Reviewed-by: @steipete
2026-06-18 15:07:21 +02:00
Vincent Koc
8f295f183d refactor(test): dedupe zip central directory fixture 2026-06-18 20:54:36 +08:00
Vincent Koc
00a06eab44 refactor(test): dedupe installer npm fixtures 2026-06-18 20:38:29 +08:00
Vincent Koc
af21bd7130 refactor(gateway): dedupe gateway client test startup 2026-06-18 20:27:14 +08:00
Vincent Koc
9592a83a29 refactor(config): dedupe plugin metadata test snapshots 2026-06-18 20:14:27 +08:00
Vincent Koc
c88d5f2611 refactor(agents): reuse embedded runner e2e fixtures 2026-06-18 19:57:01 +08:00
Vincent Koc
13d474134f refactor(plugins): dedupe install test fixtures 2026-06-18 19:44:18 +08:00
Vincent Koc
9caf4e1ae9 refactor(cron): dedupe cron test helpers 2026-06-18 19:27:44 +08:00
Vincent Koc
bba1d0204c chore(extensions): bump tokenjuice to 0.8.1 2026-06-18 19:08:22 +08:00
Vincent Koc
45366dec2e refactor(codex): remove unused websocket debug getter 2026-06-18 19:05:46 +08:00
Vincent Koc
b0375f25b3 refactor(test): remove unused channel route exports 2026-06-18 18:50:48 +08:00
Vincent Koc
c1a414bf28 fix(update): preserve RPC pre-update config 2026-06-18 18:46:18 +08:00
Vincent Koc
4e476d333e fix(update): retry gateway plugin convergence 2026-06-18 18:46:18 +08:00
masatohoshino
085ff95fcf fix(update): carry effective git channel into post-core finalize
Address ClawSweeper P1 (Carry the effective git channel into finalize):
an unconfigured git/source update runs the core update on the git/dev channel
(runGatewayUpdate: opts.channel ?? "dev"), but the finalizer received no channel
and fell back to the stable package channel, so plugin convergence could resolve
official plugins on the wrong channel.

Mirror the CLI post-core resume's effective/requested channel split: the RPC
finalize path now passes the effective channel (configChannel ?? DEFAULT_GIT_CHANNEL)
to update finalize via OPENCLAW_UPDATE_EFFECTIVE_CHANNEL (convergence-only), never
as --channel. update finalize uses it as a convergence fallback but never persists
update.channel unless the user actually requested one.
2026-06-18 18:46:18 +08:00
masatohoshino
07c29c20d6 fix(update): forward finalize --channel only when configured
Codex follow-up: defaulting the channel to dev and passing --channel made
update finalize persist update.channel into openclaw.json (persistRequestedUpdateChannel
treats any --channel as an explicit request). Only forward --channel when the
caller has a configured channel so the finalizer never writes a channel the user
did not request; when omitted it converges on the stored/default channel and the
reconcile still resolves a host-compatible version. Keeps the per-step vs
whole-process timeout decoupling.
2026-06-18 18:46:18 +08:00
masatohoshino
6214762461 fix(update): match git channel + decouple finalize process timeout
Address codex PR-review findings:
- Default the post-core finalize channel to the git/dev channel (matching
  runGatewayUpdate's git default) instead of letting update finalize fall back to
  the stable package channel, so official plugins converge on the same channel as
  the core update for default source updates.
- Decouple the finalizer's whole-process spawn timeout from the per-step
  --timeout so a valid multi-step finalize is not killed prematurely and falsely
  reported as post-core-plugin-finalize-failed.
2026-06-18 18:46:18 +08:00
masatohoshino
1b251a6af1 fix(update): harden post-core finalize (service-env strip, no-op skip)
- Strip gateway service identity (OPENCLAW_SERVICE_MARKER/KIND/PID) from the
  finalizer child so it is not mistaken for the managed service, matching the
  CLI post-core spawn.
- Skip finalize for no-op git updates (unchanged SHA and version), mirroring the
  CLI resume gate, to avoid an unnecessary doctor/convergence run.
2026-06-18 18:46:18 +08:00
masatohoshino
90d385cb93 fix(update): resume official plugin convergence after gateway git update
The gateway update.run RPC updated git/source installs via runGatewayUpdate
but, unlike the openclaw update CLI, never resumed the post-core plugin
convergence that runGatewayUpdate's doctor pass defers. As a result a
git/source core update would restart on the new core with official managed
plugins still pinned to versions built against removed core APIs.

Spawn the rebuilt binary's update finalize entrypoint after a successful
git update so official plugins reconcile to a host-compatible version, and
block the restart if convergence fails (mirroring the CLI).
2026-06-18 18:46:18 +08:00
Vincent Koc
2ac2b021cf fix(gateway): bound config.get tool results 2026-06-18 18:46:12 +08:00
Vincent Koc
0d9bb2fe47 fix(agents): sanitize oversized middleware inputs 2026-06-18 18:46:12 +08:00
Vincent Koc
d694047cb5 refactor(core): remove unused internal exports 2026-06-18 18:40:12 +08:00
Vincent Koc
dc2fdd1b99 refactor(core): remove unused internal type exports 2026-06-18 18:19:45 +08:00
Vincent Koc
9f5c9b2e22 refactor(test): remove unused proof and channel helpers 2026-06-18 18:00:46 +08:00
Vincent Koc
68ead4dd80 test(e2e): reuse shared image for multi-node updates 2026-06-18 11:42:36 +02:00
Vincent Koc
aef0e4d0b0 refactor(cron): remove unused regression fixture writer 2026-06-18 17:39:15 +08:00
Vincent Koc
eb17c0d635 refactor(test): delete unused media generation mocks 2026-06-18 17:19:59 +08:00
Vincent Koc
95c256fa98 Merge branch 'main' of https://github.com/openclaw/openclaw
* 'main' of https://github.com/openclaw/openclaw:
  refactor(test): remove unused vitest route helpers
2026-06-18 16:55:59 +08:00
Vincent Koc
915ca8135e refactor(test): remove unused vitest route helpers 2026-06-18 16:51:19 +08:00
Vincent Koc
c558e918c2 feat(cohere): externalize provider plugin 2026-06-18 16:50:56 +08:00
Patrick Erichsen
1b19c790bf feat: externalize GMI provider plugin (#94350)
* feat: externalize gmi provider plugin

* fix(plugins): repair gmi alias installs

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-06-18 16:47:38 +08:00
Vincent Koc
1e02cc9473 refactor(scripts): delete unused vitest args helper 2026-06-18 16:41:11 +08:00
Vincent Koc
cdb2fa35e2 test(e2e): show browser CDP smoke diagnostics 2026-06-18 10:37:47 +02:00
Vincent Koc
b4b2698ac2 fix(ci): report WSL2 restart-required probes 2026-06-18 10:32:36 +02:00
Vincent Koc
8607b4042f refactor(scripts): remove dead helper exports 2026-06-18 16:29:56 +08:00
Vincent Koc
e3bd804a17 refactor(browser): remove dead CLI test mocks 2026-06-18 16:15:04 +08:00
Vincent Koc
60d6a8a89d refactor(test-ui): remove dead utility code 2026-06-18 15:53:52 +08:00
Dmitry Golubev
70489061ca fix: expose OpenAI image quality and moderation CLI options (#94156)
Merged via squash.

Prepared head SHA: 7f6d39610e
Co-authored-by: lastguru-net <1543525+lastguru-net@users.noreply.github.com>
Co-authored-by: fuller-stack-dev <263060202+fuller-stack-dev@users.noreply.github.com>
Reviewed-by: @fuller-stack-dev
2026-06-18 01:21:29 -06:00
Vincent Koc
43e96439ee refactor(msteams): reuse shared activity handler test helper 2026-06-18 15:16:53 +08:00
Vincent Koc
fae30318d1 refactor(test): remove unused shared helpers 2026-06-18 15:11:39 +08:00
Vincent Koc
58b77e787d refactor(extensions): remove unused helper exports 2026-06-18 15:05:35 +08:00
Vincent Koc
dbebdb9563 refactor(discord): remove unused thread binding helpers 2026-06-18 14:58:46 +08:00
Vincent Koc
c6aa8e7423 refactor(qa): remove unused parity helpers 2026-06-18 14:55:46 +08:00
Vincent Koc
2a0aeb59be refactor(extensions): remove unused private helpers 2026-06-18 14:53:30 +08:00
Vincent Koc
48c63fcb27 refactor(browser): remove unused chrome mcp wrappers 2026-06-18 14:50:24 +08:00
Vincent Koc
31e0f894c5 refactor(imessage): remove unused chat actions 2026-06-18 14:46:19 +08:00
Vincent Koc
5b7cf461ac test(agents): widen slow MCP catalog timeout margin 2026-06-18 08:40:02 +02:00
Vincent Koc
153c395527 test(slack): throw Error instances in reconnect loop test 2026-06-18 08:40:02 +02:00
Vincent Koc
12787de566 test(agents): remove redundant provider attribution assertion 2026-06-18 08:40:02 +02:00
Vincent Koc
c50899275e fix(agents): keep strict mode for projected OpenAI tools 2026-06-18 08:40:02 +02:00
Vincent Koc
95b6df8343 refactor(agents): remove stale lane auto-resume helper 2026-06-18 14:35:39 +08:00
Vincent Koc
8f48f5e5ba refactor(agents): remove stale exec event helper 2026-06-18 14:27:12 +08:00
Vincent Koc
519eb3d088 refactor(agents): remove stale Claude CLI compat wrapper 2026-06-18 14:21:26 +08:00
Subash Natarajan
01dcaba78d fix(slack): remove socket reconnect attempt cap so gateway stays connected indefinitely (#73162)
Merged via squash.

Prepared head SHA: ac51979a7f
Co-authored-by: suboss87 <11032439+suboss87@users.noreply.github.com>
Co-authored-by: steipete <58493+steipete@users.noreply.github.com>
Reviewed-by: @steipete
2026-06-18 08:18:30 +02:00
Vincent Koc
5b5e6ff272 refactor(agents): remove stale nested lane helper 2026-06-18 14:11:22 +08:00
Vincent Koc
5feb88dc1e refactor(agents): remove stale provider auth warm wrapper 2026-06-18 14:09:23 +08:00
Vincent Koc
9dccdedc07 refactor(agents): remove stale provider attribution wrappers 2026-06-18 14:06:06 +08:00
Vincent Koc
6db0fb3197 refactor(agents): remove stale openai schema wrappers 2026-06-18 14:02:03 +08:00
Vincent Koc
a455b508a7 refactor(agents): remove stale live model switch api 2026-06-18 13:58:32 +08:00
Vincent Koc
d5d6576e06 fix(docs): refresh qa lab plugin inventory 2026-06-18 07:57:49 +02:00
Vincent Koc
6e66dfb7f0 refactor(agents): remove stale exec helpers 2026-06-18 13:56:29 +08:00
Vincent Koc
9b082cb1bb refactor(agents): remove stale subagent depth policy 2026-06-18 13:52:41 +08:00
Vincent Koc
4106794ff5 refactor(agents): remove stale acp error alias 2026-06-18 13:42:11 +08:00
Vincent Koc
ff49b7fab1 refactor(agents): remove stale runner aliases 2026-06-18 13:39:31 +08:00
Vincent Koc
270842a855 test(agents): dedupe fs bridge mock factory 2026-06-18 13:28:20 +08:00
Vincent Koc
a59f093d3e refactor(ui): dedupe usage menu close handler 2026-06-18 13:25:20 +08:00
Vincent Koc
b9b057b6d5 refactor(ui): dedupe grouped message options 2026-06-18 13:23:37 +08:00
Vincent Koc
227757079c refactor(ui): dedupe workspace open requests 2026-06-18 13:21:03 +08:00
Vincent Koc
ce611bc803 refactor(plugins): dedupe index database options 2026-06-18 13:18:06 +08:00
Vincent Koc
05fe48b289 refactor(gateway): dedupe agent id listing 2026-06-18 13:14:32 +08:00
Vincent Koc
a914d572c7 test(doctor): update cron fast-path mock 2026-06-18 07:07:48 +02:00
Vincent Koc
ac973a8f60 refactor(qa-matrix): dedupe progress timeout errors 2026-06-18 13:06:41 +08:00
Vincent Koc
0aa6b8ca0d refactor(qqbot): dedupe approval capability state 2026-06-18 13:03:55 +08:00
Vincent Koc
b89fa3333b refactor(whatsapp): dedupe read receipt helper 2026-06-18 13:00:48 +08:00
Vincent Koc
d30d409097 refactor(plugin-state): dedupe update serialization 2026-06-18 12:58:13 +08:00
Vincent Koc
77a877e869 refactor(zalo): dedupe hosted route cleanup 2026-06-18 12:54:34 +08:00
Vincent Koc
523e98a47a refactor(config): dedupe validation metadata loader 2026-06-18 12:50:19 +08:00
Vincent Koc
f8f2006c8b fix(plugin-sdk): refresh api baseline hash 2026-06-18 06:46:44 +02:00
Vincent Koc
6f1def4d68 refactor(agents): dedupe cli assistant deltas 2026-06-18 12:46:03 +08:00
Vincent Koc
3f6ed50d68 refactor(plugins): dedupe loader validation errors 2026-06-18 12:41:54 +08:00
Vincent Koc
2e44610ba2 refactor(agents): dedupe code mode json serialization 2026-06-18 12:38:02 +08:00
Vincent Koc
0f998e6cc4 fix(test): update codex app-server client fixtures 2026-06-18 06:35:01 +02:00
Vincent Koc
daf7c92db3 refactor(agents): dedupe model catalog entries 2026-06-18 12:33:09 +08:00
Vincent Koc
e55a637537 refactor(google-meet): dedupe bridge process cleanup 2026-06-18 12:30:19 +08:00
Vincent Koc
ff3ebb1c24 refactor(codex): dedupe startup client cleanup 2026-06-18 12:28:39 +08:00
Vincent Koc
0da706dbfb refactor(sessions): dedupe session helpers 2026-06-18 12:25:23 +08:00
Vincent Koc
0b68c5f6de refactor(doctor): dedupe missing skill summaries 2026-06-18 12:20:41 +08:00
Vincent Koc
de10eca7d6 refactor(scripts): dedupe release assertion readers 2026-06-18 12:17:32 +08:00
Vincent Koc
cb8daec729 refactor(scripts): dedupe mcp code-mode fixture 2026-06-18 12:14:01 +08:00
Vincent Koc
78d86f710e fix(ci): add focused windows ci probe 2026-06-18 06:10:52 +02:00
Vincent Koc
2052a3bf4e refactor(sessions): dedupe generated transcript parsing 2026-06-18 12:09:37 +08:00
Vincent Koc
887297e04a refactor(scripts): dedupe gateway bench probes 2026-06-18 12:06:40 +08:00
Vincent Koc
72628f906b refactor(config): dedupe observe suspicion checks 2026-06-18 12:01:27 +08:00
Jesse Merhi
804e5f21d1 fix: honor bound agent exec host policy
* fix(codex): honor bound agent exec host policy

* fix(codex): carry bound agent into app-server runtime

* fix(codex): preserve main session exec overrides

* fix(codex): narrow runtime session reads

* fix(codex): honor default agent session aliases

* fix(codex): isolate bound session exec overrides

* fix(codex): honor unscoped default session policy
2026-06-18 14:00:57 +10:00
Vincent Koc
ba36291a8c refactor(telegram): dedupe prompt media paths 2026-06-18 11:57:40 +08:00
Vincent Koc
b63acf54f9 refactor(agents): dedupe dead helper code 2026-06-18 11:55:21 +08:00
Vincent Koc
070b0456bb refactor(plugin-sdk): remove orphan config path helper 2026-06-18 11:40:26 +08:00
Vincent Koc
af026b383d fix(plugin-sdk): refresh api baseline hash 2026-06-18 05:34:19 +02:00
Vincent Koc
b5f71c0971 refactor(extensions): remove unused dead helpers 2026-06-18 11:33:15 +08:00
Vincent Koc
bdc46fa28d refactor(memory): remove unused session file exports 2026-06-18 11:24:02 +08:00
Vincent Koc
0cae5b3672 refactor(test): remove unused helper types 2026-06-18 11:19:28 +08:00
Vincent Koc
90a7f552b1 refactor(test): remove unused vitest helper exports 2026-06-18 11:16:28 +08:00
Vincent Koc
9fa1252119 refactor(scripts): remove unused helper exports 2026-06-18 11:14:23 +08:00
Vincent Koc
2f8f93676e refactor(extensions): remove unused utility helpers 2026-06-18 11:09:14 +08:00
Vincent Koc
c58e1abf6a refactor(telegram): remove unused message helpers 2026-06-18 11:06:15 +08:00
Vincent Koc
b4e5aa18b3 refactor(feishu): remove unused send helpers 2026-06-18 11:02:00 +08:00
Vincent Koc
4012edcd66 refactor(extensions): remove unused provider helpers 2026-06-18 10:59:33 +08:00
Vincent Koc
acc37e220c refactor(core): remove unused helper exports 2026-06-18 10:54:40 +08:00
Vincent Koc
95cf7dee72 fix(test): schedule missing docker runner lanes 2026-06-18 04:52:44 +02:00
Vincent Koc
9d7fd31dd3 refactor(ui): remove unused type exports 2026-06-18 10:51:42 +08:00
Vincent Koc
9bb263c985 refactor(channels): remove unused route helpers 2026-06-18 10:46:59 +08:00
Vincent Koc
1dccbbfc01 refactor(ui): remove unused utility helpers 2026-06-18 10:44:22 +08:00
Vincent Koc
79718d9e01 refactor(ui): remove unused helper exports 2026-06-18 10:41:07 +08:00
Vincent Koc
51d211e666 fix(test): target remaining docker runner scripts 2026-06-18 04:39:23 +02:00
Vincent Koc
967f8adc63 refactor(core): remove unused internal aliases 2026-06-18 10:34:06 +08:00
Vincent Koc
573d7bf2e3 fix(test): target release docker runner scripts 2026-06-18 04:33:53 +02:00
Vincent Koc
98bc05e008 fix(test): target update docker runner scripts 2026-06-18 04:30:09 +02:00
Vincent Koc
1b81b0ea21 fix(test): target provider web docker runners 2026-06-18 04:26:39 +02:00
Vincent Koc
e122cd09ab refactor(web): remove provider runtime facade 2026-06-18 10:21:20 +08:00
Patrick Erichsen
1a6b84b698 docs: add ClawHub namespace claims to sidebar (#94332)
Merged via squash.

Prepared head SHA: e3b4541f32
Co-authored-by: Patrick-Erichsen <20157849+Patrick-Erichsen@users.noreply.github.com>
Co-authored-by: Patrick-Erichsen <20157849+Patrick-Erichsen@users.noreply.github.com>
Reviewed-by: @Patrick-Erichsen
2026-06-17 19:21:04 -07:00
Vincent Koc
4e6057e4dd refactor(media): remove common helper facades 2026-06-18 10:14:44 +08:00
Vincent Koc
665d2601e5 fix(test): target plugin docker runner scripts 2026-06-18 04:10:03 +02:00
Vincent Koc
7b74d7332f refactor(core): inline one-use helper modules 2026-06-18 10:07:51 +08:00
Vincent Koc
a3ae453a1a refactor(config): remove prototype key facade 2026-06-18 10:05:34 +08:00
Vincent Koc
d3ced4554d refactor(subagents): remove session key normalizer facade 2026-06-18 10:03:30 +08:00
Vincent Koc
83f7203bdb refactor(normalization): remove record coercion facades 2026-06-18 10:00:40 +08:00
Vincent Koc
bf5a108695 fix(test): target package docker shell scripts 2026-06-18 04:00:12 +02:00
Vincent Koc
4552ea7ba0 refactor(agents): remove tool display record facade 2026-06-18 09:55:57 +08:00
Vincent Koc
2e49b6b769 fix(test): target cron and image docker clients 2026-06-18 03:53:27 +02:00
Vincent Koc
4651ffad4a refactor(security): remove audit tool policy facade 2026-06-18 09:51:46 +08:00
Vincent Koc
a22a1edc8f refactor(outbound): remove tool payload facade 2026-06-18 09:49:19 +08:00
Vincent Koc
4a596d9bc8 refactor(plugins): remove setup binary facade 2026-06-18 09:46:56 +08:00
Vincent Koc
496ffdf5c4 fix(test): target crestodian docker e2e scripts 2026-06-18 03:46:17 +02:00
Vincent Koc
617076687e refactor(plugins): remove import specifier facade 2026-06-18 09:44:17 +08:00
Vincent Koc
978b5225a5 refactor(doctor): remove command compatibility barrels 2026-06-18 09:41:04 +08:00
Vincent Koc
9477b11d98 fix(test): target docker e2e client changes 2026-06-18 03:38:14 +02:00
Vincent Koc
544b00e4e1 refactor(plugins): remove setup browser facade 2026-06-18 09:37:25 +08:00
Vincent Koc
4a4a5968e8 refactor(text): remove tool-call block facade 2026-06-18 09:35:17 +08:00
Vincent Koc
b3884750b2 fix(test): target mcp seed helper changes 2026-06-18 03:17:10 +02:00
Vincent Koc
f9fc2efe68 refactor(llm): remove oauth helper facades 2026-06-18 09:00:49 +08:00
Vincent Koc
2a30426133 fix(test): target docker e2e seed changes 2026-06-18 02:58:34 +02:00
Vincent Koc
5f42a93e4d refactor(commands): remove compatibility barrels 2026-06-18 08:56:48 +08:00
Vincent Koc
5fcab50e49 fix(test): avoid huge chat attachment fixture 2026-06-18 02:56:20 +02:00
937 changed files with 25722 additions and 19186 deletions

View File

@@ -12,10 +12,10 @@ content, ordering, grouping, and attribution discipline.
## Goal
Rewrite the target `CHANGELOG.md` version section from history, not from stale
draft notes. Produce grouped user-facing release notes sorted by user interest
while preserving every relevant issue/PR ref and every human `Thanks @...`
attribution.
Rebuild the target `CHANGELOG.md` version section from a complete, generated
history manifest, not stale draft notes. Produce grouped user-facing release
notes sorted by user interest while preserving every relevant issue/PR ref and
every human `Thanks @...` attribution.
## Inputs
@@ -34,8 +34,37 @@ attribution.
- `git log --first-parent --date=iso-strict --pretty=format:'%h%x09%ad%x09%s' <base-tag>..<target-ref>`
- `git log --first-parent --grep='(#' --date=short --pretty=format:'%h%x09%ad%x09%s' <base-tag>..<target-ref>`
- also inspect `--since='24 hours ago'` when main moved during the release.
3. Read linked PRs/issues or diffs for ambiguous commits. Direct commits matter;
infer notes from subject, body, touched files, tests, and nearby commits.
3. Generate the complete contribution record and editorial manifest before
writing grouped prose:
```bash
node .agents/skills/openclaw-changelog-update/scripts/verify-release-notes.mjs \
--base <base-tag> \
--target <target-ref> \
--version <YYYY.M.PATCH> \
--manifest /tmp/openclaw-release-<YYYY.M.PATCH>.json \
--write-ledger
```
- the manifest is the required input to the rewrite, not an after-the-fact
audit; it contains every referenced PR, eligible contributor credit,
inline issue context, every direct commit, and an editorial-eligibility
classification for PRs and direct commits
- for a historical backfill, add `--seed-ref <pre-backfill-ref>` once so
contribution records from the prior changelog are retained even when an
older merged commit omitted its PR number; the verifier excludes records
for work reverted after the base tag, including beta work reverted before
the stable release
- source PR discovery combines merged GitHub commit associations with merged
PR references explicitly present in active commit subjects/bodies so
cherry-picks and squash commits remain accounted for. Resolve every
association page and exclude PRs merged after the target release commit
- read the manifest before editing `### Highlights`, `### Changes`, or
`### Fixes`; do not carry old grouped prose forward without re-auditing it
- inspect linked PRs/issues or diffs for ambiguous commits. Direct commits
are editorial input, not public ledger rows; infer material user outcomes
from subject, body, touched files, tests, and nearby commits
4. Rewrite one stable-base section only:
- use `## YYYY.M.PATCH`
- do not create beta-specific headings
@@ -44,10 +73,21 @@ attribution.
section instead of deleting them
5. Section shape:
- `### Highlights`: 5-8 bullets, broad user wins first
- include only a clear user-visible capability or workflow unlock, a
material reliability/safety fix, a broad cross-surface improvement, or
a release-defining integration/compatibility milestone
- every highlight must say what changed for a user in one sentence; use
one user story per bullet and group its supporting PRs
- exclude tests, CI, refactors, docs, catalog churn, and implementation
detail unless the outcome is a material install/update, data-safety, or
widely visible user improvement
- `### Changes`: new capabilities and behavior changes
- `### Fixes`: user-facing fixes first, grouped by impact and surface
- group related changes/fixes by surface and user impact; avoid one bullet
per tiny commit when several commits tell one user-facing story
- `### Complete contribution record`: generated PR-first record after the
grouped prose; it is the exhaustive accounting surface, not a second
release summary
6. Preserve attribution:
- keep `#issue`, `(#PR)`, `Fixes #...`, and `Thanks @...`
- every human-authored merged PR represented by a user-facing entry needs
@@ -62,17 +102,35 @@ attribution.
- multiple `Thanks @...` handles in one bullet are expected; do not drop or
collapse contributor credit just because the note is grouped
- if one grouped bullet covers both direct commits and PRs, keep all PR refs
and thanks, plus any issue refs from the direct commits
- before finalizing, audit the final release-note body:
- extract all `#NNN` refs from the notes
- resolve which refs are PRs and collect human PR authors
- resolve issue refs used as bug/report refs and collect human reporters
- scan represented commits for `Co-authored-by`
- compare those handles to the final `Thanks @...` set
- fix every missing human credit or explicitly record why it is omitted
and thanks, plus any issue refs and human credit from the direct work
- issues remain normal inline `#NNN` references. Do not add a separate
linked-issues inventory. The generated PR record keeps source issues
inline as `Related #NNN` on the PR that shipped them
- when backfilling an older linked-issues inventory, preserve reporter
credit inline for every GitHub-confirmed closing PR relationship. Do not
infer a PR relationship from a generic cross-reference event, invent an
unrelated PR link for a standalone report, or recreate the retired
inventory
- the complete contribution record lists every merged source PR exactly once
as `**PR #NNN**`; source PRs include GitHub commit associations and merged
PR references explicitly present in active commit subjects/bodies. It
preserves author/co-author credit and any issue references in the original
title
- direct commits remain in the manifest with GitHub-resolved author,
co-author, issue, and editorial-eligibility data. They inform grouped
prose but are never rendered as a public `#### Direct commits` dump. Add
direct-commit credit to a grouped bullet only when it shares an explicit
closing issue reference or at least two distinctive subject terms
- the verifier rejects `docs`, `test`, `refactor`, `ci`, `build`, `chore`,
and `style` PRs in Highlights, Changes, or Fixes. Keep those internal
contributions in the complete PR record, but do not give them editorial
release-note space
- classify internal-only work from conventional prefixes and clear title
signals such as `QA`, `test`, `docs`, `refactor`, `lint`, or `CI`; an
untyped title is not automatically editorial
- do not add GHSA references, advisory IDs, or security advisory slugs to
changelog entries or GitHub release-note text unless explicitly requested
- never thank bots, `@openclaw`, `@clawsweeper`, or `@steipete`
- never thank bots, `@claude`, `@openclaw`, `@clawsweeper`, or `@steipete`
- do not use GitHub's release contributor count as the source of truth; the
changelog must carry the complete human credit set itself
7. Sorting preference:
@@ -91,36 +149,50 @@ attribution.
- if any compatibility `removeAfter` is on/before release date, resolve it
or explicitly record the blocker before shipping
10. Validate and ship:
- generate and verify the complete contribution ledger before committing:
```bash
node .agents/skills/openclaw-changelog-update/scripts/verify-release-notes.mjs \
--base <base-tag> \
--target <target-ref> \
--version <YYYY.M.PATCH> \
--write-ledger
```
- the command fails when any `#NNN` reference in release history or the
rendered release section is absent from the ledger, when reverted work is
presented as shipped, or when an eligible PR author, issue reporter, or
known co-author is missing from that entry's `Thanks @...` credit
- after the GitHub release or prerelease is published, verify every matching
release page against the same source section:
```bash
node .agents/skills/openclaw-changelog-update/scripts/verify-release-notes.mjs \
--base <base-tag> \
--target <target-ref> \
--version <YYYY.M.PATCH> \
--release-tag v<YYYY.M.PATCH> \
--check-github
```
- add one `--release-tag` for every beta and stable page in the train; a
`### Release verification` tail is permitted, but any other body drift
fails the check; the GitHub body must begin with the complete
`## YYYY.M.PATCH` changelog section, including its heading
- `git diff --check`
- for docs/changelog-only changes, no broad tests are required
- commit with `scripts/committer "docs(changelog): refresh YYYY.M.PATCH notes" CHANGELOG.md`
- push, pull/rebase if needed, then branch/rebase release from latest `main`
- after the manifest-driven rewrite, regenerate and verify the complete
contribution record before committing:
```bash
node .agents/skills/openclaw-changelog-update/scripts/verify-release-notes.mjs \
--base <base-tag> \
--target <target-ref> \
--version <YYYY.M.PATCH> \
--manifest /tmp/openclaw-release-<YYYY.M.PATCH>.json \
--write-ledger
```
- the command fails when any `#NNN` reference in release history or the
rendered release section cannot resolve, when reverted work is presented
as shipped, when a source PR is absent from the contribution record, when
direct commits are rendered as a public record dump, when non-editorial
PRs appear in grouped prose, or when an eligible PR author or known
co-author is missing from that PR's `Thanks @...` credit
- when grouped prose names a PR, that same bullet must retain every
contributor and linked-reporter credit from its generated PR record
- unqualified `#NNN` references resolve against `openclaw/openclaw`;
cross-repository references such as `openclaw/imsg#141` remain literal
text and must not be rewritten as local issue links
- after the GitHub release or prerelease is published, verify every matching
release page against the same source section:
```bash
node .agents/skills/openclaw-changelog-update/scripts/verify-release-notes.mjs \
--base <base-tag> \
--target <target-ref> \
--version <YYYY.M.PATCH> \
--release-tag v<YYYY.M.PATCH> \
--check-github
```
- add one `--release-tag` for every beta and stable page in the train; a
`### Release verification` tail is permitted, but any other body drift
fails the check; the GitHub body must begin with the complete
`## YYYY.M.PATCH` changelog section, including its heading
- GitHub release bodies are limited to 125,000 characters. If the complete
source section plus an existing verification tail exceeds that limit, keep
the source section intact and omit the tail; never truncate the
contribution record
- `git diff --check`
- for docs/changelog-only changes, no broad tests are required
- commit with `scripts/committer "docs(changelog): refresh YYYY.M.PATCH notes" CHANGELOG.md`
- push, pull/rebase if needed, then branch/rebase release from latest `main`
## Quota / API Outage Rule

View File

@@ -249,12 +249,20 @@ Stable publication is not complete until `main` carries the actual shipped relea
section from history, not existing notes. Use the last reachable stable or
beta release tag as the base, then inspect every commit through the target
release SHA.
- Generate `$openclaw-changelog-update`'s full contribution manifest before
the editorial rewrite. It is the required source for `### Highlights`,
`### Changes`, and `### Fixes`; do not preserve old grouped prose without
comparing it to the manifest's PRs, contributors, direct commits, and
unlinked commits.
- The changelog rewrite is not optional for beta reruns: any `beta.N` after a
rebase or backport must refresh the same stable-base `## YYYY.M.PATCH` section
before the new version/tag commit.
- Include both merged PR commits and direct commits on `main`. Direct commits
matter: infer notes from their subject, body, touched files, linked issues,
tests, and nearby code when no PR body exists.
- Keep direct commits in the generated manifest and use them to shape grouped
user outcomes, but never dump them into `CHANGELOG.md` or GitHub release
bodies. The public complete record is PR-first and exhaustive for PRs.
- Prefer PR bodies, issue links, review proof, and commit bodies over commit
subjects alone. If a commit fixed an issue directly, the commit body should
name the user-visible behavior, affected surface, issue ref, and credited
@@ -270,11 +278,31 @@ Stable publication is not complete until `main` carries the actual shipped relea
`#issue`, `(#PR)`, `Fixes #...`, and every human `Thanks @...` handle.
Multiple thanks in one bullet are expected when multiple contributor PRs are
grouped.
- Highlights earn their place only when they are a visible capability/workflow
unlock, a material reliability or safety repair, a broad user-facing
improvement, or a release-defining integration/compatibility change. Keep
five to eight user-outcome bullets; omit tests, CI, refactors, docs, and
implementation trivia unless their outcome materially affects users.
- Do not give `docs`, `test`, `refactor`, `ci`, `build`, `chore`, or `style`
PRs/direct commits their own Highlights, Changes, or Fixes entry. They remain
accounted for in the PR record or manifest, but are not product release
content. Treat explicit internal title signals such as `QA`, `lint`, or
`testing` the same way even when the PR has no conventional prefix.
- Use the generated `### Complete contribution record` as PR-first accounting:
every merged source PR appears once with author/co-author credit, including
PRs identified only by an explicit active-commit `#NNN` reference after a
cherry-pick or squash. Keep issues inline as `#NNN` in titles and grouped
prose; do not create a linked-issues inventory or a direct-commit listing.
When grouped prose names a PR, keep every contributor and linked-reporter
credit from that PR's record on the same bullet.
- Changelog entries should be user-facing, not internal release-process notes.
- GitHub release and prerelease bodies must use the full matching
`CHANGELOG.md` version section, not highlights or an excerpt. When creating
or editing a release, extract from `## YYYY.M.PATCH` through the line before the
next level-2 heading and use that complete block as the release notes.
- GitHub limits release bodies to 125,000 characters. If a historical
`### Release verification` tail would exceed that cap, omit the tail and keep
the complete changelog section; do not truncate the contribution record.
- Before publishing or closing a release, run
`$openclaw-changelog-update`'s `verify-release-notes.mjs` with every stable
and beta release tag in the train. Do not publish or leave a page live when

View File

@@ -20,7 +20,7 @@ paths:
- src/agents/tools/web-shared.ts
- src/plugin-sdk/ssrf-policy.ts
- src/web-fetch
- src/web/provider-runtime-shared.ts
- packages/web-content-core/src/provider-runtime-shared.ts
- packages/memory-host-sdk/src/host/ssrf-policy.ts
- packages/net-policy/src

View File

@@ -16,7 +16,7 @@ query-filters:
paths:
- src/web-fetch
- src/web-search
- src/web/provider-runtime-shared.ts
- packages/web-content-core/src/provider-runtime-shared.ts
- src/media
- src/media-understanding
- src/image-generation

View File

@@ -571,83 +571,6 @@ jobs:
retention-days: 14
if-no-files-found: warn
run_smoke_ci_crabline:
name: Run smoke-ci profile through Crabline with Convex leases
needs: [authorize_actor, validate_selected_ref]
runs-on: blacksmith-16vcpu-ubuntu-2404
timeout-minutes: 45
environment: qa-live-shared
steps:
- name: Checkout selected ref
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
persist-credentials: false
ref: ${{ needs.validate_selected_ref.outputs.selected_revision }}
fetch-depth: 1
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
install-bun: "true"
- name: Validate required QA credential env
env:
OPENCLAW_QA_CONVEX_SITE_URL: ${{ secrets.OPENCLAW_QA_CONVEX_SITE_URL }}
OPENCLAW_QA_CONVEX_SECRET_CI: ${{ secrets.OPENCLAW_QA_CONVEX_SECRET_CI }}
shell: bash
run: |
set -euo pipefail
require_var() {
local key="$1"
if [[ -z "${!key:-}" ]]; then
echo "Missing required ${key}." >&2
exit 1
fi
}
require_var OPENCLAW_QA_CONVEX_SITE_URL
require_var OPENCLAW_QA_CONVEX_SECRET_CI
- name: Build private QA runtime
env:
NODE_OPTIONS: --max-old-space-size=12288
run: pnpm build
- name: Run smoke-ci Crabline profile
id: run_lane
shell: bash
env:
OPENCLAW_QA_CONVEX_SITE_URL: ${{ secrets.OPENCLAW_QA_CONVEX_SITE_URL }}
OPENCLAW_QA_CONVEX_SECRET_CI: ${{ secrets.OPENCLAW_QA_CONVEX_SECRET_CI }}
OPENCLAW_QA_CREDENTIAL_ACQUIRE_TIMEOUT_MS: "1800000"
OPENCLAW_QA_CREDENTIAL_ROLE: ci
OPENCLAW_QA_CREDENTIAL_SOURCE: convex
OPENCLAW_QA_REDACT_PUBLIC_METADATA: "1"
run: |
set -euo pipefail
output_dir=".artifacts/qa-e2e/smoke-ci-crabline-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
echo "output_dir=${output_dir}" >> "$GITHUB_OUTPUT"
pnpm openclaw qa run \
--repo-root . \
--output-dir "${output_dir}" \
--qa-profile smoke-ci \
--provider-mode mock-openai \
--fast
- name: Upload smoke-ci Crabline artifacts
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: qa-smoke-ci-crabline-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ steps.run_lane.outputs.output_dir }}
retention-days: 14
if-no-files-found: warn
run_live_discord:
name: Run Discord live QA lane with Convex leases
needs: [authorize_actor, validate_selected_ref]

View File

@@ -37,6 +37,11 @@ on:
required: false
default: false
type: boolean
run_windows_ci:
description: "Run the focused Windows-native CI test shard after probing"
required: false
default: false
type: boolean
permissions:
contents: read
@@ -80,10 +85,21 @@ jobs:
env:
ENABLE_WSL2_FEATURES: ${{ inputs.enable_wsl2_features }}
IMPORT_UBUNTU_WSL2: ${{ inputs.import_ubuntu_wsl2 }}
UBUNTU_WSL_ROOTFS_URL: https://cloud-images.ubuntu.com/wsl/releases/24.04/current/ubuntu-noble-wsl-amd64-wsl.rootfs.tar.gz
run: |
$ErrorActionPreference = "Continue"
$ok = $false
$restartRequired = $false
function Resolve-UbuntuWslRootfsUrl {
$osArch = ([System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture).ToString().ToLowerInvariant()
switch ($osArch) {
"x64" { $wslArch = "amd64" }
"arm64" { $wslArch = "arm64" }
default { throw "Unsupported Windows architecture for Ubuntu WSL rootfs: $osArch" }
}
Write-Host "ubuntu_wsl_rootfs_arch=$wslArch"
"https://cloud-images.ubuntu.com/wsl/releases/24.04/current/ubuntu-noble-wsl-$wslArch-wsl.rootfs.tar.gz"
}
function Invoke-WslText {
param([string[]] $Arguments)
@@ -112,9 +128,15 @@ jobs:
Write-Host "wsl.exe=$($wsl.Source)"
if ($env:ENABLE_WSL2_FEATURES -eq "true") {
Write-Host "enable_wsl2_features=true"
foreach ($feature in @("Microsoft-Windows-Subsystem-Linux", "VirtualMachinePlatform", "HypervisorPlatform", "Microsoft-Hyper-V-All")) {
foreach ($feature in @("Microsoft-Windows-Subsystem-Linux", "VirtualMachinePlatform", "HypervisorPlatform")) {
dism.exe /online /enable-feature /featurename:$feature /all /norestart
Write-Host "enable_feature_${feature}_exit=$LASTEXITCODE"
if ($LASTEXITCODE -eq 3010) {
$restartRequired = $true
}
}
if ($restartRequired) {
Write-Warning "wsl2_restart_required=true; Windows optional feature changes require a runner reboot before WSL2 can be imported."
}
}
@@ -127,12 +149,13 @@ jobs:
Write-Host "wsl_list_exit=$($list.Code)"
$distros = @(Get-WslDistros)
if ($distros.Count -eq 0 -and $env:IMPORT_UBUNTU_WSL2 -eq "true") {
if ($distros.Count -eq 0 -and $env:IMPORT_UBUNTU_WSL2 -eq "true" -and -not $restartRequired) {
Write-Host "import_ubuntu_wsl2=true"
$wslRoot = "C:\wsl\UbuntuProbe"
$rootfs = "C:\wsl\ubuntu-noble-wsl.rootfs.tar.gz"
$rootfsUrl = Resolve-UbuntuWslRootfsUrl
New-Item -ItemType Directory -Force -Path @((Split-Path -Parent $rootfs), $wslRoot) | Out-Null
Invoke-WebRequest -Uri $env:UBUNTU_WSL_ROOTFS_URL -OutFile $rootfs -UseBasicParsing
Invoke-WebRequest -Uri $rootfsUrl -OutFile $rootfs -UseBasicParsing
$import = Invoke-WslText -Arguments @("--import", "UbuntuProbe", $wslRoot, $rootfs, "--version", "2")
Write-Host $import.Text
Write-Host "wsl_import_exit=$($import.Code)"
@@ -140,12 +163,16 @@ jobs:
Write-Host $list.Text
Write-Host "wsl_list_after_import_exit=$($list.Code)"
$distros = @(Get-WslDistros)
} elseif ($distros.Count -eq 0 -and $env:IMPORT_UBUNTU_WSL2 -eq "true" -and $restartRequired) {
Write-Warning "import_ubuntu_wsl2=skipped_restart_required"
}
if ($distros.Count -gt 0) {
$distro = $distros[0]
Write-Host "wsl_probe_distro=$distro"
$exec = Invoke-WslText -Arguments @("-d", $distro, "--exec", "bash", "-lc", 'set -euo pipefail; uname -a; if [ -f /etc/os-release ]; then sed -n "1,8p" /etc/os-release; fi')
} elseif ($restartRequired) {
$exec = [pscustomobject]@{ Code = 1; Text = "wsl_exec_skipped=restart_required" }
} else {
$exec = Invoke-WslText -Arguments @("--exec", "bash", "-lc", 'set -euo pipefail; uname -a; if [ -f /etc/os-release ]; then sed -n "1,8p" /etc/os-release; fi')
}
@@ -158,17 +185,99 @@ jobs:
if ($ok) {
"wsl2_ok=true" >> $env:GITHUB_OUTPUT
"wsl2_restart_required=false" >> $env:GITHUB_OUTPUT
"OPENCLAW_WSL2_PROBE_OK=true" >> $env:GITHUB_ENV
"OPENCLAW_WSL2_RESTART_REQUIRED=false" >> $env:GITHUB_ENV
Write-Host "wsl2_ok=true"
} else {
"wsl2_ok=false" >> $env:GITHUB_OUTPUT
"wsl2_restart_required=$($restartRequired.ToString().ToLowerInvariant())" >> $env:GITHUB_OUTPUT
"OPENCLAW_WSL2_PROBE_OK=false" >> $env:GITHUB_ENV
"OPENCLAW_WSL2_RESTART_REQUIRED=$($restartRequired.ToString().ToLowerInvariant())" >> $env:GITHUB_ENV
Write-Warning "wsl2_ok=false"
}
exit 0
- name: Try to exclude workspace from Windows Defender (best-effort)
if: ${{ inputs.run_windows_ci }}
shell: pwsh
run: |
$cmd = Get-Command Add-MpPreference -ErrorAction SilentlyContinue
if (-not $cmd) {
Write-Host "Add-MpPreference not available, skipping Defender exclusions."
exit 0
}
try {
Add-MpPreference -ExclusionPath "$env:GITHUB_WORKSPACE" -ErrorAction Stop
Add-MpPreference -ExclusionProcess "node.exe" -ErrorAction Stop
Write-Host "Defender exclusions applied."
} catch {
Write-Warning "Failed to apply Defender exclusions, continuing. $($_.Exception.Message)"
}
- name: Setup Node.js
if: ${{ inputs.run_windows_ci }}
shell: bash
env:
REQUESTED_NODE_VERSION: "22.x"
run: |
set -euo pipefail
source .github/actions/setup-pnpm-store-cache/ensure-node.sh
openclaw_ensure_node "$REQUESTED_NODE_VERSION"
- name: Setup pnpm
if: ${{ inputs.run_windows_ci }}
uses: ./.github/actions/setup-pnpm-store-cache
with:
node-version: 22.x
- name: Runtime versions
if: ${{ inputs.run_windows_ci }}
shell: bash
run: |
node -v
npm -v
pnpm -v
- name: Capture node path
if: ${{ inputs.run_windows_ci }}
shell: bash
run: |
node_bin="$(dirname "$(node -p 'process.execPath')")"
if command -v cygpath >/dev/null 2>&1; then
node_bin="$(cygpath -u "$node_bin")"
fi
echo "NODE_BIN=$node_bin" >> "$GITHUB_ENV"
- name: Install dependencies
if: ${{ inputs.run_windows_ci }}
shell: bash
env:
CI: true
run: |
export PATH="$NODE_BIN:$PATH"
which node
node -v
pnpm -v
pnpm install --frozen-lockfile --prefer-offline --ignore-scripts=false --config.engine-strict=false --config.enable-pre-post-scripts=true --config.side-effects-cache=true || pnpm install --frozen-lockfile --prefer-offline --ignore-scripts=false --config.engine-strict=false --config.enable-pre-post-scripts=true --config.side-effects-cache=true
- name: Run Windows CI tests
if: ${{ inputs.run_windows_ci }}
shell: bash
env:
CI: true
NODE_OPTIONS: --max-old-space-size=8192
OPENCLAW_TEST_SKIP_FULL_EXTENSIONS_SHARD: 1
OPENCLAW_VITEST_MAX_WORKERS: 1
run: |
set -euo pipefail
export PATH="$NODE_BIN:$PATH"
pnpm test:windows:ci
- name: Keep runner alive for SSH inspection
if: ${{ always() && !cancelled() }}
env:
KEEPALIVE_MINUTES: ${{ inputs.keepalive_minutes }}
run: |
@@ -185,7 +294,7 @@ jobs:
}
- name: Enforce WSL2 requirement
if: ${{ inputs.require_wsl2 }}
if: ${{ always() && !cancelled() && inputs.require_wsl2 }}
run: |
if ($env:OPENCLAW_WSL2_PROBE_OK -ne "true") {
Write-Error "WSL2 probe failed or WSL2 is unavailable on this Windows runner."

File diff suppressed because it is too large Load Diff

View File

@@ -69,6 +69,17 @@ Generate raw Google Play screenshots:
pnpm android:screenshots
```
To make screenshot capture own emulator startup, pass a named AVD:
```bash
ANDROID_SCREENSHOT_AVD=OpenClaw_QA_API35 pnpm android:screenshots
```
The screenshot script uses one connected ADB device when available. If none is
connected and `ANDROID_SCREENSHOT_AVD` is set, it boots that emulator
headlessly, waits for Android to finish booting, disables animations, captures
the screenshots, then shuts down the emulator it started.
`pnpm android:release:archive` builds signed release artifacts into `apps/android/build/release-artifacts/` and writes `.sha256` checksum files:
- Play build: `openclaw-<version>-play-release.aab`

View File

@@ -49,7 +49,7 @@ Recommended workflow:
3. Update `apps/android/CHANGELOG.md`, then run `pnpm android:version:sync` again if needed.
4. Run `MATCH_PASSWORD=<signing repo password> pnpm android:release:signing:sync:pull` to materialize encrypted Android signing assets from `apps-signing`.
5. Run `pnpm android:release:preflight` to validate Play auth, signing, synced versioning, and release notes.
6. Run `pnpm android:screenshots` to refresh raw Google Play screenshots.
6. Run `ANDROID_SCREENSHOT_AVD=<avd-name> pnpm android:screenshots` to refresh raw Google Play screenshots with a script-managed emulator, or run `pnpm android:screenshots` when exactly one ADB device is already connected.
7. Run `pnpm android:release:archive` to produce the signed Play AAB and third-party APK.
8. Run `pnpm android:release:upload` to upload metadata, screenshots, and the Play AAB to Google Play internal testing.
9. Promote to production manually in Google Play Console.

View File

@@ -223,10 +223,11 @@ class NodeForegroundService : Service() {
internal fun foregroundServiceTypesForVoiceMode(mode: VoiceCaptureMode): Int {
val base = ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE
return if (mode == VoiceCaptureMode.TalkMode) {
base or ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE
} else {
base
return when (mode) {
VoiceCaptureMode.Off -> base
VoiceCaptureMode.ManualMic,
VoiceCaptureMode.TalkMode,
-> base or ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE
}
}

View File

@@ -1396,8 +1396,9 @@ class NodeRuntime(
mode: VoiceCaptureMode,
persistManualMic: Boolean = true,
) {
if (mode == VoiceCaptureMode.TalkMode && !hasRecordAudioPermission()) {
if (mode.requiresMicrophonePermission && !hasRecordAudioPermission()) {
_voiceCaptureMode.value = VoiceCaptureMode.Off
prefs.setVoiceMicEnabled(false)
externalAudioCaptureActive.value = false
return
}
@@ -1468,6 +1469,9 @@ class NodeRuntime(
}
}
private val VoiceCaptureMode.requiresMicrophonePermission: Boolean
get() = this == VoiceCaptureMode.ManualMic || this == VoiceCaptureMode.TalkMode
fun refreshGatewayConnection() {
val endpoint = connectedEndpoint
if (endpoint == null) {

View File

@@ -114,16 +114,12 @@ private fun ConnectScene() {
@Composable
private fun ChatScene() {
ChatBubble(label = "You", text = "Summarize the launch checklist before I start the release.")
ChatBubble(label = "You", text = "Hi Molty, are you there?")
ChatBubble(
label = "OpenClaw",
text = "Android archive, Play metadata, and internal testing upload are ready. Screenshots are being refreshed now.",
label = "Molty",
text = "Always. Lurking in the shadows, exfoliating.",
raised = true,
)
CompactList(
title = "Working set",
rows = listOf("Release notes", "Play bundle", "Device screenshots"),
)
}
@Composable

View File

@@ -376,6 +376,25 @@ class GatewayBootstrapAuthTest {
assertNull(authStore.loadToken(deviceId, "operator"))
}
@Test
fun restoredManualMicWithoutRecordAudioClearsStalePreference() {
val app = RuntimeEnvironment.getApplication()
shadowOf(app).denyPermissions(Manifest.permission.RECORD_AUDIO)
val securePrefs =
app.getSharedPreferences(
"openclaw.node.secure.test.${UUID.randomUUID()}",
android.content.Context.MODE_PRIVATE,
)
val prefs = SecurePrefs(app, securePrefsOverride = securePrefs)
prefs.setVoiceMicEnabled(true)
val runtime = NodeRuntime(app, prefs)
assertEquals(VoiceCaptureMode.Off, runtime.voiceCaptureMode.value)
assertFalse(prefs.voiceMicEnabled.value)
assertFalse(readField<MutableStateFlow<Boolean>>(runtime, "externalAudioCaptureActive").value)
}
@Test
fun talkPttStart_cleansPreparedCaptureWhenBeginFails() =
runBlocking {

View File

@@ -32,13 +32,13 @@ class NodeForegroundServiceTest {
}
@Test
fun foregroundServiceTypesForVoiceMode_addsMicrophoneOnlyForTalkMode() {
fun foregroundServiceTypesForVoiceMode_addsMicrophoneForActiveCaptureModes() {
assertEquals(
ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE,
foregroundServiceTypesForVoiceMode(VoiceCaptureMode.Off),
)
assertEquals(
ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE,
ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE or ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE,
foregroundServiceTypesForVoiceMode(VoiceCaptureMode.ManualMic),
)
assertEquals(

View File

@@ -6,6 +6,7 @@ require "supply/client"
default_platform(:android)
ANDROID_FASTLANE_ROOT = File.expand_path(__dir__, Dir.pwd)
DEFAULT_PLAY_PACKAGE_NAME = "ai.openclaw.app"
DEFAULT_PLAY_TRACK = "internal"
DEFAULT_PLAY_RELEASE_STATUS = "completed"
@@ -35,7 +36,7 @@ def env_present?(value)
end
def android_root
File.expand_path("..", __dir__)
File.expand_path("..", ANDROID_FASTLANE_ROOT)
end
def repo_root
@@ -147,7 +148,7 @@ def sync_android_versioning!
end
def android_release_notes_path
File.join(__dir__, "metadata", "android", "en-US", "release_notes.txt")
File.join(ANDROID_FASTLANE_ROOT, "metadata", "android", "en-US", "release_notes.txt")
end
def validate_android_release_notes!
@@ -157,7 +158,7 @@ def validate_android_release_notes!
end
def android_changelog_path(version_code)
File.join(__dir__, "metadata", "android", "en-US", "changelogs", "#{version_code}.txt")
File.join(ANDROID_FASTLANE_ROOT, "metadata", "android", "en-US", "changelogs", "#{version_code}.txt")
end
def sync_android_changelog!(version_code)
@@ -170,7 +171,7 @@ def sync_android_changelog!(version_code)
end
def play_metadata_path
File.join(__dir__, "metadata", "android")
File.join(ANDROID_FASTLANE_ROOT, "metadata", "android")
end
def play_screenshot_paths
@@ -303,7 +304,7 @@ def upload_play_store_build!(version_metadata, upload_metadata: false, upload_im
)
end
load_env_file(File.join(__dir__, ".env"))
load_env_file(File.join(ANDROID_FASTLANE_ROOT, ".env"))
platform :android do
desc "Validate Google Play API credentials"

View File

@@ -65,9 +65,14 @@ pnpm android:release:archive
Generate deterministic Google Play screenshots:
```bash
pnpm android:screenshots
ANDROID_SCREENSHOT_AVD=OpenClaw_QA_API35 pnpm android:screenshots
```
If exactly one ADB device is already connected, `pnpm android:screenshots`
uses it. With `ANDROID_SCREENSHOT_AVD` or `--avd <name>`, the script can boot a
headless emulator, wait for boot completion, stabilize animation settings,
capture screenshots, and shut down only the emulator it started.
Upload metadata, release notes, and the Play AAB to the internal testing track:
```bash

View File

@@ -1,4 +1,4 @@
b7ec57a4f38bf44677870fd9a8347be83f3f23a25a73d97931406f0eff572181 config-baseline.json
99d506f05de601e5b45c98f302650c8608d1e2bb3dcea11bf97881c1263659ac config-baseline.core.json
e78623d6eace69e46950cd5d9a5cf14aa910dac1ecdf9d054a0bd9999e936061 config-baseline.json
5ecafa3c9a59fc0675f964f6e3238b2f20625376ebad1835278c5dd7323770d3 config-baseline.core.json
2d735389858305509528e74329b6f8c65d311e1471c3b4e91dc17aaab8e63a80 config-baseline.channel.json
a973af69b02a27b097b54e49886dd57dbebbc95e2ab29b0c7e222a9f35a105d8 config-baseline.plugin.json
7c2c51b795d32e4c4c325080d59fec8fd11317c41db7db642f70e436779738bc config-baseline.plugin.json

View File

@@ -1,2 +1,2 @@
c84eab270f19d11a807ce71e783d35ee95a7620295dbffcca7fff31dacfcc882 plugin-sdk-api-baseline.json
55656396a5f1941af61603402c43e23e0ffc90003e7efa7c1857c4541a0f1bb4 plugin-sdk-api-baseline.jsonl
dd892e0085aa61eb8b85f3e6591039accc9a051af923874499f196ee621c5545 plugin-sdk-api-baseline.json
2ea01cb391f2adc0e1d59400bf245e8e9f110a37d961546e0c7d6685c4054400 plugin-sdk-api-baseline.jsonl

View File

@@ -529,7 +529,7 @@ Notes:
- `socketMode` is ignored in HTTP Request URL mode.
- Base `channels.slack.socketMode` settings apply to all Slack accounts unless overridden. Per-account overrides use `channels.slack.accounts.<accountId>.socketMode`; because this is an object override, include every socket tuning field you want for that account.
- Only `clientPingTimeout` has an OpenClaw default (`15000`). `serverPingTimeout` and `pingPongLoggingEnabled` are passed to the Slack SDK only when configured.
- Socket Mode restart backoff starts around 2 seconds and caps around 30 seconds. Consecutive recoverable start/start-wait failures stop after 12 attempts; after a successful connection, later recoverable disconnects start a fresh retry cycle. Non-recoverable Slack auth errors such as `invalid_auth`, revoked tokens, or missing scopes fail fast instead of retrying forever.
- Socket Mode restart backoff starts around 2 seconds and caps around 30 seconds. Recoverable start, start-wait, and disconnect failures retry until the channel stops. Permanent account and credential errors such as invalid auth, revoked tokens, or missing scopes fail fast instead of retrying forever.
## Manifest and scope checklist

View File

@@ -189,6 +189,7 @@ Use `image` for generation, edit, and description.
openclaw infer image generate --prompt "friendly lobster illustration" --json
openclaw infer image generate --prompt "cinematic product photo of headphones" --json
openclaw infer image generate --model openai/gpt-image-1.5 --output-format png --background transparent --prompt "simple red circle sticker on a transparent background" --json
openclaw infer image generate --model openai/gpt-image-2 --quality low --openai-moderation low --prompt "low-cost draft poster" --json
openclaw infer image generate --prompt "slow image backend" --timeout-ms 180000 --json
openclaw infer image edit --file ./logo.png --model openai/gpt-image-1.5 --output-format png --background transparent --prompt "keep the logo, remove the background" --json
openclaw infer image edit --file ./poster.png --prompt "make this a vertical story ad" --size 2160x3840 --aspect-ratio 9:16 --resolution 4K --json
@@ -209,6 +210,9 @@ Notes:
`--model openai/gpt-image-1.5` for transparent-background OpenAI PNG output;
`--openai-background` remains available as an OpenAI-specific alias. Providers
that do not declare background support report the hint as an ignored override.
- Use `--quality low|medium|high|auto` for providers that support image quality
hints, including OpenAI. OpenAI also accepts `--openai-moderation low|auto` for
the provider-specific moderation hint.
- Use `image providers --json` to verify which bundled image providers are
discoverable, configured, selected, and which generation/edit capabilities
each provider exposes.

View File

@@ -84,7 +84,8 @@ Set `memorySearch.provider` to switch away from OpenAI.
OpenClaw indexes `MEMORY.md` and `memory/*.md` into chunks (~400 tokens with
80-token overlap) and stores them in a per-agent SQLite database.
- **Index location:** `~/.openclaw/memory/<agentId>.sqlite`
- **Index location:** the owning agent database at
`~/.openclaw/agents/<agentId>/agent/openclaw-agent.sqlite`
- **Storage maintenance:** SQLite WAL sidecars are bounded with periodic and
shutdown checkpoints.
- **File watching:** changes to memory files trigger a debounced reindex (1.5s).

View File

@@ -278,51 +278,28 @@ messages and normalizes `stats.cached` into `cacheRead`; legacy
- Example models: `vercel-ai-gateway/anthropic/claude-opus-4.6`, `vercel-ai-gateway/moonshotai/kimi-k2.6`
- CLI: `openclaw onboard --auth-choice ai-gateway-api-key`
### Kilo Gateway
- Provider: `kilocode`
- Auth: `KILOCODE_API_KEY`
- Example model: `kilocode/kilo/auto`
- CLI: `openclaw onboard --auth-choice kilocode-api-key`
- Base URL: `https://api.kilo.ai/api/gateway/`
- Static fallback catalog ships `kilocode/kilo/auto`; live `https://api.kilo.ai/api/gateway/models` discovery can expand the runtime catalog further.
- Exact upstream routing behind `kilocode/kilo/auto` is owned by Kilo Gateway, not hard-coded in OpenClaw.
See [/providers/kilocode](/providers/kilocode) for setup details.
### Other bundled provider plugins
| Provider | Id | Auth env | Example model |
| --------------------------------------- | -------------------------------- | ------------------------------------------------------------ | ---------------------------------------------------------- |
| BytePlus | `byteplus` / `byteplus-plan` | `BYTEPLUS_API_KEY` | `byteplus-plan/ark-code-latest` |
| Cerebras | `cerebras` | `CEREBRAS_API_KEY` | `cerebras/zai-glm-4.7` |
| Cohere | `cohere` | `COHERE_API_KEY` | `cohere/command-a-03-2025` |
| Cloudflare AI Gateway | `cloudflare-ai-gateway` | `CLOUDFLARE_AI_GATEWAY_API_KEY` | - |
| DeepInfra | `deepinfra` | `DEEPINFRA_API_KEY` | `deepinfra/deepseek-ai/DeepSeek-V4-Flash` |
| DeepSeek | `deepseek` | `DEEPSEEK_API_KEY` | `deepseek/deepseek-v4-flash` |
| GitHub Copilot | `github-copilot` | `COPILOT_GITHUB_TOKEN` / `GH_TOKEN` / `GITHUB_TOKEN` | - |
| GMI Cloud | `gmi` | `GMI_API_KEY` | `gmi/google/gemini-3.1-flash-lite` |
| Groq | `groq` | `GROQ_API_KEY` | - |
| Hugging Face Inference | `huggingface` | `HUGGINGFACE_HUB_TOKEN` or `HF_TOKEN` | `huggingface/deepseek-ai/DeepSeek-R1` |
| Kilo Gateway | `kilocode` | `KILOCODE_API_KEY` | `kilocode/kilo/auto` |
| Kimi Coding | `kimi` | `KIMI_API_KEY` or `KIMICODE_API_KEY` | `kimi/kimi-for-coding` |
| MiniMax | `minimax` / `minimax-portal` | `MINIMAX_API_KEY` / `MINIMAX_OAUTH_TOKEN` | `minimax/MiniMax-M3` |
| Mistral | `mistral` | `MISTRAL_API_KEY` | `mistral/mistral-large-latest` |
| Moonshot | `moonshot` | `MOONSHOT_API_KEY` | `moonshot/kimi-k2.6` |
| NVIDIA | `nvidia` | `NVIDIA_API_KEY` | `nvidia/nvidia/nemotron-3-ultra-550b-a55b` |
| NovitaAI | `novita` | `NOVITA_API_KEY` | `novita/deepseek/deepseek-v3-0324` |
| [Ollama Cloud](/providers/ollama-cloud) | `ollama-cloud` | `OLLAMA_API_KEY` | `ollama-cloud/kimi-k2.6` |
| OpenRouter | `openrouter` | OpenRouter OAuth or `OPENROUTER_API_KEY` | `openrouter/auto` |
| Qianfan | `qianfan` | `QIANFAN_API_KEY` | `qianfan/deepseek-v3.2` |
| Qwen Cloud | `qwen` | `QWEN_API_KEY` / `MODELSTUDIO_API_KEY` / `DASHSCOPE_API_KEY` | `qwen/qwen3.5-plus` |
| [Qwen OAuth](/providers/qwen-oauth) | `qwen-oauth` | `QWEN_API_KEY` | `qwen-oauth/qwen3.5-plus` |
| StepFun | `stepfun` / `stepfun-plan` | `STEPFUN_API_KEY` | `stepfun/step-3.5-flash` |
| Together | `together` | `TOGETHER_API_KEY` | `together/meta-llama/Llama-3.3-70B-Instruct-Turbo` |
| Venice | `venice` | `VENICE_API_KEY` | - |
| Vercel AI Gateway | `vercel-ai-gateway` | `AI_GATEWAY_API_KEY` | `vercel-ai-gateway/anthropic/claude-opus-4.6` |
| Volcano Engine (Doubao) | `volcengine` / `volcengine-plan` | `VOLCANO_ENGINE_API_KEY` | `volcengine-plan/ark-code-latest` |
| xAI | `xai` | SuperGrok/X Premium OAuth or `XAI_API_KEY` | `xai/grok-4.3` |
| Xiaomi | `xiaomi` / `xiaomi-token-plan` | `XIAOMI_API_KEY` / `XIAOMI_TOKEN_PLAN_API_KEY` | `xiaomi/mimo-v2-flash` / `xiaomi-token-plan/mimo-v2.5-pro` |
| Provider | Id | Auth env | Example model |
| --------------------------------------- | -------------------------------- | ---------------------------------------------------- | ---------------------------------------------------------- |
| BytePlus | `byteplus` / `byteplus-plan` | `BYTEPLUS_API_KEY` | `byteplus-plan/ark-code-latest` |
| Cohere | `cohere` | `COHERE_API_KEY` | `cohere/command-a-03-2025` |
| GitHub Copilot | `github-copilot` | `COPILOT_GITHUB_TOKEN` / `GH_TOKEN` / `GITHUB_TOKEN` | - |
| Hugging Face Inference | `huggingface` | `HUGGINGFACE_HUB_TOKEN` or `HF_TOKEN` | `huggingface/deepseek-ai/DeepSeek-R1` |
| MiniMax | `minimax` / `minimax-portal` | `MINIMAX_API_KEY` / `MINIMAX_OAUTH_TOKEN` | `minimax/MiniMax-M3` |
| Mistral | `mistral` | `MISTRAL_API_KEY` | `mistral/mistral-large-latest` |
| Moonshot | `moonshot` | `MOONSHOT_API_KEY` | `moonshot/kimi-k2.6` |
| NVIDIA | `nvidia` | `NVIDIA_API_KEY` | `nvidia/nvidia/nemotron-3-ultra-550b-a55b` |
| NovitaAI | `novita` | `NOVITA_API_KEY` | `novita/deepseek/deepseek-v3-0324` |
| [Ollama Cloud](/providers/ollama-cloud) | `ollama-cloud` | `OLLAMA_API_KEY` | `ollama-cloud/kimi-k2.6` |
| OpenRouter | `openrouter` | OpenRouter OAuth or `OPENROUTER_API_KEY` | `openrouter/auto` |
| [Qwen OAuth](/providers/qwen-oauth) | `qwen-oauth` | `QWEN_API_KEY` | `qwen-oauth/qwen3.5-plus` |
| Together | `together` | `TOGETHER_API_KEY` | `together/meta-llama/Llama-3.3-70B-Instruct-Turbo` |
| Venice | `venice` | `VENICE_API_KEY` | - |
| Vercel AI Gateway | `vercel-ai-gateway` | `AI_GATEWAY_API_KEY` | `vercel-ai-gateway/anthropic/claude-opus-4.6` |
| Volcano Engine (Doubao) | `volcengine` / `volcengine-plan` | `VOLCANO_ENGINE_API_KEY` | `volcengine-plan/ark-code-latest` |
| xAI | `xai` | SuperGrok/X Premium OAuth or `XAI_API_KEY` | `xai/grok-4.3` |
| Xiaomi | `xiaomi` / `xiaomi-token-plan` | `XIAOMI_API_KEY` / `XIAOMI_TOKEN_PLAN_API_KEY` | `xiaomi/mimo-v2-flash` / `xiaomi-token-plan/mimo-v2.5-pro` |
#### Quirks worth knowing
@@ -342,9 +319,6 @@ See [/providers/kilocode](/providers/kilocode) for setup details.
<Accordion title="xAI">
Uses the xAI Responses path. The recommended path is SuperGrok/X Premium OAuth; API keys still work via `XAI_API_KEY` or plugin config, and Grok `web_search` reuses the same auth profile before API-key fallback. `grok-4.3` is the bundled default chat model, and `grok-build-0.1` is selectable for build/coding-focused work. `/fast` or `params.fastMode: true` rewrites `grok-3`, `grok-3-mini`, `grok-4`, and `grok-4-0709` to their `*-fast` variants. `tool_stream` defaults on; disable via `agents.defaults.models["xai/<model>"].params.tool_stream=false`.
</Accordion>
<Accordion title="Cerebras">
Ships as the bundled `cerebras` provider plugin. GLM uses `zai-glm-4.7`; OpenAI-compatible base URL is `https://api.cerebras.ai/v1`.
</Accordion>
</AccordionGroup>
## Providers via `models.providers` (custom/base URL)

View File

@@ -68,24 +68,14 @@ pnpm openclaw qa run \
--output-dir .artifacts/qa-e2e/smoke-ci-profile-dispatch
```
Use `smoke-ci` as the CI QA profile: it runs the taxonomy categories selected by
the profile with deterministic mock model providers and the Crabline SDK-backed
channel driver. Use `release` for the Stable/LTS proof lane. When a command also
needs an OpenClaw root profile, put the root profile before the QA command:
Use `smoke-ci` for deterministic no-live-service proof and `release` for the
Stable/LTS proof lane. When a command also needs an OpenClaw root profile, put
the root profile before the QA command:
```bash
pnpm openclaw --profile work qa run --qa-profile smoke-ci
```
QA profiles select the channel driver. `smoke-ci` runs the taxonomy scenario set
through the Crabline SDK-backed channel driver with mock model providers.
`release` uses the native live driver: it runs the Matrix, Telegram, Discord,
Slack, and WhatsApp live lane catalogs, reads each lane's native
`qa-evidence.json`, and writes one merged profile `qa-evidence.json`. Scenarios
set `execution.channel` only when they need a specific live channel; unsupported
channels are reported as missing live coverage instead of falling back to
`qa-channel`.
## Operator flow
The current QA operator flow is a two-pane QA site:
@@ -202,14 +192,7 @@ witness video when `MANTIS_DISCORD_VIEWER_CHROME_PROFILE_DIR` or
environment. That viewer profile is only for visual capture; the pass/fail
decision still comes from the Discord REST oracle.
The scheduled/manual `.github/workflows/qa-live-transports-convex.yml` workflow
uses the same command surface. Its `smoke-ci` job runs the full smoke profile by
default; the Matrix, Telegram, Discord, Slack, and WhatsApp jobs are separate
live transport lanes for upstream service coverage. Scheduled and default manual
runs execute the fast Matrix profile with live frontier credentials, `--fast`,
and `OPENCLAW_QA_MATRIX_NO_REPLY_WINDOW_MS=3000`. Manual `matrix_profile=all`
fans out into the five profile shards so the exhaustive catalog can run in
parallel while keeping one artifact directory per shard.
CI uses the same command surface in `.github/workflows/qa-live-transports-convex.yml`. Scheduled and default manual runs execute the fast Matrix profile with live frontier credentials, `--fast`, and `OPENCLAW_QA_MATRIX_NO_REPLY_WINDOW_MS=3000`. Manual `matrix_profile=all` fans out into the five profile shards so the exhaustive catalog can run in parallel while keeping one artifact directory per shard.
For transport-real Telegram, Discord, Slack, and WhatsApp smoke lanes:
@@ -966,9 +949,7 @@ writes its own `qa-evidence.json`, whose entries are imported into the suite
output and whose artifact paths are resolved relative to that producer
`qa-evidence.json`. When `qa suite` is reached through
`qa run --qa-profile`, the same `qa-evidence.json` also includes the profile
scorecard summary for the selected taxonomy categories. Live profile runs do not
enter `qa suite`; they merge the native `qa-evidence.json` files written by the
live lane catalogs and then attach the same profile scorecard summary.
scorecard summary for the selected taxonomy categories.
Treat it as a discovery aid, not a gate replacement; the selected scenario still needs the right provider mode, live transport, Multipass, Testbox, or release lane for the behavior under test.
For character and style checks, run the same scenario across multiple live model

View File

@@ -1387,10 +1387,7 @@
"clawhub/http-api",
"clawhub/acceptable-usage",
"clawhub/moderation",
"clawhub/security",
"clawhub/security-audits",
"clawhub/content-rights",
"clawhub/plugin-validation-fixes"
"clawhub/security-audits"
]
}
]

View File

@@ -438,7 +438,7 @@ Time format in system prompt. Default: `auto` (OS preference).
- Typical values: `qwen/wan2.6-t2v`, `qwen/wan2.6-i2v`, `qwen/wan2.6-r2v`, `qwen/wan2.6-r2v-flash`, or `qwen/wan2.7-r2v`.
- If omitted, `video_generate` can still infer an auth-backed provider default. It tries the current default provider first, then the remaining registered video-generation providers in provider-id order.
- If you select a provider/model directly, configure the matching provider auth/API key too.
- The bundled Qwen video-generation provider supports up to 1 output video, 1 input image, 4 input videos, 10 seconds duration, and provider-level `size`, `aspectRatio`, `resolution`, `audio`, and `watermark` options.
- The official Qwen video-generation plugin supports up to 1 output video, 1 input image, 4 input videos, 10 seconds duration, and provider-level `size`, `aspectRatio`, `resolution`, `audio`, and `watermark` options.
- `pdfModel`: accepts either a string (`"provider/model"`) or an object (`{ primary, fallbacks }`).
- Used by the `pdf` tool for model routing.
- If omitted, the PDF tool falls back to `imageModel`, then to the resolved session/default model.

View File

@@ -590,7 +590,7 @@ Interactive custom-provider onboarding infers image input for common vision mode
<AccordionGroup>
<Accordion title="Cerebras (GLM 4.7 / GPT OSS)">
The bundled `cerebras` provider plugin can configure this via `openclaw onboard --auth-choice cerebras-api-key`. Use explicit provider config only when overriding defaults.
The official external `cerebras` provider plugin can configure this via `openclaw onboard --auth-choice cerebras-api-key`. Use explicit provider config only when overriding defaults.
```json5
{

View File

@@ -340,7 +340,7 @@ session establishment, not on every turn; use `/new`, `/reset`, or a gateway
restart after changing native plugin config.
- `plugins.entries.firecrawl.config.webFetch`: Firecrawl web-fetch provider settings.
- `apiKey`: Firecrawl API key (accepts SecretRef). Falls back to `plugins.entries.firecrawl.config.webSearch.apiKey`, legacy `tools.web.fetch.firecrawl.apiKey`, or `FIRECRAWL_API_KEY` env var.
- `apiKey`: Optional Firecrawl API key for higher limits (accepts SecretRef). Falls back to `plugins.entries.firecrawl.config.webSearch.apiKey`, legacy `tools.web.fetch.firecrawl.apiKey`, or `FIRECRAWL_API_KEY` env var.
- `baseUrl`: Firecrawl API base URL (default: `https://api.firecrawl.dev`; self-hosted overrides must target private/internal endpoints).
- `onlyMainContent`: extract only the main content from pages (default: `true`).
- `maxAgeMs`: maximum cache age in milliseconds (default: `172800000` / 2 days).

View File

@@ -857,7 +857,7 @@ lives on the [First-run FAQ](/help/faq-first-run).
- If you use allowlists, add `web_search`/`web_fetch`/`x_search` or `group:web`.
- `web_fetch` is enabled by default (unless explicitly disabled).
- If `tools.web.fetch.provider` is omitted, OpenClaw auto-detects the first ready fetch fallback provider from available credentials. Today the bundled provider is Firecrawl.
- If `tools.web.fetch.provider` is omitted, OpenClaw auto-detects the first ready fetch fallback provider from available credentials. The official Firecrawl plugin provides that fallback.
- Daemons read env vars from `~/.openclaw/.env` (or the service environment).
Docs: [Web tools](/tools/web).

View File

@@ -28,7 +28,7 @@ OpenClaw auto-detects in this order and stops at the first working option:
- `whisper` (Python CLI; downloads models automatically)
3. **Provider auth**
- Configured `models.providers.*` entries that support audio are tried first
- Bundled fallback order: OpenAI → Groq → xAI → Deepgram → Google → SenseAudio → ElevenLabs → Mistral
- Provider fallback order: OpenAI → Groq → xAI → Deepgram → Google → SenseAudio → ElevenLabs → Mistral
As of 2026-05-22, Gemini CLI auto-detect is no longer supported for media understanding. Google is transitioning Gemini CLI users to Antigravity CLI; audio should use local or provider transcription, while image/video CLI fallback should move to Antigravity CLI (`agy`).

View File

@@ -179,8 +179,7 @@ The harness reads its config from per-attempt input
registered with `overridesBuiltInTool: true` and
`skipPermission: true` so 100% of tool calls flow through OpenClaw's
wrapped `execute()`. See [Permissions and ask_user](#permissions-and-ask_user).
- `enableSessionTelemetry` — opt-in OpenTelemetry routing via
`telemetry-bridge.ts`.
- `enableSessionTelemetry` — optional SDK session telemetry flag.
Nothing in the rest of OpenClaw needs to know about these fields. Other
plugins, channels, and core code only see the standard
@@ -267,9 +266,7 @@ real Copilot CLI or touch the host fs.
decisions from the initial prompt rather than asking clarifying
questions mid-turn. A follow-up will port the codex pattern at
`extensions/codex/src/app-server/user-input-bridge.ts` to route SDK
`UserInputRequest`s through the OpenClaw channel/TUI prompt path; the
dormant scaffolding in `extensions/copilot/src/user-input-bridge.ts`
is the surface that follow-up will wire.
`UserInputRequest`s through the OpenClaw channel/TUI prompt path.
## Permissions and ask_user

View File

@@ -51,7 +51,7 @@ Each entry lists the package, distribution route, and description.
## Core npm package
91 plugins
72 plugins
- **[admin-http-rpc](/plugins/reference/admin-http-rpc)** (`@openclaw/admin-http-rpc`) - included in OpenClaw. OpenClaw admin HTTP RPC endpoint.
@@ -59,8 +59,6 @@ Each entry lists the package, distribution route, and description.
- **[anthropic](/plugins/reference/anthropic)** (`@openclaw/anthropic-provider`) - included in OpenClaw. Adds Anthropic model provider support to OpenClaw.
- **[arcee](/plugins/reference/arcee)** (`@openclaw/arcee-provider`) - included in OpenClaw. Adds Arcee model provider support to OpenClaw.
- **[azure-speech](/plugins/reference/azure-speech)** (`@openclaw/azure-speech`) - included in OpenClaw. Azure AI Speech text-to-speech (MP3, native Ogg/Opus voice notes, PCM telephony).
- **[bonjour](/plugins/reference/bonjour)** (`@openclaw/bonjour`) - included in OpenClaw. Advertise the local OpenClaw gateway over Bonjour/mDNS.
@@ -71,17 +69,11 @@ Each entry lists the package, distribution route, and description.
- **[canvas](/plugins/reference/canvas)** (`@openclaw/canvas-plugin`) - included in OpenClaw. Experimental Canvas control and A2UI rendering surfaces for paired nodes.
- **[cerebras](/plugins/reference/cerebras)** (`@openclaw/cerebras-provider`) - included in OpenClaw. Adds Cerebras model provider support to OpenClaw.
- **[chutes](/plugins/reference/chutes)** (`@openclaw/chutes-provider`) - included in OpenClaw. Adds Chutes model provider support to OpenClaw.
- **[clickclack](/plugins/reference/clickclack)** (`@openclaw/clickclack`) - included in OpenClaw. Adds the Clickclack channel surface for sending and receiving OpenClaw messages.
- **[cloudflare-ai-gateway](/plugins/reference/cloudflare-ai-gateway)** (`@openclaw/cloudflare-ai-gateway-provider`) - included in OpenClaw. Adds Cloudflare AI Gateway model provider support to OpenClaw.
- **[codex-supervisor](/plugins/reference/codex-supervisor)** (`@openclaw/codex-supervisor`) - included in OpenClaw. Supervise Codex app-server sessions from OpenClaw.
- **[cohere](/plugins/reference/cohere)** (`@openclaw/cohere-provider`) - included in OpenClaw. Adds Cohere model provider support to OpenClaw.
- **[cohere](/plugins/reference/cohere)** (`@openclaw/cohere-provider`) - included in OpenClaw; npm; ClawHub: `clawhub:@openclaw/cohere-provider`. OpenClaw Cohere provider plugin.
- **[comfy](/plugins/reference/comfy)** (`@openclaw/comfy-provider`) - included in OpenClaw. Adds ComfyUI model provider support to OpenClaw.
@@ -89,48 +81,28 @@ Each entry lists the package, distribution route, and description.
- **[deepgram](/plugins/reference/deepgram)** (`@openclaw/deepgram-provider`) - included in OpenClaw. Adds media understanding provider support. Adds realtime transcription provider support.
- **[deepinfra](/plugins/reference/deepinfra)** (`@openclaw/deepinfra-provider`) - included in OpenClaw. Adds DeepInfra model provider support to OpenClaw.
- **[deepseek](/plugins/reference/deepseek)** (`@openclaw/deepseek-provider`) - included in OpenClaw. Adds DeepSeek model provider support to OpenClaw.
- **[document-extract](/plugins/reference/document-extract)** (`@openclaw/document-extract-plugin`) - included in OpenClaw. Extract text and fallback page images from local document attachments.
- **[duckduckgo](/plugins/reference/duckduckgo)** (`@openclaw/duckduckgo-plugin`) - included in OpenClaw. Adds web search provider support.
- **[elevenlabs](/plugins/reference/elevenlabs)** (`@openclaw/elevenlabs-speech`) - included in OpenClaw. Adds media understanding provider support. Adds realtime transcription provider support. Adds text-to-speech provider support.
- **[exa](/plugins/reference/exa)** (`@openclaw/exa-plugin`) - included in OpenClaw. Adds web search provider support.
- **[fal](/plugins/reference/fal)** (`@openclaw/fal-provider`) - included in OpenClaw. Adds fal model provider support to OpenClaw.
- **[file-transfer](/plugins/reference/file-transfer)** (`@openclaw/file-transfer`) - included in OpenClaw. Fetch, list, and write files on paired nodes via dedicated node commands. Bypasses bash stdout truncation by using base64 over node.invoke for binaries up to 16 MB.
- **[firecrawl](/plugins/reference/firecrawl)** (`@openclaw/firecrawl-plugin`) - included in OpenClaw. Adds agent-callable tools. Adds web fetch provider support. Adds web search provider support.
- **[fireworks](/plugins/reference/fireworks)** (`@openclaw/fireworks-provider`) - included in OpenClaw. Adds Fireworks model provider support to OpenClaw.
- **[github-copilot](/plugins/reference/github-copilot)** (`@openclaw/github-copilot-provider`) - included in OpenClaw. Adds GitHub Copilot model provider support to OpenClaw.
- **[gmi](/plugins/reference/gmi)** (`@openclaw/gmi-provider`) - included in OpenClaw. Adds Gmi, Gmi Cloud, Gmicloud model provider support to OpenClaw.
- **[google](/plugins/reference/google)** (`@openclaw/google-plugin`) - included in OpenClaw. Adds Google, Google Gemini CLI, Google Vertex model provider support to OpenClaw.
- **[gradium](/plugins/reference/gradium)** (`@openclaw/gradium-speech`) - included in OpenClaw. Adds text-to-speech provider support.
- **[groq](/plugins/reference/groq)** (`@openclaw/groq-provider`) - included in OpenClaw. Adds Groq model provider support to OpenClaw.
- **[huggingface](/plugins/reference/huggingface)** (`@openclaw/huggingface-provider`) - included in OpenClaw. Adds Hugging Face model provider support to OpenClaw.
- **[imessage](/plugins/reference/imessage)** (`@openclaw/imessage`) - included in OpenClaw. Adds the iMessage channel surface for sending and receiving OpenClaw messages.
- **[inworld](/plugins/reference/inworld)** (`@openclaw/inworld-speech`) - included in OpenClaw. Inworld streaming text-to-speech (MP3, OGG_OPUS, PCM telephony).
- **[irc](/plugins/reference/irc)** (`@openclaw/irc`) - included in OpenClaw. Adds the IRC channel surface for sending and receiving OpenClaw messages.
- **[kilocode](/plugins/reference/kilocode)** (`@openclaw/kilocode-provider`) - included in OpenClaw. Adds Kilocode model provider support to OpenClaw.
- **[kimi](/plugins/reference/kimi)** (`@openclaw/kimi-provider`) - included in OpenClaw. Adds Kimi, Kimi Coding model provider support to OpenClaw.
- **[litellm](/plugins/reference/litellm)** (`@openclaw/litellm-provider`) - included in OpenClaw. Adds LiteLLM model provider support to OpenClaw.
- **[llm-task](/plugins/reference/llm-task)** (`@openclaw/llm-task`) - included in OpenClaw. Generic JSON-only LLM tool for structured tasks callable from workflows.
@@ -175,16 +147,8 @@ Each entry lists the package, distribution route, and description.
- **[openrouter](/plugins/reference/openrouter)** (`@openclaw/openrouter-provider`) - included in OpenClaw. Adds OpenRouter model provider support to OpenClaw.
- **[parallel](/tools/parallel-search)** (`@openclaw/parallel-plugin`) - included in OpenClaw. Adds web search provider support.
- **[perplexity](/plugins/reference/perplexity)** (`@openclaw/perplexity-plugin`) - included in OpenClaw. Adds web search provider support.
- **[policy](/plugins/reference/policy)** (`@openclaw/policy`) - included in OpenClaw. Adds policy-backed doctor checks for workspace conformance.
- **[qianfan](/plugins/reference/qianfan)** (`@openclaw/qianfan-provider`) - included in OpenClaw. Adds Qianfan model provider support to OpenClaw.
- **[qwen](/plugins/reference/qwen)** (`@openclaw/qwen-provider`) - included in OpenClaw. Adds Qwen, Qwen Cloud, Model Studio, DashScope, Qwen Oauth, Qwen Portal, Qwen CLI model provider support to OpenClaw.
- **[runway](/plugins/reference/runway)** (`@openclaw/runway-provider`) - included in OpenClaw. Adds video generation provider support.
- **[searxng](/plugins/reference/searxng)** (`@openclaw/searxng-plugin`) - included in OpenClaw. Adds web search provider support.
@@ -197,8 +161,6 @@ Each entry lists the package, distribution route, and description.
- **[sms](/plugins/reference/sms)** (`@openclaw/sms`) - included in OpenClaw. Twilio SMS channel plugin for OpenClaw text messages.
- **[stepfun](/plugins/reference/stepfun)** (`@openclaw/stepfun-provider`) - included in OpenClaw. Adds StepFun, StepFun Plan model provider support to OpenClaw.
- **[synthetic](/plugins/reference/synthetic)** (`@openclaw/synthetic-provider`) - included in OpenClaw. Adds Synthetic model provider support to OpenClaw.
- **[tavily](/plugins/reference/tavily)** (`@openclaw/tavily-plugin`) - included in OpenClaw. Adds agent-callable tools. Adds web search provider support.
@@ -237,7 +199,7 @@ Each entry lists the package, distribution route, and description.
## Official external packages
35 plugins
54 plugins
- **[acpx](/plugins/reference/acpx)** (`@openclaw/acpx`) - npm; ClawHub. OpenClaw ACP runtime backend with plugin-owned session and transport management.
@@ -247,12 +209,24 @@ Each entry lists the package, distribution route, and description.
- **[anthropic-vertex](/plugins/reference/anthropic-vertex)** (`@openclaw/anthropic-vertex-provider`) - npm; ClawHub. OpenClaw Anthropic Vertex provider plugin for Claude models on Google Vertex AI.
- **[arcee](/plugins/reference/arcee)** (`@openclaw/arcee-provider`) - npm; ClawHub: `clawhub:@openclaw/arcee-provider`. Adds Arcee model provider support to OpenClaw.
- **[brave](/plugins/reference/brave)** (`@openclaw/brave-plugin`) - npm; ClawHub. OpenClaw Brave Search provider plugin for web search.
- **[cerebras](/plugins/reference/cerebras)** (`@openclaw/cerebras-provider`) - npm; ClawHub: `clawhub:@openclaw/cerebras-provider`. Adds Cerebras model provider support to OpenClaw.
- **[chutes](/plugins/reference/chutes)** (`@openclaw/chutes-provider`) - npm; ClawHub: `clawhub:@openclaw/chutes-provider`. Adds Chutes model provider support to OpenClaw.
- **[cloudflare-ai-gateway](/plugins/reference/cloudflare-ai-gateway)** (`@openclaw/cloudflare-ai-gateway-provider`) - npm; ClawHub: `clawhub:@openclaw/cloudflare-ai-gateway-provider`. Adds Cloudflare AI Gateway model provider support to OpenClaw.
- **[codex](/plugins/reference/codex)** (`@openclaw/codex`) - npm; ClawHub. OpenClaw Codex app-server harness and model provider plugin with a Codex-managed GPT catalog.
- **[copilot](/plugins/reference/copilot)** (`@openclaw/copilot`) - npm; ClawHub: `clawhub:@openclaw/copilot`. Registers the GitHub Copilot agent runtime.
- **[deepinfra](/plugins/reference/deepinfra)** (`@openclaw/deepinfra-provider`) - npm; ClawHub: `clawhub:@openclaw/deepinfra-provider`. Adds DeepInfra model provider support to OpenClaw.
- **[deepseek](/plugins/reference/deepseek)** (`@openclaw/deepseek-provider`) - npm; ClawHub: `clawhub:@openclaw/deepseek-provider`. Adds DeepSeek model provider support to OpenClaw.
- **[diagnostics-otel](/plugins/reference/diagnostics-otel)** (`@openclaw/diagnostics-otel`) - npm; ClawHub: `clawhub:@openclaw/diagnostics-otel`. OpenClaw diagnostics OpenTelemetry exporter for metrics and traces.
- **[diagnostics-prometheus](/plugins/reference/diagnostics-prometheus)** (`@openclaw/diagnostics-prometheus`) - npm; ClawHub: `clawhub:@openclaw/diagnostics-prometheus`. OpenClaw diagnostics Prometheus exporter for runtime metrics.
@@ -263,12 +237,28 @@ Each entry lists the package, distribution route, and description.
- **[discord](/plugins/reference/discord)** (`@openclaw/discord`) - npm; ClawHub. OpenClaw Discord channel plugin for channels, DMs, commands, and app events.
- **[exa](/plugins/reference/exa)** (`@openclaw/exa-plugin`) - npm; ClawHub: `clawhub:@openclaw/exa-plugin`. Adds web search provider support.
- **[feishu](/plugins/reference/feishu)** (`@openclaw/feishu`) - npm; ClawHub. OpenClaw Feishu/Lark channel plugin for chats and workplace tools (community maintained by @m1heng).
- **[firecrawl](/plugins/reference/firecrawl)** (`@openclaw/firecrawl-plugin`) - npm; ClawHub: `clawhub:@openclaw/firecrawl-plugin`. Adds agent-callable tools. Adds web fetch provider support. Adds web search provider support.
- **[gmi](/plugins/reference/gmi)** (`@openclaw/gmi-provider`) - npm; ClawHub: `clawhub:@openclaw/gmi-provider`. OpenClaw GMI Cloud provider plugin.
- **[google-meet](/plugins/reference/google-meet)** (`@openclaw/google-meet`) - npm; ClawHub. OpenClaw Google Meet participant plugin for joining calls through Chrome or Twilio transports.
- **[googlechat](/plugins/reference/googlechat)** (`@openclaw/googlechat`) - npm; ClawHub. OpenClaw Google Chat channel plugin for spaces and direct messages.
- **[gradium](/plugins/reference/gradium)** (`@openclaw/gradium-speech`) - npm; ClawHub: `clawhub:@openclaw/gradium-speech`. Adds text-to-speech provider support.
- **[groq](/plugins/reference/groq)** (`@openclaw/groq-provider`) - npm; ClawHub: `clawhub:@openclaw/groq-provider`. Adds Groq model provider support to OpenClaw.
- **[inworld](/plugins/reference/inworld)** (`@openclaw/inworld-speech`) - npm; ClawHub: `clawhub:@openclaw/inworld-speech`. Inworld streaming text-to-speech (MP3, OGG_OPUS, PCM telephony).
- **[kilocode](/plugins/reference/kilocode)** (`@openclaw/kilocode-provider`) - npm; ClawHub: `clawhub:@openclaw/kilocode-provider`. Adds Kilocode model provider support to OpenClaw.
- **[kimi](/plugins/reference/kimi)** (`@openclaw/kimi-provider`) - npm; ClawHub: `clawhub:@openclaw/kimi-provider`. Adds Kimi, Kimi Coding model provider support to OpenClaw.
- **[line](/plugins/reference/line)** (`@openclaw/line`) - npm; ClawHub. OpenClaw LINE channel plugin for LINE Bot API chats.
- **[llama-cpp](/plugins/reference/llama-cpp)** (`@openclaw/llama-cpp-provider`) - npm; ClawHub. Local GGUF embeddings through node-llama-cpp.
@@ -287,12 +277,22 @@ Each entry lists the package, distribution route, and description.
- **[openshell](/plugins/reference/openshell)** (`@openclaw/openshell-sandbox`) - npm; ClawHub. OpenClaw sandbox backend for the NVIDIA OpenShell CLI with mirrored local workspaces and SSH command execution.
- **[parallel](/tools/parallel-search)** (`@openclaw/parallel-plugin`) - npm; ClawHub: `clawhub:@openclaw/parallel-plugin`. Adds web search provider support.
- **[perplexity](/plugins/reference/perplexity)** (`@openclaw/perplexity-plugin`) - npm; ClawHub: `clawhub:@openclaw/perplexity-plugin`. Adds web search provider support.
- **[pixverse](/plugins/reference/pixverse)** (`@openclaw/pixverse-provider`) - npm; ClawHub: `clawhub:@openclaw/pixverse-provider`. OpenClaw PixVerse video generation provider plugin.
- **[qianfan](/plugins/reference/qianfan)** (`@openclaw/qianfan-provider`) - npm; ClawHub: `clawhub:@openclaw/qianfan-provider`. Adds Qianfan model provider support to OpenClaw.
- **[qqbot](/plugins/reference/qqbot)** (`@openclaw/qqbot`) - npm; ClawHub. OpenClaw QQ Bot channel plugin for group and direct-message workflows.
- **[qwen](/plugins/reference/qwen)** (`@openclaw/qwen-provider`) - npm; ClawHub: `clawhub:@openclaw/qwen-provider`. Adds Qwen, Qwen Cloud, Model Studio, DashScope, Qwen Oauth, Qwen Portal, Qwen CLI model provider support to OpenClaw.
- **[slack](/plugins/reference/slack)** (`@openclaw/slack`) - npm; ClawHub. OpenClaw Slack channel plugin for channels, DMs, commands, and app events.
- **[stepfun](/plugins/reference/stepfun)** (`@openclaw/stepfun-provider`) - npm. Adds StepFun, StepFun Plan model provider support to OpenClaw.
- **[synology-chat](/plugins/reference/synology-chat)** (`@openclaw/synology-chat`) - npm; ClawHub. Synology Chat channel plugin for OpenClaw channels and direct messages.
- **[tlon](/plugins/reference/tlon)** (`@openclaw/tlon`) - npm; ClawHub. OpenClaw Tlon/Urbit channel plugin for chat workflows.

View File

@@ -12,7 +12,7 @@ Adds Arcee model provider support to OpenClaw.
## Distribution
- Package: `@openclaw/arcee-provider`
- Install route: included in OpenClaw
- Install route: npm; ClawHub: `clawhub:@openclaw/arcee-provider`
## Surface

View File

@@ -12,7 +12,7 @@ Adds Cerebras model provider support to OpenClaw.
## Distribution
- Package: `@openclaw/cerebras-provider`
- Install route: included in OpenClaw
- Install route: npm; ClawHub: `clawhub:@openclaw/cerebras-provider`
## Surface

View File

@@ -12,7 +12,7 @@ Adds Chutes model provider support to OpenClaw.
## Distribution
- Package: `@openclaw/chutes-provider`
- Install route: included in OpenClaw
- Install route: npm; ClawHub: `clawhub:@openclaw/chutes-provider`
## Surface

View File

@@ -12,7 +12,7 @@ Adds Cloudflare AI Gateway model provider support to OpenClaw.
## Distribution
- Package: `@openclaw/cloudflare-ai-gateway-provider`
- Install route: included in OpenClaw
- Install route: npm; ClawHub: `clawhub:@openclaw/cloudflare-ai-gateway-provider`
## Surface

View File

@@ -1,5 +1,5 @@
---
summary: "Adds Cohere model provider support to OpenClaw."
summary: "OpenClaw Cohere provider plugin."
read_when:
- You are installing, configuring, or auditing the cohere plugin
title: "Cohere plugin"
@@ -7,12 +7,12 @@ title: "Cohere plugin"
# Cohere plugin
Adds Cohere model provider support to OpenClaw.
OpenClaw Cohere provider plugin.
## Distribution
- Package: `@openclaw/cohere-provider`
- Install route: included in OpenClaw
- Install route: included in OpenClaw; npm; ClawHub: `clawhub:@openclaw/cohere-provider`
## Surface

View File

@@ -12,7 +12,7 @@ Adds DeepInfra model provider support to OpenClaw.
## Distribution
- Package: `@openclaw/deepinfra-provider`
- Install route: included in OpenClaw
- Install route: npm; ClawHub: `clawhub:@openclaw/deepinfra-provider`
## Surface

View File

@@ -12,7 +12,7 @@ Adds DeepSeek model provider support to OpenClaw.
## Distribution
- Package: `@openclaw/deepseek-provider`
- Install route: included in OpenClaw
- Install route: npm; ClawHub: `clawhub:@openclaw/deepseek-provider`
## Surface

View File

@@ -12,7 +12,7 @@ Adds web search provider support.
## Distribution
- Package: `@openclaw/exa-plugin`
- Install route: included in OpenClaw
- Install route: npm; ClawHub: `clawhub:@openclaw/exa-plugin`
## Surface

View File

@@ -12,7 +12,7 @@ Adds agent-callable tools. Adds web fetch provider support. Adds web search prov
## Distribution
- Package: `@openclaw/firecrawl-plugin`
- Install route: included in OpenClaw
- Install route: npm; ClawHub: `clawhub:@openclaw/firecrawl-plugin`
## Surface

View File

@@ -1,5 +1,5 @@
---
summary: "Adds Gmi, Gmi Cloud, Gmicloud model provider support to OpenClaw."
summary: "OpenClaw GMI Cloud provider plugin."
read_when:
- You are installing, configuring, or auditing the gmi plugin
title: "Gmi plugin"
@@ -7,12 +7,12 @@ title: "Gmi plugin"
# Gmi plugin
Adds Gmi, Gmi Cloud, Gmicloud model provider support to OpenClaw.
OpenClaw GMI Cloud provider plugin.
## Distribution
- Package: `@openclaw/gmi-provider`
- Install route: included in OpenClaw
- Install route: npm; ClawHub: `clawhub:@openclaw/gmi-provider`
## Surface

View File

@@ -12,7 +12,7 @@ Adds text-to-speech provider support.
## Distribution
- Package: `@openclaw/gradium-speech`
- Install route: included in OpenClaw
- Install route: npm; ClawHub: `clawhub:@openclaw/gradium-speech`
## Surface

View File

@@ -12,7 +12,7 @@ Adds Groq model provider support to OpenClaw.
## Distribution
- Package: `@openclaw/groq-provider`
- Install route: included in OpenClaw
- Install route: npm; ClawHub: `clawhub:@openclaw/groq-provider`
## Surface

View File

@@ -12,7 +12,7 @@ Inworld streaming text-to-speech (MP3, OGG_OPUS, PCM telephony).
## Distribution
- Package: `@openclaw/inworld-speech`
- Install route: included in OpenClaw
- Install route: npm; ClawHub: `clawhub:@openclaw/inworld-speech`
## Surface

View File

@@ -12,7 +12,7 @@ Adds Kilocode model provider support to OpenClaw.
## Distribution
- Package: `@openclaw/kilocode-provider`
- Install route: included in OpenClaw
- Install route: npm; ClawHub: `clawhub:@openclaw/kilocode-provider`
## Surface

View File

@@ -12,7 +12,7 @@ Adds Kimi, Kimi Coding model provider support to OpenClaw.
## Distribution
- Package: `@openclaw/kimi-provider`
- Install route: included in OpenClaw
- Install route: npm; ClawHub: `clawhub:@openclaw/kimi-provider`
## Surface

View File

@@ -12,7 +12,7 @@ Adds web search provider support.
## Distribution
- Package: `@openclaw/perplexity-plugin`
- Install route: included in OpenClaw
- Install route: npm; ClawHub: `clawhub:@openclaw/perplexity-plugin`
## Surface

View File

@@ -16,4 +16,4 @@ OpenClaw QA lab plugin with private debugger UI and scenario runner.
## Surface
plugin
contracts: webSearchProviders

View File

@@ -12,7 +12,7 @@ Adds Qianfan model provider support to OpenClaw.
## Distribution
- Package: `@openclaw/qianfan-provider`
- Install route: included in OpenClaw
- Install route: npm; ClawHub: `clawhub:@openclaw/qianfan-provider`
## Surface

View File

@@ -12,7 +12,7 @@ Adds Qwen, Qwen Cloud, Model Studio, DashScope, Qwen Oauth, Qwen Portal, Qwen CL
## Distribution
- Package: `@openclaw/qwen-provider`
- Install route: included in OpenClaw
- Install route: npm; ClawHub: `clawhub:@openclaw/qwen-provider`
## Surface

View File

@@ -12,7 +12,7 @@ Adds StepFun, StepFun Plan model provider support to OpenClaw.
## Distribution
- Package: `@openclaw/stepfun-provider`
- Install route: included in OpenClaw
- Install route: npm
## Surface

View File

@@ -248,6 +248,7 @@ usage endpoint failed or returned no usable usage data.
| `plugin-sdk/reply-reference` | `createReplyReferencePlanner` |
| `plugin-sdk/reply-chunking` | Narrow text/markdown chunking helpers |
| `plugin-sdk/session-store-runtime` | Session workflow helpers (`getSessionEntry`, `listSessionEntries`, `patchSessionEntry`, `upsertSessionEntry`), legacy session store path/session-key helpers, updated-at reads, and deprecated whole-store mutation helpers |
| `plugin-sdk/sqlite-runtime` | Focused SQLite agent-schema, path, and transaction helpers for first-party runtime |
| `plugin-sdk/cron-store-runtime` | Cron store path/load/save helpers |
| `plugin-sdk/state-paths` | State/OAuth dir path helpers |
| `plugin-sdk/plugin-state-runtime` | Plugin sidecar SQLite keyed-state types plus centralized connection pragma and WAL maintenance setup for plugin-owned databases |
@@ -306,6 +307,7 @@ usage endpoint failed or returned no usable usage data.
| `plugin-sdk/response-limit-runtime` | Bounded response-body reader without the broad media runtime surface |
| `plugin-sdk/session-binding-runtime` | Current conversation binding state without configured binding routing or pairing stores |
| `plugin-sdk/session-store-runtime` | Session-store helpers without broad config writes/maintenance imports |
| `plugin-sdk/sqlite-runtime` | Focused SQLite agent-schema, path, and transaction helpers without database lifecycle controls |
| `plugin-sdk/context-visibility-runtime` | Context visibility resolution and supplemental context filtering without broad config/security imports |
| `plugin-sdk/string-coerce-runtime` | Narrow primitive record/string coercion and normalization helpers without markdown/logging imports |
| `plugin-sdk/host-runtime` | Hostname and SCP host normalization helpers |

View File

@@ -17,6 +17,15 @@ Arcee AI models can be accessed directly via the Arcee platform or through [Open
| API | OpenAI-compatible |
| Base URL | `https://api.arcee.ai/api/v1` (direct) or `https://openrouter.ai/api/v1` (OpenRouter) |
## Install plugin
Install the official plugin, then restart Gateway:
```bash
openclaw plugins install @openclaw/arcee-provider
openclaw gateway restart
```
## Getting started
<Tabs>
@@ -96,7 +105,7 @@ Arcee AI models can be accessed directly via the Arcee platform or through [Open
## Built-in catalog
OpenClaw currently ships this bundled Arcee catalog:
OpenClaw currently ships this Arcee static catalog:
| Model ref | Name | Input | Context | Cost (in/out per 1M) | Notes |
| ------------------------------ | ---------------------- | ----- | ------- | -------------------- | ----------------------------------------- |

View File

@@ -6,12 +6,12 @@ read_when:
- You need the Cerebras API key env var or CLI auth choice
---
[Cerebras](https://www.cerebras.ai) provides high-speed OpenAI-compatible inference on custom inference hardware. OpenClaw includes a bundled Cerebras provider plugin with a static four-model catalog.
[Cerebras](https://www.cerebras.ai) provides high-speed OpenAI-compatible inference on custom inference hardware. The Cerebras provider plugin includes a static four-model catalog.
| Property | Value |
| --------------- | ---------------------------------------- |
| Provider id | `cerebras` |
| Plugin | bundled, `enabledByDefault: true` |
| Plugin | official external package |
| Auth env var | `CEREBRAS_API_KEY` |
| Onboarding flag | `--auth-choice cerebras-api-key` |
| Direct CLI flag | `--cerebras-api-key <key>` |
@@ -19,6 +19,15 @@ read_when:
| Base URL | `https://api.cerebras.ai/v1` |
| Default model | `cerebras/zai-glm-4.7` |
## Install plugin
Install the official plugin, then restart Gateway:
```bash
openclaw plugins install @openclaw/cerebras-provider
openclaw gateway restart
```
## Getting started
<Steps>
@@ -50,7 +59,7 @@ export CEREBRAS_API_KEY=csk-...
openclaw models list --provider cerebras
```
The list should include all four bundled models. If `CEREBRAS_API_KEY` is unresolved, `openclaw models status --json` reports the missing credential under `auth.unusableProfiles`.
The list should include all four static models. If `CEREBRAS_API_KEY` is unresolved, `openclaw models status --json` reports the missing credential under `auth.unusableProfiles`.
</Step>
</Steps>
@@ -81,7 +90,7 @@ OpenClaw ships a static Cerebras catalog that mirrors the public OpenAI-compatib
## Manual config
The bundled plugin usually means you only need the API key. Use explicit `models.providers.cerebras` config when you want to override model metadata or run in `mode: "merge"` against the static catalog:
The plugin usually means you only need the API key. Use explicit `models.providers.cerebras` config when you want to override model metadata or run in `mode: "merge"` against the static catalog:
```json5
{

View File

@@ -9,7 +9,7 @@ read_when:
[Chutes](https://chutes.ai) exposes open-source model catalogs through an
OpenAI-compatible API. OpenClaw supports both browser OAuth and direct API-key
auth for the bundled `chutes` provider.
auth for the `chutes` provider.
| Property | Value |
| -------- | ---------------------------- |
@@ -18,6 +18,15 @@ auth for the bundled `chutes` provider.
| Base URL | `https://llm.chutes.ai/v1` |
| Auth | OAuth or API key (see below) |
## Install plugin
Install the official plugin, then restart Gateway:
```bash
openclaw plugins install @openclaw/chutes-provider
openclaw gateway restart
```
## Getting started
<Tabs>
@@ -33,7 +42,7 @@ auth for the bundled `chutes` provider.
</Step>
<Step title="Verify the default model">
After onboarding, the default model is set to
`chutes/zai-org/GLM-4.7-TEE` and the bundled Chutes catalog is
`chutes/zai-org/GLM-4.7-TEE` and the Chutes static catalog is
registered.
</Step>
</Steps>
@@ -51,7 +60,7 @@ auth for the bundled `chutes` provider.
</Step>
<Step title="Verify the default model">
After onboarding, the default model is set to
`chutes/zai-org/GLM-4.7-TEE` and the bundled Chutes catalog is
`chutes/zai-org/GLM-4.7-TEE` and the Chutes static catalog is
registered.
</Step>
</Steps>
@@ -59,7 +68,7 @@ auth for the bundled `chutes` provider.
</Tabs>
<Note>
Both auth paths register the bundled Chutes catalog and set the default model to
Both auth paths register the Chutes static catalog and set the default model to
`chutes/zai-org/GLM-4.7-TEE`. Runtime environment variables: `CHUTES_API_KEY`,
`CHUTES_OAUTH_TOKEN`.
</Note>
@@ -68,11 +77,11 @@ Both auth paths register the bundled Chutes catalog and set the default model to
When Chutes auth is available, OpenClaw queries the Chutes catalog with that
credential and uses the discovered models. If discovery fails, OpenClaw falls
back to a bundled static catalog so onboarding and startup still work.
back to a static catalog so onboarding and startup still work.
## Default aliases
OpenClaw registers three convenience aliases for the bundled Chutes catalog:
OpenClaw registers three convenience aliases for the Chutes static catalog:
| Alias | Target model |
| --------------- | ----------------------------------------------------- |
@@ -82,7 +91,7 @@ OpenClaw registers three convenience aliases for the bundled Chutes catalog:
## Built-in starter catalog
The bundled fallback catalog includes current Chutes refs:
The static fallback catalog includes current Chutes refs:
| Model ref |
| ----------------------------------------------------- |
@@ -130,7 +139,7 @@ The bundled fallback catalog includes current Chutes refs:
<Accordion title="Notes">
- API-key and OAuth discovery both use the same `chutes` provider id.
- Chutes models are registered as `chutes/<model-id>`.
- If discovery fails at startup, the bundled static catalog is used automatically.
- If discovery fails at startup, the static catalog is used automatically.
</Accordion>
</AccordionGroup>

View File

@@ -24,6 +24,15 @@ assistant prefill turns before sending the payload through Cloudflare AI Gateway
Anthropic rejects response prefilling with extended thinking, while ordinary
non-thinking prefill remains available.
## Install plugin
Install the official plugin, then restart Gateway:
```bash
openclaw plugins install @openclaw/cloudflare-ai-gateway-provider
openclaw gateway restart
```
## Getting started
<Steps>

View File

@@ -6,23 +6,30 @@ read_when:
- You need the Cohere API key env var or CLI auth choice
---
[Cohere](https://cohere.com) provides OpenAI-compatible inference through its Compatibility API. OpenClaw includes a bundled Cohere provider plugin with the Command A model catalog.
[Cohere](https://cohere.com) provides OpenAI-compatible inference through its Compatibility API. OpenClaw ships the Cohere provider during its externalization transition and also publishes it as an official external plugin with the Command A model catalog.
| Property | Value |
| --------------- | ---------------------------------------- |
| Provider id | `cohere` |
| Plugin | bundled, `enabledByDefault: true` |
| Auth env var | `COHERE_API_KEY` |
| Onboarding flag | `--auth-choice cohere-api-key` |
| Direct CLI flag | `--cohere-api-key <key>` |
| API | OpenAI-compatible (`openai-completions`) |
| Base URL | `https://api.cohere.ai/compatibility/v1` |
| Default model | `cohere/command-a-03-2025` |
| Property | Value |
| --------------- | ---------------------------------------------------- |
| Provider id | `cohere` |
| Plugin | bundled during transition; official external package |
| Auth env var | `COHERE_API_KEY` |
| Onboarding flag | `--auth-choice cohere-api-key` |
| Direct CLI flag | `--cohere-api-key <key>` |
| API | OpenAI-compatible (`openai-completions`) |
| Base URL | `https://api.cohere.ai/compatibility/v1` |
| Default model | `cohere/command-a-03-2025` |
## Get started
1. Create a Cohere API key.
2. Run onboarding:
1. Cohere is included in current OpenClaw packages. If it is unavailable, install the external package and restart the Gateway:
```bash
openclaw plugins install @openclaw/cohere-provider
openclaw gateway restart
```
2. Create a Cohere API key.
3. Run onboarding:
```bash
openclaw onboard --non-interactive \
@@ -30,7 +37,7 @@ openclaw onboard --non-interactive \
--cohere-api-key "$COHERE_API_KEY"
```
3. Confirm the catalog is available:
4. Confirm the catalog is available:
```bash
openclaw models list --provider cohere
@@ -40,7 +47,7 @@ The default model is set only when no primary model is already configured.
## Environment-only setup
Make `COHERE_API_KEY` available to the Gateway process, then select the bundled model:
Make `COHERE_API_KEY` available to the Gateway process, then select the Cohere model:
```json5
{

View File

@@ -9,6 +9,15 @@ title: "DeepInfra"
DeepInfra provides a **unified API** that routes requests to the most popular open source and frontier models behind a single
endpoint and API key. It is OpenAI-compatible, so most OpenAI SDKs work by switching the base URL.
## Install plugin
Install the official plugin, then restart Gateway:
```bash
openclaw plugins install @openclaw/deepinfra-provider
openclaw gateway restart
```
## Getting an API key
1. Go to [https://deepinfra.com/](https://deepinfra.com/)
@@ -42,7 +51,7 @@ export DEEPINFRA_API_KEY="<your-deepinfra-api-key>" # pragma: allowlist secret
## Supported OpenClaw surfaces
The bundled plugin registers all DeepInfra surfaces that match current
The plugin registers all DeepInfra surfaces that match current
OpenClaw provider contracts. Chat, image generation, and video generation
refresh their model catalogues live from `/v1/openai/models?sort_by=openclaw&filter=with_meta`
when `DEEPINFRA_API_KEY` is configured; the other surfaces use the curated

View File

@@ -15,6 +15,15 @@ read_when:
| API | OpenAI-compatible |
| Base URL | `https://api.deepseek.com` |
## Install plugin
Install the official plugin, then restart Gateway:
```bash
openclaw plugins install @openclaw/deepseek-provider
openclaw gateway restart
```
## Getting started
<Steps>
@@ -34,7 +43,7 @@ read_when:
openclaw models list --provider deepseek
```
To inspect the bundled static catalog without requiring a running Gateway,
To inspect the plugin's static catalog without requiring a running Gateway,
use:
```bash

View File

@@ -7,9 +7,9 @@ title: "GMI Cloud"
---
GMI Cloud is a hosted inference platform for frontier and open-weight models
behind an OpenAI-compatible API. In OpenClaw it is a bundled model provider,
which means you can select it with the provider id `gmi`, store credentials
through normal model auth, and use model refs like
behind an OpenAI-compatible API. In OpenClaw it is an official external provider
plugin, which means you install it once, select it with the provider id `gmi`,
store credentials through normal model auth, and use model refs like
`gmi/google/gemini-3.1-flash-lite`.
Use GMI when you want one API key for several hosted model families, including
@@ -24,7 +24,14 @@ model availability, billing, rate limits, and any provider-side routing policy.
## Setup
Create an API key in GMI Cloud, then run:
Install the plugin, restart the gateway, then create an API key in GMI Cloud:
```bash
openclaw plugins install @openclaw/gmi-provider
openclaw gateway restart
```
Then run:
```bash
openclaw onboard --auth-choice gmi-api-key
@@ -60,7 +67,7 @@ GPU control matters more than hosted convenience.
## Models
The bundled catalog seeds commonly available GMI Cloud route ids, including:
The plugin catalog seeds commonly available GMI Cloud route ids, including:
- `gmi/zai-org/GLM-5.1-FP8`
- `gmi/deepseek-ai/DeepSeek-V3.2`

View File

@@ -6,7 +6,7 @@ read_when:
title: "Gradium"
---
[Gradium](https://gradium.ai) is a bundled text-to-speech provider for OpenClaw. The plugin can render normal audio replies (WAV), voice-note-compatible Opus output, and 8 kHz u-law audio for telephony surfaces.
[Gradium](https://gradium.ai) is a text-to-speech provider for OpenClaw. The plugin can render normal audio replies (WAV), voice-note-compatible Opus output, and 8 kHz u-law audio for telephony surfaces.
| Property | Value |
| ------------- | ------------------------------------ |
@@ -15,6 +15,15 @@ title: "Gradium"
| Base URL | `https://api.gradium.ai` (default) |
| Default voice | `Emma` (`YTpq7expH9539ERJ`) |
## Install plugin
Install the official plugin, then restart Gateway:
```bash
openclaw plugins install @openclaw/gradium-speech
openclaw gateway restart
```
## Setup
Create a Gradium API key, then expose it to OpenClaw with either an env var or the config key.

View File

@@ -7,19 +7,27 @@ read_when:
- You are configuring Whisper audio transcription on Groq
---
[Groq](https://groq.com) provides ultra-fast inference on open-weight models (Llama, Gemma, Kimi, Qwen, GPT OSS, and more) using custom LPU hardware. OpenClaw includes a bundled Groq plugin that registers both an OpenAI-compatible chat provider and an audio media-understanding provider.
[Groq](https://groq.com) provides ultra-fast inference on open-weight models (Llama, Gemma, Kimi, Qwen, GPT OSS, and more) using custom LPU hardware. The Groq plugin registers both an OpenAI-compatible chat provider and an audio media-understanding provider.
| Property | Value |
| ---------------------- | ---------------------------------------- |
| Provider id | `groq` |
| Plugin | bundled, `enabledByDefault: true` |
| Plugin | official external package |
| Auth env var | `GROQ_API_KEY` |
| Onboarding flag | `--auth-choice groq-api-key` |
| API | OpenAI-compatible (`openai-completions`) |
| Base URL | `https://api.groq.com/openai/v1` |
| Audio transcription | `whisper-large-v3-turbo` (default) |
| Suggested chat default | `groq/llama-3.3-70b-versatile` |
## Install plugin
Install the official plugin, then restart Gateway:
```bash
openclaw plugins install @openclaw/groq-provider
openclaw gateway restart
```
## Getting started
<Steps>
@@ -27,18 +35,9 @@ read_when:
Create an API key at [console.groq.com/keys](https://console.groq.com/keys).
</Step>
<Step title="Set the API key">
<CodeGroup>
```bash Onboarding
openclaw onboard --auth-choice groq-api-key
```
```bash Env only
```bash
export GROQ_API_KEY=gsk_...
```
</CodeGroup>
</Step>
<Step title="Set a default model">
```json5
@@ -73,7 +72,7 @@ export GROQ_API_KEY=gsk_...
## Built-in catalog
OpenClaw ships a manifest-backed Groq catalog with both reasoning and non-reasoning entries. Run `openclaw models list --provider groq` to see the bundled rows for your installed version, or check [console.groq.com/docs/models](https://console.groq.com/docs/models) for Groq's authoritative list.
OpenClaw ships a manifest-backed Groq catalog with both reasoning and non-reasoning entries. Run `openclaw models list --provider groq` to see the static rows for your installed version, or check [console.groq.com/docs/models](https://console.groq.com/docs/models) for Groq's authoritative list.
| Model ref | Name | Reasoning | Input | Context |
| ------------------------------------------------ | ----------------------- | --------- | ------------ | ------- |
@@ -103,7 +102,7 @@ See [Thinking modes](/tools/thinking) for the shared `/think` levels and how Ope
## Audio transcription
Groq's bundled plugin also registers an **audio media-understanding provider** so voice messages can be transcribed through the shared `tools.media.audio` surface.
Groq's plugin also registers an **audio media-understanding provider** so voice messages can be transcribed through the shared `tools.media.audio` surface.
| Property | Value |
| ------------------ | ----------------------------------------- |
@@ -138,7 +137,7 @@ To make Groq the default audio backend:
</Accordion>
<Accordion title="Custom Groq model ids">
OpenClaw accepts any Groq model id at runtime. Use the exact id shown by Groq and prefix it with `groq/`. The bundled catalog covers the common cases; uncatalogued ids fall through to the default OpenAI-compatible template.
OpenClaw accepts any Groq model id at runtime. Use the exact id shown by Groq and prefix it with `groq/`. The static catalog covers the common cases; uncatalogued ids fall through to the default OpenAI-compatible template.
```json5
{

View File

@@ -17,7 +17,7 @@ the standard reply-audio pipeline.
| Property | Value |
| ------------- | --------------------------------------------------------------- |
| Provider id | `inworld` |
| Plugin | bundled, `enabledByDefault: true` |
| Plugin | official external package |
| Contract | `speechProviders` (TTS only) |
| Auth env var | `INWORLD_API_KEY` (HTTP Basic, Base64 dashboard credential) |
| Base URL | `https://api.inworld.ai` |
@@ -27,6 +27,15 @@ the standard reply-audio pipeline.
| Website | [inworld.ai](https://inworld.ai) |
| Docs | [docs.inworld.ai/tts/tts](https://docs.inworld.ai/tts/tts) |
## Install plugin
Install the official plugin, then restart Gateway:
```bash
openclaw plugins install @openclaw/inworld-speech
openclaw gateway restart
```
## Getting started
<Steps>
@@ -112,7 +121,7 @@ the standard reply-audio pipeline.
Full config reference including `messages.tts` settings.
</Card>
<Card title="Providers" href="/providers" icon="grid">
All bundled OpenClaw providers.
All supported OpenClaw providers.
</Card>
<Card title="Troubleshooting" href="/help/troubleshooting" icon="wrench">
Common issues and debugging steps.

View File

@@ -16,6 +16,15 @@ endpoint and API key. It is OpenAI-compatible, so most OpenAI SDKs work by switc
| API | OpenAI-compatible |
| Base URL | `https://api.kilo.ai/api/gateway/` |
## Install plugin
Install the official plugin, then restart Gateway:
```bash
openclaw plugins install @openclaw/kilocode-provider
openclaw gateway restart
```
## Getting started
<Steps>
@@ -70,7 +79,7 @@ Any model available on the gateway can be used with the `kilocode/` prefix:
<Tip>
At startup, OpenClaw queries `GET https://api.kilo.ai/api/gateway/models` and merges
discovered models ahead of the static fallback catalog. The bundled fallback always
discovered models ahead of the static fallback catalog. The static fallback always
includes `kilocode/kilo/auto` (`Kilo Auto`) with `input: ["text", "image"]`,
`reasoning: true`, `contextWindow: 1000000`, and `maxTokens: 128000`.
</Tip>
@@ -113,7 +122,7 @@ includes `kilocode/kilo/auto` (`Kilo Auto`) with `input: ["text", "image"]`,
</Accordion>
<Accordion title="Troubleshooting">
- If model discovery fails at startup, OpenClaw falls back to the bundled static catalog containing `kilocode/kilo/auto`.
- If model discovery fails at startup, OpenClaw falls back to the static catalog containing `kilocode/kilo/auto`.
- Confirm your API key is valid and that your Kilo account has the desired models enabled.
- When the Gateway runs as a daemon, ensure `KILOCODE_API_KEY` is available to that process (for example in `~/.openclaw/.env` or via `env.shellEnv`).

View File

@@ -200,6 +200,12 @@ Choose your provider and follow the setup steps.
</Tab>
<Tab title="Kimi Coding">
Install the official plugin, then restart Gateway:
```bash
openclaw plugins install @openclaw/kimi-provider
openclaw gateway restart
```
**Best for:** code-focused tasks via the Kimi Coding endpoint.
<Note>

View File

@@ -506,6 +506,9 @@ openclaw infer image generate \
Use the same `--output-format` and `--background` flags with
`openclaw infer image edit` when starting from an input file.
`--openai-background` remains available as an OpenAI-specific alias.
Use `--quality low|medium|high|auto` when you need to control OpenAI Images
quality and cost. Use `--openai-moderation low|auto` to pass OpenAI's
provider-specific moderation hint from either `image generate` or `image edit`.
For ChatGPT/Codex OAuth installs, keep the same `openai/gpt-image-2` ref. When an
`openai` OAuth profile is configured, OpenClaw resolves that stored OAuth

View File

@@ -19,6 +19,15 @@ This page is the Perplexity **provider** setup. For the Perplexity **tool** (how
| Auth | `PERPLEXITY_API_KEY` (direct) or `OPENROUTER_API_KEY` (via OpenRouter) |
| Config path | `plugins.entries.perplexity.config.webSearch.apiKey` |
## Install plugin
Install the official plugin, then restart Gateway:
```bash
openclaw plugins install @openclaw/perplexity-plugin
openclaw gateway restart
```
## Getting started
<Steps>

View File

@@ -16,6 +16,15 @@ endpoint and API key. It is OpenAI-compatible, so most OpenAI SDKs work by switc
| API | OpenAI-compatible |
| Base URL | `https://qianfan.baidubce.com/v2` |
## Install plugin
Install the official plugin, then restart Gateway:
```bash
openclaw plugins install @openclaw/qianfan-provider
openclaw gateway restart
```
## Getting started
<Steps>
@@ -45,7 +54,7 @@ endpoint and API key. It is OpenAI-compatible, so most OpenAI SDKs work by switc
| `qianfan/ernie-5.0-thinking-preview` | text, image | 119,000 | 64,000 | Yes | Multimodal |
<Tip>
The default bundled model ref is `qianfan/deepseek-v3.2`. You only need to override `models.providers.qianfan` when you need a custom base URL or model metadata.
The default model ref is `qianfan/deepseek-v3.2`. You only need to override `models.providers.qianfan` when you need a custom base URL or model metadata.
</Tip>
## Config example
@@ -98,7 +107,7 @@ The default bundled model ref is `qianfan/deepseek-v3.2`. You only need to overr
</Accordion>
<Accordion title="Catalog and overrides">
The bundled catalog currently includes `deepseek-v3.2` and `ernie-5.0-thinking-preview`. Add or override `models.providers.qianfan` only when you need a custom base URL or model metadata.
The static catalog currently includes `deepseek-v3.2` and `ernie-5.0-thinking-preview`. Add or override `models.providers.qianfan` only when you need a custom base URL or model metadata.
<Note>
Model refs use the `qianfan/` prefix (for example `qianfan/deepseek-v3.2`).

View File

@@ -64,11 +64,11 @@ provider instead.
- You need to test compatibility with the Qwen Portal endpoint specifically.
Choose [Qwen](/providers/qwen) for new setup, broader endpoint choices, Standard
ModelStudio, Coding Plan, and the full bundled Qwen catalog.
ModelStudio, Coding Plan, and the full Qwen plugin catalog.
## Models
The bundled catalog seeds the Qwen Portal default:
The Qwen plugin catalog seeds the Qwen Portal default:
- `qwen-oauth/qwen3.5-plus`

View File

@@ -1,13 +1,13 @@
---
summary: "Use Qwen Cloud via OpenClaw's bundled qwen provider"
summary: "Use Qwen Cloud through its OpenClaw plugin"
read_when:
- You want to use Qwen with OpenClaw
- You previously used Qwen OAuth
title: "Qwen"
---
OpenClaw now treats Qwen as a first-class bundled provider with canonical id
`qwen`. The bundled provider targets the Qwen Cloud / Alibaba DashScope and
OpenClaw now treats Qwen as a first-class provider plugin with canonical id
`qwen`. The provider plugin targets the Qwen Cloud / Alibaba DashScope and
Coding Plan endpoints, keeps legacy `modelstudio` ids working as a compatibility
alias, and also exposes the Qwen Portal token flow as provider `qwen-oauth`.
@@ -22,6 +22,15 @@ If you want `qwen3.6-plus`, prefer the **Standard (pay-as-you-go)** endpoint.
Coding Plan support can lag behind the public catalog.
</Tip>
## Install plugin
Install the official plugin, then restart Gateway:
```bash
openclaw plugins install @openclaw/qwen-provider
openclaw gateway restart
```
## Getting started
Choose your plan type and follow the setup steps.
@@ -185,7 +194,7 @@ You can override with a custom `baseUrl` in config.
## Built-in catalog
OpenClaw currently ships this bundled Qwen catalog. The configured catalog is
OpenClaw currently ships this Qwen static catalog. The configured catalog is
endpoint-aware: Coding Plan configs omit models that are only known to work on
the Standard endpoint.
@@ -204,12 +213,12 @@ the Standard endpoint.
<Note>
Availability can still vary by endpoint and billing plan even when a model is
present in the bundled catalog.
present in the static catalog.
</Note>
## Thinking Controls
For reasoning-enabled Qwen Cloud models, the bundled provider maps OpenClaw
For reasoning-enabled Qwen Cloud models, the provider maps OpenClaw
thinking levels to DashScope's top-level `enable_thinking` request flag. Disabled
thinking sends `enable_thinking: false`; other thinking levels send
`enable_thinking: true`.
@@ -242,7 +251,7 @@ See [Video Generation](/tools/video-generation) for shared tool parameters, prov
<AccordionGroup>
<Accordion title="Image and video understanding">
The bundled Qwen plugin registers media understanding for images and video
The Qwen plugin registers media understanding for images and video
on the **Standard** DashScope endpoints (not the Coding Plan endpoints).
| Property | Value |
@@ -267,7 +276,7 @@ See [Video Generation](/tools/video-generation) for shared tool parameters, prov
`qwen3.6-plus`, switch to Standard (pay-as-you-go) instead of the Coding Plan
endpoint/key pair.
OpenClaw's bundled Qwen catalog does not advertise `qwen3.6-plus` on Coding
OpenClaw's Qwen static catalog does not advertise `qwen3.6-plus` on Coding
Plan endpoints, but explicitly configured `qwen/qwen3.6-plus` entries under
`models.providers.qwen.models` are honored on Coding Plan baseUrls so you
can opt that model in if Aliyun enables it on your subscription. The
@@ -279,13 +288,13 @@ See [Video Generation](/tools/video-generation) for shared tool parameters, prov
The `qwen` plugin is being positioned as the vendor home for the full Qwen
Cloud surface, not just coding/text models.
- **Text/chat models:** bundled now
- **Text/chat models:** available through the plugin
- **Tool calling, structured output, thinking:** inherited from the OpenAI-compatible transport
- **Image generation:** planned at the provider-plugin layer
- **Image/video understanding:** bundled now on the Standard endpoint
- **Image/video understanding:** available through the plugin on the Standard endpoint
- **Speech/audio:** planned at the provider-plugin layer
- **Memory embeddings/reranking:** planned through the embedding adapter surface
- **Video generation:** bundled now through the shared video-generation capability
- **Video generation:** available through the plugin through the shared video-generation capability
</Accordion>
@@ -300,7 +309,7 @@ See [Video Generation](/tools/video-generation) for shared tool parameters, prov
Coding Plan or Standard Qwen hosts still keeps video generation on the correct
regional DashScope video endpoint.
Current bundled Qwen video-generation limits:
Current Qwen video-generation limits:
- Up to **1** output video per request
- Up to **1** input image

View File

@@ -6,7 +6,7 @@ read_when:
title: "StepFun"
---
OpenClaw includes a bundled StepFun provider plugin with two provider ids:
The StepFun provider plugin supports two provider ids:
- `stepfun` for the standard endpoint
- `stepfun-plan` for the Step Plan endpoint
@@ -15,6 +15,15 @@ OpenClaw includes a bundled StepFun provider plugin with two provider ids:
Standard and Step Plan are **separate providers** with different endpoints and model ref prefixes (`stepfun/...` vs `stepfun-plan/...`). Use a China key with the `.com` endpoints and a global key with the `.ai` endpoints.
</Warning>
## Install plugin
Install the official plugin, then restart Gateway:
```bash
openclaw plugins install @openclaw/stepfun-provider
openclaw gateway restart
```
## Region and endpoint overview
| Endpoint | China (`.com`) | Global (`.ai`) |
@@ -199,7 +208,7 @@ Choose your provider surface and follow the setup steps.
</Accordion>
<Accordion title="Notes">
- The provider is bundled with OpenClaw, so there is no separate plugin install step.
- The provider is an official external package; install it before setup.
- `step-3.5-flash-2603` is currently exposed only on `stepfun-plan`.
- A single auth flow writes region-matched profiles for both `stepfun` and `stepfun-plan`, so both surfaces can be discovered together.
- Use `openclaw models list` and `openclaw models set <provider/model>` to inspect or switch models.

View File

@@ -382,15 +382,16 @@ The branch already has a real shared SQLite base:
exact transcript event row.
- Memory-core indexes now use explicit agent-database tables
`memory_index_meta`, `memory_index_sources`, `memory_index_chunks`, and
`memory_embedding_cache`; optional FTS/vector side indexes use the same
`memory_index_*` prefix instead of generic `meta`, `files`, `chunks`, or
`chunks_vec` tables. `memory_index_sources` is keyed by
`(source_kind, source_key)` and carries optional `session_id` ownership, so
session-derived sources and chunks cascade when a session is deleted. Cached
chunk embeddings are stored as Float32 SQLite BLOBs, not JSON text arrays.
These tables are derived/search cache, not canonical transcript storage; they
can be deleted and rebuilt from `sessions`, `transcript_events`, and memory
workspace files.
`memory_embedding_cache`, with `memory_index_state` tracking revision changes.
Optional FTS/vector side indexes are named `memory_index_chunks_fts` and
`memory_index_chunks_vec` instead of generic `meta`, `files`, `chunks`,
`chunks_fts`, or `chunks_vec` tables. The canonical names retain the current
path/source row shape and serialized embedding compatibility. These tables
are derived/search cache, not canonical transcript storage; they can be
deleted and rebuilt from memory workspace files and configured sources.
Opening a shipped generic-name memory index migrates its metadata, sources,
chunks, and embedding cache into the canonical tables; derived FTS/vector
tables are rebuilt under their canonical names.
- Subagent run recovery state now lives in typed shared `subagent_runs` rows
with indexed child, requester, and controller session keys. The old
`subagents/runs.json` file is doctor migration input only.
@@ -878,9 +879,9 @@ sessionId}` and session key context.
- Plugin runtime no longer exposes `api.runtime.agent.session.resolveTranscriptLocatorPath`;
plugin code uses SQLite row helpers and scope values.
- The public `session-store-runtime` SDK surface now only exports session row
and transcript row helpers. Raw SQLite database open/path and close/reset
helpers live in the focused `sqlite-runtime` SDK surface, so plugin tests no
longer pull the deprecated broad testing barrel for database cleanup.
and transcript row helpers. Focused SQLite schema/path/transaction helpers
live in `sqlite-runtime`; raw open/close/reset helpers remain local-only for
first-party tests.
- Legacy `.jsonl` trajectory/checkpoint filename classifiers now live in the
doctor legacy session-file module. Core session validation no longer imports
file-artifact helpers to decide normal SQLite session ids.
@@ -1491,10 +1492,11 @@ vfs_entries(namespace, path, kind, content_blob, metadata_json, updated_at)
tool_artifacts(run_id, artifact_id, kind, metadata_json, blob, created_at)
run_artifacts(run_id, path, kind, metadata_json, blob, created_at)
trajectory_runtime_events(session_id, run_id, seq, event_json, created_at)
memory_index_meta(meta_key, schema_version, provider, model, provider_key, sources_json, scope_hash, chunk_tokens, chunk_overlap, vector_dims, fts_tokenizer, config_hash, updated_at)
memory_index_sources(source_kind, source_key, path, session_id, hash, mtime, size)
memory_index_chunks(id, source_kind, source_key, path, session_id, start_line, end_line, hash, model, text, embedding, embedding_dims, updated_at)
memory_index_meta(key, value)
memory_index_sources(path, source, hash, mtime, size)
memory_index_chunks(id, path, source, start_line, end_line, hash, model, text, embedding, updated_at)
memory_embedding_cache(provider, model, provider_key, hash, embedding, dims, updated_at)
memory_index_state(id, revision)
cache_entries(scope, key, value_json, blob, expires_at, updated_at)
```
@@ -1722,9 +1724,12 @@ Keep shared coordination state in `state/openclaw.sqlite`:
`media_blobs` and removes the source files after successful row writes.
- Debug proxy capture sessions, events, and payload blobs. Done: captures live
in the shared state DB and open through the shared state DB bootstrap, schema,
WAL, and busy-timeout settings. There is no debug proxy runtime sidecar DB
override, blob directory, or proxy-capture-only generated schema/codegen
target.
WAL, and busy-timeout settings. Payload bytes are gzip-compressed in
`capture_blobs.data`; there is no debug proxy runtime sidecar DB override,
blob directory, or proxy-capture-only generated schema/codegen target.
Doctor/startup migration imports shipped `debug-proxy/capture.sqlite` rows
and referenced payload blobs, including active legacy DB/blob environment
overrides, then archives those sources while leaving CA certificates intact.
This phase also deletes duplicate sidecar openers, permission helpers, WAL
setup, filesystem pruning, and compatibility writers from those subsystems.

View File

@@ -154,7 +154,8 @@ See [Web tools](/tools/web).
### 5) Web fetch tool (Firecrawl)
`web_fetch` can call **Firecrawl** when an API key is present:
`web_fetch` can call **Firecrawl** with keyless starter access. Add an API key
for higher limits:
- `FIRECRAWL_API_KEY` or `plugins.entries.firecrawl.config.webFetch.apiKey`

View File

@@ -460,10 +460,12 @@ When sqlite-vec is unavailable, OpenClaw falls back to in-process cosine similar
## Index storage
| Key | Type | Default | Description |
| --------------------- | -------- | ------------------------------------- | ------------------------------------------- |
| `store.path` | `string` | `~/.openclaw/memory/{agentId}.sqlite` | Index location (supports `{agentId}` token) |
| `store.fts.tokenizer` | `string` | `unicode61` | FTS5 tokenizer (`unicode61` or `trigram`) |
Built-in memory indexes live in each agent's OpenClaw SQLite database at
`agents/<agentId>/agent/openclaw-agent.sqlite`.
| Key | Type | Default | Description |
| --------------------- | -------- | ----------- | ----------------------------------------- |
| `store.fts.tokenizer` | `string` | `unicode61` | FTS5 tokenizer (`unicode61` or `trigram`) |
---

View File

@@ -11,6 +11,15 @@ OpenClaw supports [Exa AI](https://exa.ai/) as a `web_search` provider. Exa
offers neural, keyword, and hybrid search modes with built-in content
extraction (highlights, text, summaries).
## Install plugin
Install the official plugin, then restart Gateway:
```bash
openclaw plugins install @openclaw/exa-plugin
openclaw gateway restart
```
## Get an API key
<Steps>

View File

@@ -2,7 +2,8 @@
summary: "Firecrawl search, scrape, and web_fetch fallback"
read_when:
- You want Firecrawl-backed web extraction
- You need a Firecrawl API key
- You want keyless Firecrawl web_fetch
- You need a Firecrawl API key for search or higher limits
- You want Firecrawl as a web_search provider
- You want anti-bot extraction for web_fetch
title: "Firecrawl"
@@ -17,10 +18,21 @@ OpenClaw can use **Firecrawl** in three ways:
It is a hosted extraction/search service that supports bot circumvention and caching,
which helps with JS-heavy sites or pages that block plain HTTP fetches.
## Get an API key
## Install plugin
1. Create a Firecrawl account and generate an API key.
2. Store it in config or set `FIRECRAWL_API_KEY` in the gateway environment.
Install the official plugin, then restart Gateway:
```bash
openclaw plugins install @openclaw/firecrawl-plugin
openclaw gateway restart
```
## Keyless web_fetch and API keys
The explicitly selected hosted Firecrawl `web_fetch` fallback supports starter
access without an API key. Add `FIRECRAWL_API_KEY` in the gateway environment
or configure it when you need higher limits. Firecrawl `web_search` and
`firecrawl_scrape` require an API key.
## Configure Firecrawl search
@@ -51,23 +63,29 @@ which helps with JS-heavy sites or pages that block plain HTTP fetches.
Notes:
- Choosing Firecrawl in onboarding or `openclaw configure --section web` enables the bundled Firecrawl plugin automatically.
- Choosing Firecrawl in onboarding or `openclaw configure --section web` enables the installed Firecrawl plugin automatically.
- `web_search` with Firecrawl supports `query` and `count`.
- For Firecrawl-specific controls like `sources`, `categories`, or result scraping, use `firecrawl_search`.
- `baseUrl` defaults to hosted Firecrawl at `https://api.firecrawl.dev`. Self-hosted overrides are allowed only for private/internal endpoints; HTTP is accepted only for those private targets.
- `FIRECRAWL_BASE_URL` is the shared env fallback for Firecrawl search and scrape base URLs.
## Configure Firecrawl scrape + web_fetch fallback
## Configure Firecrawl web_fetch fallback
```json5
{
tools: {
web: {
fetch: {
provider: "firecrawl", // explicit selection enables keyless fallback
},
},
},
plugins: {
entries: {
firecrawl: {
enabled: true,
config: {
webFetch: {
apiKey: "FIRECRAWL_API_KEY_HERE",
baseUrl: "https://api.firecrawl.dev",
onlyMainContent: true,
maxAgeMs: 172800000,
@@ -82,13 +100,15 @@ Notes:
Notes:
- Firecrawl fallback attempts run only when an API key is available (`plugins.entries.firecrawl.config.webFetch.apiKey` or `FIRECRAWL_API_KEY`).
- The explicitly selected Firecrawl `web_fetch` fallback works without an API key. When configured, OpenClaw sends `plugins.entries.firecrawl.config.webFetch.apiKey` or `FIRECRAWL_API_KEY` for higher limits.
- Choosing Firecrawl during onboarding or `openclaw configure --section web` enables the plugin and selects Firecrawl for `web_fetch` unless another fetch provider is already configured.
- `firecrawl_scrape` requires an API key.
- `maxAgeMs` controls how old cached results can be (ms). Default is 2 days.
- Legacy `tools.web.fetch.firecrawl.*` config is auto-migrated by `openclaw doctor --fix`.
- Firecrawl scrape/base URL overrides follow the same hosted/private rule as search: public hosted traffic uses `https://api.firecrawl.dev`; self-hosted overrides must resolve to private/internal endpoints.
- `firecrawl_scrape` rejects obvious private, loopback, metadata, and non-HTTP(S) target URLs before forwarding them to Firecrawl, matching the `web_fetch` target-safety contract for explicit Firecrawl scrape calls.
`firecrawl_scrape` reuses the same `plugins.entries.firecrawl.config.webFetch.*` settings and env vars.
`firecrawl_scrape` reuses the same `plugins.entries.firecrawl.config.webFetch.*` settings and env vars, including its required API key.
### Self-hosted Firecrawl
@@ -141,12 +161,12 @@ than basic-only scraping.
`web_fetch` extraction order:
1. Readability (local)
2. Firecrawl (if selected or auto-detected as the active web-fetch fallback)
2. Firecrawl (when selected, or auto-detected from configured credentials)
3. Basic HTML cleanup (last fallback)
The selection knob is `tools.web.fetch.provider`. If you omit it, OpenClaw
auto-detects the first ready web-fetch provider from available credentials.
Today the bundled provider is Firecrawl.
The official Firecrawl plugin provides that fallback.
## Related

View File

@@ -494,6 +494,23 @@ openclaw infer image generate \
--json
```
</Tab>
<Tab title="Generate (OpenAI low quality)">
```text
/tool image_generate action=generate model=openai/gpt-image-2 prompt="Low-cost draft poster for a quiet productivity app" quality=low openai='{"moderation":"low"}'
```
Equivalent CLI:
```bash
openclaw infer image generate \
--model openai/gpt-image-2 \
--quality low \
--openai-moderation low \
--prompt "Low-cost draft poster for a quiet productivity app" \
--json
```
</Tab>
<Tab title="Generate (two square)">
```text
@@ -517,11 +534,11 @@ openclaw infer image generate \
</Tab>
</Tabs>
The same `--output-format` and `--background` flags are available on
`openclaw infer image edit`; `--openai-background` remains as an
OpenAI-specific alias. Bundled providers other than OpenAI do not declare
explicit background control today, so `background: "transparent"` is reported
as ignored for them.
The same `--output-format`, `--background`, `--quality`, and
`--openai-moderation` flags are available on `openclaw infer image edit`;
`--openai-background` remains as an OpenAI-specific alias. Bundled providers
other than OpenAI do not declare explicit background control today, so
`background: "transparent"` is reported as ignored for them.
## Related

View File

@@ -7,7 +7,7 @@ read_when:
title: "Parallel search"
---
OpenClaw bundles two [Parallel](https://parallel.ai/) `web_search` providers:
The Parallel plugin provides two [Parallel](https://parallel.ai/) `web_search` providers:
- **Parallel Search (Free)** (`parallel-free`) -- Parallel's free
[Search MCP](https://docs.parallel.ai/integrations/mcp/search-mcp). Requires no
@@ -27,6 +27,15 @@ explicitly.
through Parallel.
</Note>
## Install plugin
Install the official plugin, then restart Gateway:
```bash
openclaw plugins install @openclaw/parallel-plugin
openclaw gateway restart
```
## API key (paid provider)
`parallel-free` requires no API key, but it still must be selected as the

View File

@@ -12,6 +12,15 @@ It returns structured results with `title`, `url`, and `snippet` fields.
For compatibility, OpenClaw also supports legacy Perplexity Sonar/OpenRouter setups.
If you use `OPENROUTER_API_KEY`, an `sk-or-...` key in `plugins.entries.perplexity.config.webSearch.apiKey`, or set `plugins.entries.perplexity.config.webSearch.baseUrl` / `model`, the provider switches to the chat-completions path and returns AI-synthesized answers with citations instead of structured Search API results.
## Install plugin
Install the official plugin, then restart Gateway:
```bash
openclaw plugins install @openclaw/perplexity-plugin
openclaw gateway restart
```
## Getting a Perplexity API key
1. Create a Perplexity account at [perplexity.ai/settings/api](https://www.perplexity.ai/settings/api)

View File

@@ -144,7 +144,7 @@ the shared live sweep:
| Alibaba | ✓ | ✓ | ✓ | `generate`, `imageToVideo`; `videoToVideo` skipped because this provider needs remote `http(s)` video URLs |
| BytePlus | ✓ | ✓ | - | `generate`, `imageToVideo` |
| ComfyUI | ✓ | ✓ | - | Not in the shared sweep; workflow-specific coverage lives with Comfy tests |
| DeepInfra | ✓ | - | - | `generate`; native DeepInfra video schemas are text-to-video in the bundled contract |
| DeepInfra | ✓ | - | - | `generate`; native DeepInfra video schemas are text-to-video in the plugin contract |
| fal | ✓ | ✓ | ✓ | `generate`, `imageToVideo`; `videoToVideo` only when using Seedance reference-to-video |
| Google | ✓ | ✓ | ✓ | `generate`, `imageToVideo`; shared `videoToVideo` skipped because the current buffer-backed Gemini/Veo sweep does not accept that input |
| MiniMax | ✓ | ✓ | - | `generate`, `imageToVideo` |

View File

@@ -48,7 +48,7 @@ Truncate output to this many characters.
Runs Readability (main-content extraction) on the HTML response.
</Step>
<Step title="Fallback (optional)">
If Readability fails and Firecrawl is configured, retries through the
If Readability fails and Firecrawl is selected, retries through the
Firecrawl API with bot-circumvention mode.
</Step>
<Step title="Cache">
@@ -120,7 +120,7 @@ If Readability extraction fails, `web_fetch` can fall back to
enabled: true,
config: {
webFetch: {
apiKey: "fc-...", // optional if FIRECRAWL_API_KEY is set
// apiKey: "fc-...", // optional; omit for keyless starter access
baseUrl: "https://api.firecrawl.dev",
onlyMainContent: true,
maxAgeMs: 86400000, // cache duration (1 day)
@@ -133,11 +133,11 @@ If Readability extraction fails, `web_fetch` can fall back to
}
```
`plugins.entries.firecrawl.config.webFetch.apiKey` supports SecretRef objects.
`plugins.entries.firecrawl.config.webFetch.apiKey` is optional and supports SecretRef objects.
Legacy `tools.web.fetch.firecrawl.*` config is auto-migrated by `openclaw doctor --fix`.
<Note>
If Firecrawl is enabled and its SecretRef is unresolved with no
If you configure a Firecrawl API-key SecretRef and it is unresolved with no
`FIRECRAWL_API_KEY` env fallback, gateway startup fails fast.
</Note>
@@ -151,10 +151,13 @@ Current runtime behavior:
- `tools.web.fetch.provider` selects the fetch fallback provider explicitly.
- If `provider` is omitted, OpenClaw auto-detects the first ready web-fetch
provider from available credentials. Non-sandboxed `web_fetch` can use
provider from configured credentials. Non-sandboxed `web_fetch` can use
installed plugins that declare `contracts.webFetchProviders` and register a
matching provider at runtime. Today the bundled provider is Firecrawl.
- Sandboxed `web_fetch` calls stay limited to bundled providers.
matching provider at runtime. The official Firecrawl plugin provides this
fallback.
- Sandboxed `web_fetch` calls allow bundled providers plus installed providers
whose official npm or ClawHub provenance is verified. Today that permits the
official Firecrawl plugin; third-party external fetch plugins stay excluded.
- If Readability is disabled, `web_fetch` skips straight to the selected
provider fallback. If no provider is available, it fails closed.

View File

@@ -260,7 +260,7 @@ to route them through the managed path.
<Note>
All provider key fields support SecretRef objects. Plugin-scoped SecretRefs
under `plugins.entries.<plugin>.config.webSearch.apiKey` are resolved for the
bundled API-backed web search providers, including Brave, Exa, Firecrawl,
installed API-backed web search providers, including Brave, Exa, Firecrawl,
Gemini, Grok, Kimi, MiniMax, Parallel, Perplexity, and Tavily,
whether the provider is picked explicitly via `tools.web.search.provider` or
selected through auto-detect. In auto-detect mode, OpenClaw resolves only the
@@ -307,10 +307,11 @@ plugin or run `openclaw doctor --fix` to clean up the stale config.
- choose it with `tools.web.fetch.provider`
- or omit that field and let OpenClaw auto-detect the first ready web-fetch
provider from available credentials
provider from configured credentials
- non-sandboxed `web_fetch` can use installed plugin providers that declare
`contracts.webFetchProviders`; sandboxed fetches stay bundled-only
- today the bundled web-fetch provider is Firecrawl, configured under
`contracts.webFetchProviders`; sandboxed fetches allow bundled providers and
verified official plugin installs, but exclude third-party external plugins
- the official Firecrawl plugin provides web-fetch fallback, configured under
`plugins.entries.firecrawl.config.webFetch.*`
When you choose **Kimi** during `openclaw onboard` or

View File

@@ -20,7 +20,7 @@ vi.mock("./cli-auth-seam.js", async (importActual) => {
};
});
const { buildAnthropicCliMigrationResult, hasClaudeCliAuth } = await import("./cli-migration.js");
const { buildAnthropicCliMigrationResult } = await import("./cli-migration.js");
const { resolveKnownAnthropicModelRef } = await import("./claude-model-refs.js");
const { createTestWizardPrompter, registerSingleProviderPlugin } =
await import("openclaw/plugin-sdk/plugin-test-runtime");
@@ -135,23 +135,6 @@ function createProviderAuthMethodNonInteractiveContext(
}
describe("anthropic cli migration", () => {
it("detects local Claude CLI auth", () => {
readClaudeCliCredentialsForSetup.mockReturnValue({ type: "oauth" });
expect(hasClaudeCliAuth()).toBe(true);
});
it("uses the non-interactive Claude auth probe without keychain prompts", () => {
readClaudeCliCredentialsForSetup.mockReset();
readClaudeCliCredentialsForSetupNonInteractive.mockReset();
readClaudeCliCredentialsForSetup.mockReturnValue(null);
readClaudeCliCredentialsForSetupNonInteractive.mockReturnValue({ type: "oauth" });
expect(hasClaudeCliAuth({ allowKeychainPrompt: false })).toBe(true);
expect(readClaudeCliCredentialsForSetup).not.toHaveBeenCalled();
expect(readClaudeCliCredentialsForSetupNonInteractive).toHaveBeenCalledTimes(1);
});
it("keeps anthropic defaults and selects the claude-cli runtime", () => {
const result = buildAnthropicCliMigrationResult({
agents: {

View File

@@ -12,10 +12,7 @@ import {
normalizeLowercaseStringOrEmpty,
} from "openclaw/plugin-sdk/string-coerce-runtime";
import { resolveClaudeCliAnthropicModelRefs } from "./claude-model-refs.js";
import {
readClaudeCliCredentialsForSetup,
readClaudeCliCredentialsForSetupNonInteractive,
} from "./cli-auth-seam.js";
import type { readClaudeCliCredentialsForSetup } from "./cli-auth-seam.js";
import { CLAUDE_CLI_BACKEND_ID, CLAUDE_CLI_DEFAULT_ALLOWLIST_REFS } from "./cli-shared.js";
type AgentDefaultsModel = NonNullable<NonNullable<OpenClawConfig["agents"]>["defaults"]>["model"];
@@ -172,15 +169,6 @@ function modelEntryWithClaudeCliRuntime(entry: unknown): Record<string, unknown>
return base;
}
/** Return whether Claude CLI credentials are available for setup migration. */
export function hasClaudeCliAuth(options?: { allowKeychainPrompt?: boolean }): boolean {
return Boolean(
options?.allowKeychainPrompt === false
? readClaudeCliCredentialsForSetupNonInteractive()
: readClaudeCliCredentialsForSetup(),
);
}
function buildClaudeCliAuthProfiles(
credential?: ClaudeCliCredential | null,
): ProviderAuthResult["profiles"] {

View File

@@ -0,0 +1,12 @@
# OpenClaw Arcee AI Provider
Official OpenClaw provider plugin for Arcee AI.
Install from OpenClaw:
```bash
openclaw plugins install @openclaw/arcee-provider
openclaw gateway restart
```
See <https://docs.openclaw.ai/providers/arcee> for setup and configuration.

12
extensions/arcee/npm-shrinkwrap.json generated Normal file
View File

@@ -0,0 +1,12 @@
{
"name": "@openclaw/arcee-provider",
"version": "2026.6.8",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@openclaw/arcee-provider",
"version": "2026.6.8"
}
}
}

View File

@@ -1,8 +1,11 @@
{
"name": "@openclaw/arcee-provider",
"version": "2026.6.8",
"private": true,
"description": "OpenClaw Arcee provider plugin",
"description": "OpenClaw Arcee provider plugin.",
"repository": {
"type": "git",
"url": "https://github.com/openclaw/openclaw"
},
"type": "module",
"devDependencies": {
"@openclaw/plugin-sdk": "workspace:*"
@@ -10,6 +13,23 @@
"openclaw": {
"extensions": [
"./index.ts"
]
],
"install": {
"clawhubSpec": "clawhub:@openclaw/arcee-provider",
"npmSpec": "@openclaw/arcee-provider",
"defaultChoice": "npm",
"minHostVersion": ">=2026.6.8"
},
"compat": {
"pluginApi": ">=2026.6.8"
},
"build": {
"openclawVersion": "2026.6.8",
"bundledDist": false
},
"release": {
"publishToClawHub": true,
"publishToNpm": true
}
}
}

View File

@@ -14,7 +14,7 @@
},
"devDependencies": {
"@openclaw/plugin-sdk": "workspace:*",
"undici": "8.3.0"
"undici": "8.5.0"
},
"openclaw": {
"extensions": [

View File

@@ -19,9 +19,10 @@ import {
hasProxyEnv,
withManagedProxyForCdpUrl,
withNoProxyForCdpUrl,
withNoProxyForLocalhost,
} from "./cdp-proxy-bypass.js";
const LOOPBACK_CDP_URL = "http://127.0.0.1:9222";
beforeEach(() => {
vi.useRealTimers();
registerManagedProxyBrowserCdpBypassMock.mockReset();
@@ -152,7 +153,7 @@ describe("cdp-proxy-bypass", () => {
});
});
describe("withNoProxyForLocalhost", () => {
describe("withNoProxyForCdpUrl loopback", () => {
const saved: Record<string, string | undefined> = {};
const vars = ["HTTP_PROXY", "NO_PROXY", "no_proxy"];
@@ -178,7 +179,7 @@ describe("cdp-proxy-bypass", () => {
delete process.env.no_proxy;
let capturedNoProxy: string | undefined;
await withNoProxyForLocalhost(async () => {
await withNoProxyForCdpUrl(LOOPBACK_CDP_URL, async () => {
capturedNoProxy = process.env.NO_PROXY;
});
@@ -194,7 +195,7 @@ describe("cdp-proxy-bypass", () => {
process.env.NO_PROXY = "internal.corp";
let capturedNoProxy: string | undefined;
await withNoProxyForLocalhost(async () => {
await withNoProxyForCdpUrl(LOOPBACK_CDP_URL, async () => {
capturedNoProxy = process.env.NO_PROXY;
});
@@ -210,7 +211,7 @@ describe("cdp-proxy-bypass", () => {
delete process.env.ALL_PROXY;
delete process.env.NO_PROXY;
await withNoProxyForLocalhost(async () => {
await withNoProxyForCdpUrl(LOOPBACK_CDP_URL, async () => {
expect(process.env.NO_PROXY).toBeUndefined();
});
});
@@ -220,7 +221,7 @@ describe("cdp-proxy-bypass", () => {
delete process.env.NO_PROXY;
await expect(
withNoProxyForLocalhost(async () => {
withNoProxyForCdpUrl(LOOPBACK_CDP_URL, async () => {
throw new Error("boom");
}),
).rejects.toThrow("boom");
@@ -230,16 +231,13 @@ describe("cdp-proxy-bypass", () => {
});
});
describe("withNoProxyForLocalhost concurrency", () => {
describe("withNoProxyForCdpUrl concurrency", () => {
it("does not leak NO_PROXY when called concurrently", async () => {
await withIsolatedNoProxyEnv(async () => {
const { withNoProxyForLocalhost: withNoProxyForLocalhostScoped } =
await import("./cdp-proxy-bypass.js");
const releaseA = createDeferred();
const enteredA = createDeferred();
const callA = withNoProxyForLocalhostScoped(async () => {
const callA = withNoProxyForCdpUrl(LOOPBACK_CDP_URL, async () => {
expect(process.env.NO_PROXY).toContain("localhost");
expect(process.env.NO_PROXY).toContain("[::1]");
enteredA.resolve();
@@ -249,7 +247,7 @@ describe("withNoProxyForLocalhost concurrency", () => {
await enteredA.promise;
const callB = withNoProxyForLocalhostScoped(async () => {
const callB = withNoProxyForCdpUrl(LOOPBACK_CDP_URL, async () => {
return "b";
});
@@ -263,25 +261,22 @@ describe("withNoProxyForLocalhost concurrency", () => {
});
});
describe("withNoProxyForLocalhost reverse exit order", () => {
describe("withNoProxyForCdpUrl reverse exit order", () => {
it("restores NO_PROXY when first caller exits before second", async () => {
await withIsolatedNoProxyEnv(async () => {
const { withNoProxyForLocalhost: withNoProxyForLocalhostItem } =
await import("./cdp-proxy-bypass.js");
const enteredA = createDeferred();
const enteredB = createDeferred();
const releaseA = createDeferred();
const releaseB = createDeferred();
const callA = withNoProxyForLocalhostItem(async () => {
const callA = withNoProxyForCdpUrl(LOOPBACK_CDP_URL, async () => {
enteredA.resolve();
await releaseA.promise;
return "a";
});
await enteredA.promise;
const callB = withNoProxyForLocalhostItem(async () => {
const callB = withNoProxyForCdpUrl(LOOPBACK_CDP_URL, async () => {
enteredB.resolve();
await releaseB.promise;
return "b";
@@ -301,7 +296,7 @@ describe("withNoProxyForLocalhost reverse exit order", () => {
});
});
describe("withNoProxyForLocalhost preserves user-configured NO_PROXY", () => {
describe("withNoProxyForCdpUrl preserves user-configured NO_PROXY", () => {
it("does not delete NO_PROXY when loopback entries already present", async () => {
const userNoProxy = "localhost,127.0.0.1,[::1],myhost.internal";
process.env.NO_PROXY = userNoProxy;
@@ -309,10 +304,7 @@ describe("withNoProxyForLocalhost preserves user-configured NO_PROXY", () => {
process.env.HTTP_PROXY = "http://proxy:8080";
try {
const { withNoProxyForLocalhost: withNoProxyForLocalhostCandidate } =
await import("./cdp-proxy-bypass.js");
await withNoProxyForLocalhostCandidate(async () => {
await withNoProxyForCdpUrl(LOOPBACK_CDP_URL, async () => {
// Should not modify since loopback is already covered
expect(process.env.NO_PROXY).toBe(userNoProxy);
return "ok";
@@ -336,10 +328,7 @@ describe("withNoProxyForLocalhost preserves user-configured NO_PROXY", () => {
process.env.HTTP_PROXY = "http://proxy:8080";
try {
const { withNoProxyForLocalhost: withNoProxyForLocalhostEntry } =
await import("./cdp-proxy-bypass.js");
await withNoProxyForLocalhostEntry(async () => {
await withNoProxyForCdpUrl(LOOPBACK_CDP_URL, async () => {
expect(process.env.NO_PROXY).toBe(`${coveredNoProxy},localhost,127.0.0.1,[::1]`);
expect(process.env.no_proxy).toBe(`${staleLowerNoProxy},localhost,127.0.0.1,[::1]`);
});
@@ -360,10 +349,7 @@ describe("withNoProxyForLocalhost preserves user-configured NO_PROXY", () => {
process.env.HTTP_PROXY = "http://proxy:8080";
try {
const { withNoProxyForLocalhost: withNoProxyForLocalhostResult } =
await import("./cdp-proxy-bypass.js");
await withNoProxyForLocalhostResult(async () => {
await withNoProxyForCdpUrl(LOOPBACK_CDP_URL, async () => {
expect(process.env.NO_PROXY).toBe(`${lowerNoProxy},localhost,127.0.0.1,[::1]`);
expect(process.env.no_proxy).toBe(`${lowerNoProxy},localhost,127.0.0.1,[::1]`);
});
@@ -384,10 +370,7 @@ describe("withNoProxyForLocalhost preserves user-configured NO_PROXY", () => {
process.env.HTTP_PROXY = "http://proxy:8080";
try {
const { withNoProxyForLocalhost: withNoProxyForLocalhostValue } =
await import("./cdp-proxy-bypass.js");
await withNoProxyForLocalhostValue(async () => {
await withNoProxyForCdpUrl(LOOPBACK_CDP_URL, async () => {
expect(process.env.NO_PROXY).toBe(`${userNoProxy},localhost,127.0.0.1,[::1]`);
expect(process.env.no_proxy).toBe(`${userNoProxy},localhost,127.0.0.1,[::1]`);
delete process.env.no_proxy;
@@ -409,10 +392,7 @@ describe("withNoProxyForLocalhost preserves user-configured NO_PROXY", () => {
process.env.HTTP_PROXY = "http://proxy:8080";
try {
const { withNoProxyForLocalhost: withNoProxyForLocalhostLocal } =
await import("./cdp-proxy-bypass.js");
await withNoProxyForLocalhostLocal(async () => {
await withNoProxyForCdpUrl(LOOPBACK_CDP_URL, async () => {
expect(process.env.NO_PROXY).toBe(`${userNoProxy},localhost,127.0.0.1,[::1]`);
expect(process.env.no_proxy).toBe(`${userNoProxy},localhost,127.0.0.1,[::1]`);
});

View File

@@ -67,10 +67,6 @@ function appendLoopbackEntries(value: string | undefined): string {
return value ? `${value},${LOOPBACK_ENTRIES}` : LOOPBACK_ENTRIES;
}
export async function withNoProxyForLocalhost<T>(fn: () => Promise<T>): Promise<T> {
return await withNoProxyForCdpUrl("http://127.0.0.1", fn);
}
function isLoopbackCdpUrl(url: string): boolean {
try {
return isLoopbackHost(new URL(url).hostname);

View File

@@ -6,18 +6,12 @@ import "../test-support/browser-security.mock.js";
import {
type AriaSnapshotNode,
captureScreenshot,
captureScreenshotPng,
createTargetViaCdp,
type DomSnapshotNode,
evaluateJavaScript,
formatAriaSnapshot,
getDomText,
normalizeCdpWsUrl,
type QueryMatch,
querySelector,
type RawAXNode,
snapshotAria,
snapshotDom,
snapshotRoleViaCdp,
} from "./cdp.js";
@@ -164,27 +158,6 @@ describe("cdp internal", () => {
expect(buf.toString("utf8")).toBe("PNGDATA");
});
it("captureScreenshotPng forwards to the png captureScreenshot flow", async () => {
const server = await startMockWsServer((msg, socket) => {
if (msg.method === "Page.enable") {
socket.send(JSON.stringify({ id: msg.id, result: {} }));
return;
}
if (msg.method === "Page.captureScreenshot") {
expect(msg.params?.format).toBe("png");
socket.send(
JSON.stringify({
id: msg.id,
result: { data: Buffer.from("WRAPPED").toString("base64") },
}),
);
}
});
wss = server.wss;
const buf = await captureScreenshotPng({ wsUrl: server.wsUrl });
expect(buf.toString("utf8")).toBe("WRAPPED");
});
it("clamps out-of-range JPEG quality values into [0, 100]", async () => {
const { observed } = await captureScreenshotAndObserveParams({
format: "jpeg",
@@ -730,257 +703,6 @@ describe("cdp internal", () => {
});
});
describe("snapshotDom", () => {
it("returns the nodes array from the evaluated expression", async () => {
const server = await startMockWsServer((msg, socket) => {
if (msg.method === "Runtime.enable") {
socket.send(JSON.stringify({ id: msg.id, result: {} }));
return;
}
if (msg.method === "Runtime.evaluate") {
const fake: DomSnapshotNode[] = [{ ref: "n1", parentRef: null, depth: 0, tag: "html" }];
socket.send(
JSON.stringify({
id: msg.id,
result: { result: { value: { nodes: fake } } },
}),
);
}
});
wss = server.wss;
const snap = await snapshotDom({ wsUrl: server.wsUrl, limit: 10, maxTextChars: 200 });
expect(snap.nodes[0]?.tag).toBe("html");
});
it("returns an empty nodes array when the value is not an object", async () => {
const server = await startMockWsServer((msg, socket) => {
if (msg.method === "Runtime.enable") {
socket.send(JSON.stringify({ id: msg.id, result: {} }));
return;
}
if (msg.method === "Runtime.evaluate") {
socket.send(
JSON.stringify({
id: msg.id,
result: { result: { value: null } },
}),
);
}
});
wss = server.wss;
const snap = await snapshotDom({ wsUrl: server.wsUrl });
expect(snap.nodes).toStrictEqual([]);
});
it("returns an empty nodes array when nodes is not an array", async () => {
const server = await startMockWsServer((msg, socket) => {
if (msg.method === "Runtime.enable") {
socket.send(JSON.stringify({ id: msg.id, result: {} }));
return;
}
if (msg.method === "Runtime.evaluate") {
socket.send(
JSON.stringify({
id: msg.id,
result: { result: { value: { nodes: "not-an-array" } } },
}),
);
}
});
wss = server.wss;
const snap = await snapshotDom({ wsUrl: server.wsUrl });
expect(snap.nodes).toStrictEqual([]);
});
it("uses default DOM snapshot budgets for non-finite options", async () => {
const server = await startMockWsServer((msg, socket) => {
if (msg.method === "Runtime.enable") {
socket.send(JSON.stringify({ id: msg.id, result: {} }));
return;
}
if (msg.method === "Runtime.evaluate") {
const expression =
typeof msg.params?.expression === "string" ? msg.params.expression : "";
expect(expression).toContain("const maxNodes = 800;");
expect(expression).toContain("const maxText = 220;");
socket.send(JSON.stringify({ id: msg.id, result: { result: { value: { nodes: [] } } } }));
}
});
wss = server.wss;
const snap = await snapshotDom({
wsUrl: server.wsUrl,
limit: Number.NaN,
maxTextChars: Number.NaN,
});
expect(snap.nodes).toStrictEqual([]);
});
});
describe("getDomText", () => {
it("returns the evaluated string for text format", async () => {
const server = await startMockWsServer((msg, socket) => {
if (msg.method === "Runtime.enable") {
socket.send(JSON.stringify({ id: msg.id, result: {} }));
return;
}
if (msg.method === "Runtime.evaluate") {
socket.send(
JSON.stringify({
id: msg.id,
result: { result: { value: "plain body text" } },
}),
);
}
});
wss = server.wss;
const res = await getDomText({ wsUrl: server.wsUrl, format: "text", maxChars: 100 });
expect(res.text).toBe("plain body text");
});
it("returns the html outerHTML for html format with a selector", async () => {
const server = await startMockWsServer((msg, socket) => {
if (msg.method === "Runtime.enable") {
socket.send(JSON.stringify({ id: msg.id, result: {} }));
return;
}
if (msg.method === "Runtime.evaluate") {
socket.send(
JSON.stringify({
id: msg.id,
result: { result: { value: "<div>html</div>" } },
}),
);
}
});
wss = server.wss;
const res = await getDomText({
wsUrl: server.wsUrl,
format: "html",
selector: "#foo",
});
expect(res.text).toBe("<div>html</div>");
});
it("coerces numeric/boolean values to strings and falls back to empty for objects", async () => {
const responses: unknown[] = [42, true, { shape: "object" }];
let i = 0;
const server = await startMockWsServer((msg, socket) => {
if (msg.method === "Runtime.enable") {
socket.send(JSON.stringify({ id: msg.id, result: {} }));
return;
}
if (msg.method === "Runtime.evaluate") {
socket.send(
JSON.stringify({
id: msg.id,
result: { result: { value: responses[i++] } },
}),
);
}
});
wss = server.wss;
const num = await getDomText({ wsUrl: server.wsUrl, format: "text" });
expect(num.text).toBe("42");
const bool = await getDomText({ wsUrl: server.wsUrl, format: "text" });
expect(bool.text).toBe("true");
const obj = await getDomText({ wsUrl: server.wsUrl, format: "text" });
expect(obj.text).toBe("");
});
it("uses the default text budget for non-finite maxChars", async () => {
const server = await startMockWsServer((msg, socket) => {
if (msg.method === "Runtime.enable") {
socket.send(JSON.stringify({ id: msg.id, result: {} }));
return;
}
if (msg.method === "Runtime.evaluate") {
const expression =
typeof msg.params?.expression === "string" ? msg.params.expression : "";
expect(expression).toContain("const max = 200000;");
socket.send(JSON.stringify({ id: msg.id, result: { result: { value: "ok" } } }));
}
});
wss = server.wss;
const res = await getDomText({
wsUrl: server.wsUrl,
format: "text",
maxChars: Number.NaN,
});
expect(res.text).toBe("ok");
});
});
describe("querySelector", () => {
it("returns the matches array from the evaluated expression", async () => {
const server = await startMockWsServer((msg, socket) => {
if (msg.method === "Runtime.enable") {
socket.send(JSON.stringify({ id: msg.id, result: {} }));
return;
}
if (msg.method === "Runtime.evaluate") {
const matches: QueryMatch[] = [{ index: 1, tag: "button", text: "OK" }];
socket.send(JSON.stringify({ id: msg.id, result: { result: { value: matches } } }));
}
});
wss = server.wss;
const out = await querySelector({
wsUrl: server.wsUrl,
selector: "button",
limit: 5,
maxTextChars: 100,
maxHtmlChars: 500,
});
expect(out.matches[0]?.tag).toBe("button");
});
it("returns an empty array when the value is not an array", async () => {
const server = await startMockWsServer((msg, socket) => {
if (msg.method === "Runtime.enable") {
socket.send(JSON.stringify({ id: msg.id, result: {} }));
return;
}
if (msg.method === "Runtime.evaluate") {
socket.send(JSON.stringify({ id: msg.id, result: { result: { value: "not-array" } } }));
}
});
wss = server.wss;
const out = await querySelector({ wsUrl: server.wsUrl, selector: "button" });
expect(out.matches).toStrictEqual([]);
});
it("uses default query budgets for non-finite options", async () => {
const server = await startMockWsServer((msg, socket) => {
if (msg.method === "Runtime.enable") {
socket.send(JSON.stringify({ id: msg.id, result: {} }));
return;
}
if (msg.method === "Runtime.evaluate") {
const expression =
typeof msg.params?.expression === "string" ? msg.params.expression : "";
expect(expression).toContain("const lim = 20;");
expect(expression).toContain("const maxText = 500;");
expect(expression).toContain("const maxHtml = 1500;");
socket.send(JSON.stringify({ id: msg.id, result: { result: { value: [] } } }));
}
});
wss = server.wss;
const out = await querySelector({
wsUrl: server.wsUrl,
selector: "button",
limit: Number.NaN,
maxTextChars: Number.NaN,
maxHtmlChars: Number.NaN,
});
expect(out.matches).toStrictEqual([]);
});
});
describe("normalizeCdpWsUrl fill-in", () => {
it("respects an already-non-loopback ws hostname (no-rewrite branch)", () => {
// Covers the else side of the loopback/wildcard-guard in normalizeCdpWsUrl.
@@ -1287,21 +1009,4 @@ describe("cdp internal", () => {
});
});
describe("getDomText branch coverage", () => {
it("coerces a missing evaluated value to an empty string", async () => {
// Covers the right-hand side of `evaluated.result?.value ?? ""`.
const server = await startMockWsServer((msg, socket) => {
if (msg.method === "Runtime.enable") {
socket.send(JSON.stringify({ id: msg.id, result: {} }));
return;
}
if (msg.method === "Runtime.evaluate") {
socket.send(JSON.stringify({ id: msg.id, result: { result: {} } }));
}
});
wss = server.wss;
const res = await getDomText({ wsUrl: server.wsUrl, format: "text" });
expect(res.text).toBe("");
});
});
});

View File

@@ -65,20 +65,6 @@ export function normalizeCdpWsUrl(wsUrl: string, cdpUrl: string): string {
return ws.toString();
}
/** Capture a PNG screenshot through CDP. */
export async function captureScreenshotPng(opts: {
wsUrl: string;
fullPage?: boolean;
timeoutMs?: number;
}): Promise<Buffer> {
return await captureScreenshot({
wsUrl: opts.wsUrl,
fullPage: opts.fullPage,
format: "png",
timeoutMs: opts.timeoutMs,
});
}
/** Capture a PNG or JPEG screenshot through CDP, optionally full-page. */
export async function captureScreenshot(opts: {
wsUrl: string;
@@ -979,200 +965,3 @@ export async function snapshotRoleViaCdp(opts: {
{ commandTimeoutMs: opts.timeoutMs ?? 5000 },
);
}
/** Capture a raw DOM snapshot through CDP. */
export async function snapshotDom(opts: {
wsUrl: string;
limit?: number;
maxTextChars?: number;
}): Promise<{
nodes: DomSnapshotNode[];
}> {
const limit = resolveIntegerOption(opts.limit, 800, { min: 1, max: 5000 });
const maxTextChars = resolveIntegerOption(opts.maxTextChars, 220, { min: 0, max: 5000 });
const expression = `(() => {
const maxNodes = ${JSON.stringify(limit)};
const maxText = ${JSON.stringify(maxTextChars)};
const lower = (value) => String(value || "").toLocaleLowerCase();
const nodes = [];
const root = document.documentElement;
if (!root) return { nodes };
const stack = [{ el: root, depth: 0, parentRef: null }];
while (stack.length && nodes.length < maxNodes) {
const cur = stack.pop();
const el = cur.el;
if (!el || el.nodeType !== 1) continue;
const ref = "n" + String(nodes.length + 1);
const tag = lower(el.tagName);
const id = el.id ? String(el.id) : undefined;
const className = el.className ? String(el.className).slice(0, 300) : undefined;
const role = el.getAttribute && el.getAttribute("role") ? String(el.getAttribute("role")) : undefined;
const name = el.getAttribute && el.getAttribute("aria-label") ? String(el.getAttribute("aria-label")) : undefined;
let text = "";
try { text = String(el.innerText || "").trim(); } catch {}
if (maxText && text.length > maxText) text = text.slice(0, maxText) + "…";
const href = (el.href !== undefined && el.href !== null) ? String(el.href) : undefined;
const type = (el.type !== undefined && el.type !== null) ? String(el.type) : undefined;
const value = (el.value !== undefined && el.value !== null) ? String(el.value).slice(0, 500) : undefined;
nodes.push({
ref,
parentRef: cur.parentRef,
depth: cur.depth,
tag,
...(id ? { id } : {}),
...(className ? { className } : {}),
...(role ? { role } : {}),
...(name ? { name } : {}),
...(text ? { text } : {}),
...(href ? { href } : {}),
...(type ? { type } : {}),
...(value ? { value } : {}),
});
const children = el.children ? Array.from(el.children) : [];
for (let i = children.length - 1; i >= 0; i--) {
stack.push({ el: children[i], depth: cur.depth + 1, parentRef: ref });
}
}
return { nodes };
})()`;
const evaluated = await evaluateJavaScript({
wsUrl: opts.wsUrl,
expression,
awaitPromise: true,
returnByValue: true,
});
const value = evaluated.result?.value;
if (!value || typeof value !== "object") {
return { nodes: [] };
}
const nodes = (value as { nodes?: unknown }).nodes;
return { nodes: Array.isArray(nodes) ? (nodes as DomSnapshotNode[]) : [] };
}
/** Simplified DOM node returned by DOM snapshot helpers. */
export type DomSnapshotNode = {
ref: string;
parentRef: string | null;
depth: number;
tag: string;
id?: string;
className?: string;
role?: string;
name?: string;
text?: string;
href?: string;
type?: string;
value?: string;
};
/** Extract visible DOM text from a CDP target. */
export async function getDomText(opts: {
wsUrl: string;
format: "html" | "text";
maxChars?: number;
selector?: string;
}): Promise<{ text: string }> {
const maxChars = resolveIntegerOption(opts.maxChars, 200_000, { min: 0, max: 5_000_000 });
const selectorExpr = opts.selector ? JSON.stringify(opts.selector) : "null";
const expression = `(() => {
const fmt = ${JSON.stringify(opts.format)};
const max = ${JSON.stringify(maxChars)};
const sel = ${selectorExpr};
const pick = sel ? document.querySelector(sel) : null;
let out = "";
if (fmt === "text") {
const el = pick || document.body || document.documentElement;
try { out = String(el && el.innerText ? el.innerText : ""); } catch { out = ""; }
} else {
const el = pick || document.documentElement;
try { out = String(el && el.outerHTML ? el.outerHTML : ""); } catch { out = ""; }
}
if (max && out.length > max) out = out.slice(0, max) + "\\n<!-- …truncated… -->";
return out;
})()`;
const evaluated = await evaluateJavaScript({
wsUrl: opts.wsUrl,
expression,
awaitPromise: true,
returnByValue: true,
});
const textValue = (evaluated.result?.value ?? "") as unknown;
const text =
typeof textValue === "string"
? textValue
: typeof textValue === "number" || typeof textValue === "boolean"
? String(textValue)
: "";
return { text };
}
/** Query a selector in a CDP target and return matching node metadata. */
export async function querySelector(opts: {
wsUrl: string;
selector: string;
limit?: number;
maxTextChars?: number;
maxHtmlChars?: number;
}): Promise<{
matches: QueryMatch[];
}> {
const limit = resolveIntegerOption(opts.limit, 20, { min: 1, max: 200 });
const maxText = resolveIntegerOption(opts.maxTextChars, 500, { min: 0, max: 5000 });
const maxHtml = resolveIntegerOption(opts.maxHtmlChars, 1500, { min: 0, max: 20_000 });
const expression = `(() => {
const sel = ${JSON.stringify(opts.selector)};
const lim = ${JSON.stringify(limit)};
const maxText = ${JSON.stringify(maxText)};
const maxHtml = ${JSON.stringify(maxHtml)};
const lower = (value) => String(value || "").toLocaleLowerCase();
const els = Array.from(document.querySelectorAll(sel)).slice(0, lim);
return els.map((el, i) => {
const tag = lower(el.tagName);
const id = el.id ? String(el.id) : undefined;
const className = el.className ? String(el.className).slice(0, 300) : undefined;
let text = "";
try { text = String(el.innerText || "").trim(); } catch {}
if (maxText && text.length > maxText) text = text.slice(0, maxText) + "…";
const value = (el.value !== undefined && el.value !== null) ? String(el.value).slice(0, 500) : undefined;
const href = (el.href !== undefined && el.href !== null) ? String(el.href) : undefined;
let outerHTML = "";
try { outerHTML = String(el.outerHTML || ""); } catch {}
if (maxHtml && outerHTML.length > maxHtml) outerHTML = outerHTML.slice(0, maxHtml) + "…";
return {
index: i + 1,
tag,
...(id ? { id } : {}),
...(className ? { className } : {}),
...(text ? { text } : {}),
...(value ? { value } : {}),
...(href ? { href } : {}),
...(outerHTML ? { outerHTML } : {}),
};
});
})()`;
const evaluated = await evaluateJavaScript({
wsUrl: opts.wsUrl,
expression,
awaitPromise: true,
returnByValue: true,
});
const matches = evaluated.result?.value;
return { matches: Array.isArray(matches) ? (matches as QueryMatch[]) : [] };
}
/** Selector match metadata returned by querySelector. */
export type QueryMatch = {
index: number;
tag: string;
id?: string;
className?: string;
text?: string;
value?: string;
href?: string;
outerHTML?: string;
};

View File

@@ -1743,22 +1743,6 @@ export async function resizeChromeMcpPage(params: {
});
}
/** Accept or dismiss a Chrome MCP browser dialog. */
export async function handleChromeMcpDialog(params: {
profileName: string;
profile?: ChromeMcpProfileOptions;
userDataDir?: string;
targetId: string;
action: "accept" | "dismiss";
promptText?: string;
}): Promise<void> {
await callTool(params.profileName, chromeMcpProfileOptionsFromParams(params), "handle_dialog", {
pageId: parsePageId(params.targetId),
action: params.action,
...(params.promptText ? { promptText: params.promptText } : {}),
});
}
/** Evaluate a JavaScript function in a Chrome MCP page. */
export async function evaluateChromeMcpScript(params: {
profileName: string;
@@ -1781,22 +1765,6 @@ export async function evaluateChromeMcpScript(params: {
return extractJsonMessage(result);
}
/** Wait for text conditions in a Chrome MCP page. */
export async function waitForChromeMcpText(params: {
profileName: string;
profile?: ChromeMcpProfileOptions;
userDataDir?: string;
targetId: string;
text: string[];
timeoutMs?: number;
}): Promise<void> {
await callTool(params.profileName, chromeMcpProfileOptionsFromParams(params), "wait_for", {
pageId: parsePageId(params.targetId),
text: params.text,
...(typeof params.timeoutMs === "number" ? { timeout: params.timeoutMs } : {}),
});
}
/** Replace Chrome MCP session creation for focused tests. */
export function setChromeMcpSessionFactoryForTest(factory: ChromeMcpSessionFactory | null): void {
sessionFactory = factory;

View File

@@ -30,7 +30,9 @@ vi.mock("openclaw/plugin-sdk/ssrf-runtime-internal", () => ({
registerManagedProxyBrowserCdpBypass: registerManagedProxyBrowserCdpBypassMock,
}));
const ensurePortAvailableMock = vi.hoisted(() => vi.fn(async () => {}));
const ensurePortAvailableMock = vi.hoisted(() =>
vi.fn<(port: number, host?: string) => Promise<void>>(async () => {}),
);
vi.mock("../infra/ports.js", () => ({
ensurePortAvailable: ensurePortAvailableMock,
@@ -524,6 +526,7 @@ describe("chrome.ts internal", () => {
color: "#FF4500",
cdpPort,
cdpUrl: `http://127.0.0.1:${cdpPort}`,
cdpHost: "127.0.0.1",
cdpIsLoopback: true,
}) as unknown as ResolvedBrowserProfile;
@@ -559,8 +562,33 @@ describe("chrome.ts internal", () => {
await expect(launchOpenClawChrome(makeResolved(), profile)).rejects.toThrow(
/No supported browser found/,
);
expect(ensurePortAvailableMock).toHaveBeenCalledWith(51111, "127.0.0.1");
});
it.each([
{ cdpUrl: "http://[::1]:51111", configuredProbeHost: "::1" },
{ cdpUrl: "http://localhost:51111", configuredProbeHost: "localhost" },
])(
"checks Chrome's IPv4 bind and the configured $configuredProbeHost endpoint",
async ({ cdpUrl, configuredProbeHost }) => {
vi.spyOn(fs, "existsSync").mockReturnValue(false);
const portBusy = new Error("Port is already in use.");
portBusy.name = "PortInUseError";
ensurePortAvailableMock.mockImplementation(async (_port, host) => {
if (host === configuredProbeHost) {
throw portBusy;
}
});
const profile = { ...makeProfile(51111), cdpUrl };
await expect(launchOpenClawChrome(makeResolved(), profile)).rejects.toThrow(portBusy);
expect(ensurePortAvailableMock.mock.calls).toEqual([
[51111, "127.0.0.1"],
[51111, configuredProbeHost],
]);
},
);
it("completes successfully when Chrome reports /json/version and CDP is reachable", async () => {
// Mock executable discovery to a truthy path.
vi.spyOn(fs, "existsSync").mockImplementation((p) => {

View File

@@ -633,8 +633,19 @@ async function ensureManagedChromePortAvailable(
profile: ResolvedBrowserProfile,
userDataDir: string,
): Promise<void> {
const configuredHost = new URL(profile.cdpUrl).hostname.replace(/^\[|\]$/g, "");
const probeHosts =
configuredHost === "127.0.0.1" ? [configuredHost] : ["127.0.0.1", configuredHost];
const ensureProbeHostsAvailable = async () => {
for (const host of probeHosts) {
await ensurePortAvailable(profile.cdpPort, host);
}
};
// Chromium tries IPv4 loopback first, while OpenClaw polls the configured endpoint.
// Probe both so neither Chrome's bind nor the later readiness check can be captured.
try {
await ensurePortAvailable(profile.cdpPort);
await ensureProbeHostsAvailable();
return;
} catch (err) {
const exe = resolveBrowserExecutable(resolved, profile);
@@ -645,7 +656,7 @@ async function ensureManagedChromePortAvailable(
throw err;
}
}
await ensurePortAvailable(profile.cdpPort);
await ensureProbeHostsAvailable();
}
function chromeLaunchHints(params: {

View File

@@ -11,7 +11,6 @@ import {
resolveBrowserConfig,
resolveManagedBrowserHeadlessMode,
resolveProfile,
shouldStartLocalBrowserServer,
} from "./config.js";
import { getBrowserProfileCapabilities } from "./profile-capabilities.js";
@@ -47,7 +46,6 @@ describe("browser config", () => {
expect(resolved.enabled).toBe(true);
expect(resolved.controlPort).toBe(18791);
expect(resolved.color).toBe("#FF4500");
expect(shouldStartLocalBrowserServer(resolved)).toBe(true);
expect(resolved.cdpHost).toBe("127.0.0.1");
expect(resolved.cdpProtocol).toBe("http");
const profile = resolveProfile(resolved, resolved.defaultProfile);

View File

@@ -655,8 +655,3 @@ export function getManagedBrowserMissingDisplayError(
`Set ${OPENCLAW_BROWSER_HEADLESS_ENV}=1, remove the headed override, or launch under Xvfb.`
);
}
/** Return whether local browser control should start for a resolved config. */
export function shouldStartLocalBrowserServer(_resolved: unknown) {
return true;
}

View File

@@ -43,28 +43,3 @@ export function getBrowserCliRuntimeCapture(): CliRuntimeCapture {
export function getBrowserCliRuntime() {
return getBrowserCliRuntimeCapture().defaultRuntime;
}
/** Provides a mock module shape for defaultRuntime imports. */
export async function mockBrowserCliDefaultRuntime() {
browserCliRuntimeState.capture ??= createCliRuntimeCapture();
return { defaultRuntime: browserCliRuntimeState.capture.defaultRuntime };
}
/** Runs a command action through the same error callback shape as the real helper. */
export async function runCommandWithRuntimeMock(
_runtime: unknown,
action: () => Promise<void>,
onError: (err: unknown) => void,
) {
return await action().catch(onError);
}
/** Provides a mock module shape for core runCommandWithRuntime imports. */
export async function createBrowserCliUtilsMockModule() {
return { runCommandWithRuntime: runCommandWithRuntimeMock };
}
/** Provides a mock module shape for Browser CLI runtime imports. */
export async function createBrowserCliRuntimeMockModule() {
return await mockBrowserCliDefaultRuntime();
}

View File

@@ -0,0 +1,12 @@
# OpenClaw Cerebras Provider
Official OpenClaw provider plugin for Cerebras.
Install from OpenClaw:
```bash
openclaw plugins install @openclaw/cerebras-provider
openclaw gateway restart
```
See <https://docs.openclaw.ai/providers/cerebras> for setup and configuration.

Some files were not shown because too many files have changed in this diff Show More