mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-19 04:42:01 +08:00
Compare commits
295 Commits
codex/mult
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1bfa2787b5 | ||
|
|
e385f6663a | ||
|
|
76bdb025d6 | ||
|
|
d2e36a176d | ||
|
|
b637414871 | ||
|
|
aa39600793 | ||
|
|
61b116d597 | ||
|
|
033162f209 | ||
|
|
dfd8a2220b | ||
|
|
74ad4f592a | ||
|
|
a14b1e05e5 | ||
|
|
8b63a3d551 | ||
|
|
f4e9a6e047 | ||
|
|
2ae84f75ef | ||
|
|
dca17477dc | ||
|
|
7f1fa65399 | ||
|
|
b6a06f0e49 | ||
|
|
9501d4dec2 | ||
|
|
d9397e5b9b | ||
|
|
d9d7766a41 | ||
|
|
0771ac8563 | ||
|
|
906174bff1 | ||
|
|
c677424edb | ||
|
|
ea4ddb2eb5 | ||
|
|
f19c5f6b2f | ||
|
|
1e83f42a64 | ||
|
|
4c9b4c32ef | ||
|
|
4fc5cf4579 | ||
|
|
8f8162704d | ||
|
|
f381dca15b | ||
|
|
9ab9469d04 | ||
|
|
a2f5ac82d5 | ||
|
|
7b7e40cb0e | ||
|
|
e1c2926628 | ||
|
|
cebe5cb94a | ||
|
|
0b14724c87 | ||
|
|
e879a67bf7 | ||
|
|
53bb55e023 | ||
|
|
317919ec52 | ||
|
|
1f71e92297 | ||
|
|
d2e847e8cf | ||
|
|
720c0ab372 | ||
|
|
70e39da00f | ||
|
|
9eb6e6d326 | ||
|
|
9de9562cb7 | ||
|
|
6b25ccc4b1 | ||
|
|
111018984c | ||
|
|
7c24de5c87 | ||
|
|
3125cdacb5 | ||
|
|
8151a547c5 | ||
|
|
940feee71b | ||
|
|
46d359237e | ||
|
|
ae655345c4 | ||
|
|
a48e5091cb | ||
|
|
e9229ab77e | ||
|
|
65e77b82f5 | ||
|
|
2c7fe6a39c | ||
|
|
7ef85bfb1d | ||
|
|
361320cd9f | ||
|
|
65eb2ccd4a | ||
|
|
b6cb3d2901 | ||
|
|
a9cc2697d3 | ||
|
|
4eb0ddab00 | ||
|
|
60335533a9 | ||
|
|
681e5a0f8c | ||
|
|
d074c0d970 | ||
|
|
39c9fd8592 | ||
|
|
1d6b4ccfef | ||
|
|
b7b452d531 | ||
|
|
d37de2a39a | ||
|
|
6e6bd5633f | ||
|
|
28fb5b019a | ||
|
|
9cfd1cd287 | ||
|
|
21163a704b | ||
|
|
d16d2460b8 | ||
|
|
cc80128222 | ||
|
|
9b66e291ae | ||
|
|
7485dd6492 | ||
|
|
8413984e87 | ||
|
|
0876ed59e0 | ||
|
|
88bc08c124 | ||
|
|
acdcf37f87 | ||
|
|
d64db0fc3d | ||
|
|
25e68e9fa7 | ||
|
|
1e30af71bd | ||
|
|
1db7c41bf2 | ||
|
|
a7fada4b61 | ||
|
|
2ef2258804 | ||
|
|
8b7269d197 | ||
|
|
f324f7e281 | ||
|
|
1a3ded761e | ||
|
|
0922ee9955 | ||
|
|
3d6840bc59 | ||
|
|
8b34b8537a | ||
|
|
4e0376255f | ||
|
|
0710eebec2 | ||
|
|
a4c27323bf | ||
|
|
21728777df | ||
|
|
350aa7c3be | ||
|
|
4929cbdb04 | ||
|
|
2c499756ad | ||
|
|
9546b19a59 | ||
|
|
1eaace70e3 | ||
|
|
4560597c4b | ||
|
|
beea31a6b5 | ||
|
|
fa4f1abb29 | ||
|
|
bf9c59b6a4 | ||
|
|
1c18a7d6a3 | ||
|
|
508594a620 | ||
|
|
2ff1dfdebe | ||
|
|
81eaa88ce5 | ||
|
|
8ab36e4308 | ||
|
|
ba779d8367 | ||
|
|
ed389a87e9 | ||
|
|
73aab30989 | ||
|
|
967fb8d917 | ||
|
|
961ce49f97 | ||
|
|
092e9f69ab | ||
|
|
c8fc2fedee | ||
|
|
1fc97cf05d | ||
|
|
61210b015f | ||
|
|
ab25e340f9 | ||
|
|
af3acf0626 | ||
|
|
64db900bc4 | ||
|
|
8d808de80a | ||
|
|
49d03d24a5 | ||
|
|
890055d421 | ||
|
|
eb07ba5117 | ||
|
|
bad21faf62 | ||
|
|
3f4c04f4df | ||
|
|
54a2af32c9 | ||
|
|
49f10a140b | ||
|
|
22affb874d | ||
|
|
ad550ae3dc | ||
|
|
7bdea54a82 | ||
|
|
779e096bcc | ||
|
|
6ed2189a49 | ||
|
|
9cfc37b997 | ||
|
|
55750f7c6d | ||
|
|
c0539a4060 | ||
|
|
e40396bfc1 | ||
|
|
0e61c1112f | ||
|
|
32906798d2 | ||
|
|
fd8e3bf652 | ||
|
|
459ba6e07a | ||
|
|
967f0e0a30 | ||
|
|
e84b719c99 | ||
|
|
44500daaa0 | ||
|
|
7cca3d4618 | ||
|
|
9a571399bb | ||
|
|
4ec9d4a2b5 | ||
|
|
3f965012bf | ||
|
|
aad0559de0 | ||
|
|
712e45daf1 | ||
|
|
8f295f183d | ||
|
|
00a06eab44 | ||
|
|
af21bd7130 | ||
|
|
9592a83a29 | ||
|
|
c88d5f2611 | ||
|
|
13d474134f | ||
|
|
9caf4e1ae9 | ||
|
|
bba1d0204c | ||
|
|
45366dec2e | ||
|
|
b0375f25b3 | ||
|
|
c1a414bf28 | ||
|
|
4e476d333e | ||
|
|
085ff95fcf | ||
|
|
07c29c20d6 | ||
|
|
6214762461 | ||
|
|
1b251a6af1 | ||
|
|
90d385cb93 | ||
|
|
2ac2b021cf | ||
|
|
0d9bb2fe47 | ||
|
|
d694047cb5 | ||
|
|
dc2fdd1b99 | ||
|
|
9f5c9b2e22 | ||
|
|
68ead4dd80 | ||
|
|
aef0e4d0b0 | ||
|
|
eb17c0d635 | ||
|
|
95c256fa98 | ||
|
|
915ca8135e | ||
|
|
c558e918c2 | ||
|
|
1b19c790bf | ||
|
|
1e02cc9473 | ||
|
|
cdb2fa35e2 | ||
|
|
b4b2698ac2 | ||
|
|
8607b4042f | ||
|
|
e3bd804a17 | ||
|
|
60d6a8a89d | ||
|
|
70489061ca | ||
|
|
43e96439ee | ||
|
|
fae30318d1 | ||
|
|
58b77e787d | ||
|
|
dbebdb9563 | ||
|
|
c6aa8e7423 | ||
|
|
2a0aeb59be | ||
|
|
48c63fcb27 | ||
|
|
31e0f894c5 | ||
|
|
5b7cf461ac | ||
|
|
153c395527 | ||
|
|
12787de566 | ||
|
|
c50899275e | ||
|
|
95b6df8343 | ||
|
|
8f48f5e5ba | ||
|
|
519eb3d088 | ||
|
|
01dcaba78d | ||
|
|
5b5e6ff272 | ||
|
|
5feb88dc1e | ||
|
|
9dccdedc07 | ||
|
|
6db0fb3197 | ||
|
|
a455b508a7 | ||
|
|
d5d6576e06 | ||
|
|
6e66dfb7f0 | ||
|
|
9b082cb1bb | ||
|
|
4106794ff5 | ||
|
|
ff49b7fab1 | ||
|
|
270842a855 | ||
|
|
a59f093d3e | ||
|
|
b9b057b6d5 | ||
|
|
227757079c | ||
|
|
ce611bc803 | ||
|
|
05fe48b289 | ||
|
|
a914d572c7 | ||
|
|
ac973a8f60 | ||
|
|
0aa6b8ca0d | ||
|
|
b89fa3333b | ||
|
|
d30d409097 | ||
|
|
77a877e869 | ||
|
|
523e98a47a | ||
|
|
f8f2006c8b | ||
|
|
6f1def4d68 | ||
|
|
3f6ed50d68 | ||
|
|
2e44610ba2 | ||
|
|
0f998e6cc4 | ||
|
|
daf7c92db3 | ||
|
|
e55a637537 | ||
|
|
ff3ebb1c24 | ||
|
|
0da706dbfb | ||
|
|
0b68c5f6de | ||
|
|
de10eca7d6 | ||
|
|
cb8daec729 | ||
|
|
78d86f710e | ||
|
|
2052a3bf4e | ||
|
|
887297e04a | ||
|
|
72628f906b | ||
|
|
804e5f21d1 | ||
|
|
ba36291a8c | ||
|
|
b63acf54f9 | ||
|
|
070b0456bb | ||
|
|
af026b383d | ||
|
|
b5f71c0971 | ||
|
|
bdc46fa28d | ||
|
|
0cae5b3672 | ||
|
|
90a7f552b1 | ||
|
|
9fa1252119 | ||
|
|
2f8f93676e | ||
|
|
c58e1abf6a | ||
|
|
b4e5aa18b3 | ||
|
|
4012edcd66 | ||
|
|
acc37e220c | ||
|
|
95cf7dee72 | ||
|
|
9d7fd31dd3 | ||
|
|
9bb263c985 | ||
|
|
1dccbbfc01 | ||
|
|
79718d9e01 | ||
|
|
51d211e666 | ||
|
|
967f8adc63 | ||
|
|
573d7bf2e3 | ||
|
|
98bc05e008 | ||
|
|
1b81b0ea21 | ||
|
|
e122cd09ab | ||
|
|
1a6b84b698 | ||
|
|
4e6057e4dd | ||
|
|
665d2601e5 | ||
|
|
7b74d7332f | ||
|
|
a3ae453a1a | ||
|
|
d3ced4554d | ||
|
|
83f7203bdb | ||
|
|
bf5a108695 | ||
|
|
4552ea7ba0 | ||
|
|
2e49b6b769 | ||
|
|
4651ffad4a | ||
|
|
a22a1edc8f | ||
|
|
4a596d9bc8 | ||
|
|
496ffdf5c4 | ||
|
|
617076687e | ||
|
|
978b5225a5 | ||
|
|
9477b11d98 | ||
|
|
544b00e4e1 | ||
|
|
4a4a5968e8 | ||
|
|
b3884750b2 | ||
|
|
f9fc2efe68 | ||
|
|
2a30426133 | ||
|
|
5f42a93e4d | ||
|
|
5fcab50e49 |
@@ -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
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
77
.github/workflows/qa-live-transports-convex.yml
vendored
77
.github/workflows/qa-live-transports-convex.yml
vendored
@@ -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]
|
||||
|
||||
119
.github/workflows/windows-testbox-probe.yml
vendored
119
.github/workflows/windows-testbox-probe.yml
vendored
@@ -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."
|
||||
|
||||
3112
CHANGELOG.md
3112
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
@@ -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`
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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`).
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -16,4 +16,4 @@ OpenClaw QA lab plugin with private debugger UI and scenario runner.
|
||||
|
||||
## Surface
|
||||
|
||||
plugin
|
||||
contracts: webSearchProviders
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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 |
|
||||
| ------------------------------ | ---------------------- | ----- | ------- | -------------------- | ----------------------------------------- |
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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`).
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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`).
|
||||
|
||||
@@ -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`
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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`
|
||||
|
||||
|
||||
@@ -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`) |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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` |
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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"] {
|
||||
|
||||
12
extensions/arcee/README.md
Normal file
12
extensions/arcee/README.md
Normal 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
12
extensions/arcee/npm-shrinkwrap.json
generated
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@openclaw/plugin-sdk": "workspace:*",
|
||||
"undici": "8.3.0"
|
||||
"undici": "8.5.0"
|
||||
},
|
||||
"openclaw": {
|
||||
"extensions": [
|
||||
|
||||
@@ -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]`);
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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("");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
12
extensions/cerebras/README.md
Normal file
12
extensions/cerebras/README.md
Normal 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
Reference in New Issue
Block a user