mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-27 18:01:53 +08:00
Compare commits
269 Commits
codex/agen
...
fix/slack-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99180ec420 | ||
|
|
0e7a6a5272 | ||
|
|
9f07d8906c | ||
|
|
552ec2b49d | ||
|
|
4d0f19a968 | ||
|
|
072d3ed7b5 | ||
|
|
1bccd29304 | ||
|
|
498567190d | ||
|
|
5880e0afc4 | ||
|
|
65fec9d787 | ||
|
|
4d9cd7d227 | ||
|
|
12ea61a08d | ||
|
|
4932366b92 | ||
|
|
4f3d81b918 | ||
|
|
e09b9dfc1b | ||
|
|
ff35f3bb2c | ||
|
|
ff18374293 | ||
|
|
fa78cfbfb7 | ||
|
|
8252fc009f | ||
|
|
c691872b9e | ||
|
|
e0932e0bc4 | ||
|
|
808c227edb | ||
|
|
deb0ffdcdf | ||
|
|
a846b879ec | ||
|
|
43dd34262e | ||
|
|
6883c6c070 | ||
|
|
91726e9624 | ||
|
|
289865b392 | ||
|
|
6db4624f43 | ||
|
|
b7a9d3005c | ||
|
|
bdd365a348 | ||
|
|
a82902c725 | ||
|
|
aca905cce5 | ||
|
|
ab966c214b | ||
|
|
41c00a65d6 | ||
|
|
eba1ca683f | ||
|
|
b3eee03740 | ||
|
|
6109420e5c | ||
|
|
44e522cf6b | ||
|
|
ab8cd3dac9 | ||
|
|
816c2cf1f8 | ||
|
|
9cc10a8382 | ||
|
|
c6757d7a75 | ||
|
|
a70e7ce24b | ||
|
|
6b98d179b6 | ||
|
|
cb4e9e4118 | ||
|
|
0023cc816a | ||
|
|
6d62dae215 | ||
|
|
8d61631b40 | ||
|
|
68bed5e902 | ||
|
|
7b549a26e8 | ||
|
|
57f62a5fd9 | ||
|
|
ba70d365ac | ||
|
|
ce88d65779 | ||
|
|
689baa5c1e | ||
|
|
4c4396c4c2 | ||
|
|
c1336b6b41 | ||
|
|
d4a01e48bc | ||
|
|
a0e9ca1e95 | ||
|
|
1b6557dfa2 | ||
|
|
2968004680 | ||
|
|
9636bea901 | ||
|
|
1089253ca9 | ||
|
|
e5123e44b0 | ||
|
|
1cd6f81a46 | ||
|
|
80c754ddf4 | ||
|
|
512f0f1bf7 | ||
|
|
338e119533 | ||
|
|
e4f63577d0 | ||
|
|
94d93d4c85 | ||
|
|
7718e25b2a | ||
|
|
8079aa62a2 | ||
|
|
6f162f321a | ||
|
|
527f8f0cbb | ||
|
|
c05d0d5bbf | ||
|
|
535af4452b | ||
|
|
ec737ee74d | ||
|
|
9a735bea03 | ||
|
|
81e53202f2 | ||
|
|
e9f9a68d68 | ||
|
|
db255b1154 | ||
|
|
4fc504d321 | ||
|
|
751a6c23f0 | ||
|
|
899f65097b | ||
|
|
a6a4652c70 | ||
|
|
3b292ba9d4 | ||
|
|
0fdfc9f65f | ||
|
|
448b7c75b6 | ||
|
|
6830aa39ea | ||
|
|
a0b397748f | ||
|
|
dd0e4f6e61 | ||
|
|
0da26499da | ||
|
|
1f941a026e | ||
|
|
941e8f1ef2 | ||
|
|
95b97e5b0b | ||
|
|
13ecca5408 | ||
|
|
c68484acc4 | ||
|
|
d2da8c79d9 | ||
|
|
1aa7cafc35 | ||
|
|
66e2fcc6f8 | ||
|
|
b3ac552c82 | ||
|
|
5715b55000 | ||
|
|
0247eab773 | ||
|
|
646e54ae35 | ||
|
|
d3620da3e0 | ||
|
|
7b5ee739eb | ||
|
|
bfc33ac114 | ||
|
|
cc124d2921 | ||
|
|
7cce191b05 | ||
|
|
7fefc5ff58 | ||
|
|
19707cce1d | ||
|
|
a3b4e8102f | ||
|
|
4bd68aef65 | ||
|
|
8bc069f76f | ||
|
|
1adb119ba0 | ||
|
|
57c07d7f3b | ||
|
|
3c8ff0d1c3 | ||
|
|
3a03d1e70b | ||
|
|
9047b1cfa1 | ||
|
|
ba004b3547 | ||
|
|
3092b4fd0d | ||
|
|
116758e69a | ||
|
|
cd3793185b | ||
|
|
5fccf06b5f | ||
|
|
bbf494955d | ||
|
|
f12ade0082 | ||
|
|
56baf9d079 | ||
|
|
dc12b998da | ||
|
|
cf512f639b | ||
|
|
29670c13f6 | ||
|
|
bead84f0ee | ||
|
|
497d53d821 | ||
|
|
446d98d601 | ||
|
|
82a6a57330 | ||
|
|
01ce03c5b1 | ||
|
|
5881dc8ac3 | ||
|
|
31a0f97dd9 | ||
|
|
ace22feb3f | ||
|
|
ecd29fe572 | ||
|
|
6039da3ed6 | ||
|
|
8b4be2fdd4 | ||
|
|
210ea659f7 | ||
|
|
c0a61f5351 | ||
|
|
7f2c04ce11 | ||
|
|
f9e0dce731 | ||
|
|
71422a9a5a | ||
|
|
2e6e17f7c5 | ||
|
|
1ba1fecaa6 | ||
|
|
4ecb45bf77 | ||
|
|
0757cad597 | ||
|
|
21b21583cc | ||
|
|
c8c4490b17 | ||
|
|
d693b70bfc | ||
|
|
2b8c089b76 | ||
|
|
1d1c2f4f72 | ||
|
|
3ce398712a | ||
|
|
3c2a3d9d2b | ||
|
|
33d7a2a3f7 | ||
|
|
94ae918d8f | ||
|
|
af906225fa | ||
|
|
08b7fddf80 | ||
|
|
d7dff3cbf4 | ||
|
|
42d0a1267e | ||
|
|
99f56cd548 | ||
|
|
e6a2f61e94 | ||
|
|
c030b305a4 | ||
|
|
770b19f496 | ||
|
|
793b604b23 | ||
|
|
31e941c3fc | ||
|
|
56d95b18f4 | ||
|
|
e7f2b125f6 | ||
|
|
643410c1f3 | ||
|
|
8d4e40d293 | ||
|
|
068ae4eb4b | ||
|
|
dad7168c2f | ||
|
|
31a65e0647 | ||
|
|
1a04b8eb98 | ||
|
|
a21144d8a6 | ||
|
|
8a5cb85c31 | ||
|
|
61d4ff782e | ||
|
|
3ab7a72764 | ||
|
|
b4bdea0d02 | ||
|
|
113d6f3c64 | ||
|
|
0a14444924 | ||
|
|
0a042f68df | ||
|
|
3ab8d6aa60 | ||
|
|
f2af052cee | ||
|
|
c6f5725906 | ||
|
|
f47fb91d29 | ||
|
|
15bfadf2bd | ||
|
|
1d172637d6 | ||
|
|
dad5ce64d4 | ||
|
|
170bf72e64 | ||
|
|
ad5a26cf69 | ||
|
|
259877dccf | ||
|
|
d8ee630b20 | ||
|
|
2c714ac2e0 | ||
|
|
0cdb050bac | ||
|
|
fab0048d7b | ||
|
|
4a7659920c | ||
|
|
070996e5c3 | ||
|
|
af8cd23f17 | ||
|
|
2fe50f69db | ||
|
|
fc198d862a | ||
|
|
2ddedad1d0 | ||
|
|
33d0019eaf | ||
|
|
875e26e4bb | ||
|
|
d23977edbc | ||
|
|
10e03f797e | ||
|
|
f0f5da0e39 | ||
|
|
9777c68563 | ||
|
|
6d0306b920 | ||
|
|
d716900929 | ||
|
|
e2d282f16e | ||
|
|
9514faca27 | ||
|
|
3848b9619f | ||
|
|
365279b86f | ||
|
|
1adc076148 | ||
|
|
a49816ffbb | ||
|
|
fa6a9509bc | ||
|
|
9d82906f79 | ||
|
|
3168987b28 | ||
|
|
7e2b2d2987 | ||
|
|
8670d28126 | ||
|
|
c561319708 | ||
|
|
387ef7ebc4 | ||
|
|
00b6f49b24 | ||
|
|
c81fec0370 | ||
|
|
eac1d3349c | ||
|
|
aa56abc94a | ||
|
|
b6bc3ed0db | ||
|
|
9ad959a870 | ||
|
|
f2bc159b79 | ||
|
|
4c841ac575 | ||
|
|
8ecbf83c67 | ||
|
|
3fbdbb5440 | ||
|
|
da50a450d2 | ||
|
|
ff332d3819 | ||
|
|
c2d2f7fef9 | ||
|
|
6df67285df | ||
|
|
5eec2158ea | ||
|
|
d01c290601 | ||
|
|
d3cfef3bd8 | ||
|
|
f163d778c0 | ||
|
|
b302b491da | ||
|
|
4d4769c0d6 | ||
|
|
f57a30289d | ||
|
|
bcbd521c1b | ||
|
|
94ab33036e | ||
|
|
47d3d1b1f1 | ||
|
|
0347ae48ea | ||
|
|
4ae0a5d958 | ||
|
|
c5f10b5f7c | ||
|
|
f29dbd3ebd | ||
|
|
3217165be7 | ||
|
|
dbe2802cdc | ||
|
|
5f25651fd9 | ||
|
|
d7c69da6a6 | ||
|
|
e77994ed5a | ||
|
|
db3307b02a | ||
|
|
6b1755aa2b | ||
|
|
fa2379dbc8 | ||
|
|
ce6d97d580 | ||
|
|
d1c2934d0d | ||
|
|
605aede38c | ||
|
|
6163b1977b | ||
|
|
eabc12b7d6 | ||
|
|
b58e6e0734 | ||
|
|
d83cd282c6 |
@@ -13,12 +13,13 @@ registration edge limit.
|
||||
|
||||
- The scarce resource is Blacksmith runner registrations, not Blacksmith vCPU
|
||||
capacity.
|
||||
- GitHub runner registrations are capped at 1,500 per 5 minutes per repository,
|
||||
organization, or enterprise. The `openclaw` organization shares one bucket.
|
||||
- GitHub runner registrations for `openclaw` are currently capped at 3,000 per
|
||||
5 minutes per repository, organization, or enterprise. The `openclaw`
|
||||
organization shares one bucket.
|
||||
- Core REST quota does not draw down this bucket. Check
|
||||
`actions_runner_registration` separately; core quota can be healthy while
|
||||
runner registration is throttled.
|
||||
- Use 1,000 registrations per 5 minutes as the operating target. Leave the last
|
||||
- Use 2,000 registrations per 5 minutes as the operating target. Leave the last
|
||||
third for other repos, retries, and burst overlap.
|
||||
- Jobs that route, notify, summarize, choose shards, or run short CodeQL quality
|
||||
scans should stay on GitHub-hosted runners unless measured evidence says
|
||||
@@ -87,7 +88,7 @@ admission. The debounce only suppresses pushes that arrive while
|
||||
registrations are spent even if a later push cancels the run. If timing is
|
||||
uncertain, count every sequential push in the window.
|
||||
|
||||
Reject a change unless the org-level worst case stays below 1,000 registrations
|
||||
Reject a change unless the org-level worst case stays below 2,000 registrations
|
||||
per 5 minutes with headroom for ClawSweeper, ClawHub, Clownfish, OpenClaw RTT,
|
||||
and Clawbench.
|
||||
|
||||
@@ -127,8 +128,8 @@ These are intentionally guarded by `test/scripts/ci-workflow-guards.test.ts`:
|
||||
- `runner-admission` on `ubuntu-24.04` with
|
||||
`OPENCLAW_MAIN_CI_DEBOUNCE_SECONDS=90`.
|
||||
- `preflight` and `security-fast` needing `runner-admission`.
|
||||
- CI matrix caps: fast/check lanes at 8, compact Node PR plan at current caps,
|
||||
Windows and Android at 2.
|
||||
- CI matrix caps: fast/check lanes at 12, Node test shards at 24, Windows and
|
||||
Android at 2.
|
||||
- `build-artifacts` on `blacksmith-16vcpu-ubuntu-2404`.
|
||||
- lower-weight Node/check shards on `blacksmith-4vcpu-ubuntu-2404`.
|
||||
- heavy retained Linux/Android shards on `blacksmith-8vcpu-ubuntu-2404`.
|
||||
|
||||
@@ -5,7 +5,7 @@ description: "Run or recover OpenClaw macOS release signing, notarization, appca
|
||||
|
||||
# OpenClaw Mac Release
|
||||
|
||||
Use with `$release-openclaw-maintainer`, `$release-openclaw-ci`, `$one-password`, and `$release-private` if it exists when stable macOS assets, private mac preflight, notarization, appcast promotion, or mac release recovery is involved.
|
||||
Use with `$release-openclaw-maintainer`, `$release-openclaw-ci`, `$one-password`, and `$release-private` if it exists when stable macOS assets, release-ops mac preflight, notarization, appcast promotion, or mac release recovery is involved.
|
||||
|
||||
## Credentials
|
||||
|
||||
@@ -23,7 +23,7 @@ Use with `$release-openclaw-maintainer`, `$release-openclaw-ci`, `$one-password`
|
||||
|
||||
## GitHub Secrets
|
||||
|
||||
Target private repo environment: `openclaw/releases-private`, env `mac-release`.
|
||||
Target release-ops repo environment: `openclaw/releases`, env `mac-release`.
|
||||
|
||||
Set only after local notary auth validation:
|
||||
|
||||
@@ -35,12 +35,24 @@ Do not update these from mixed sources. All three ASC fields must come from the
|
||||
|
||||
## Workflow Shape
|
||||
|
||||
- `openclaw/openclaw` is the public product repo. Its GitHub Releases page is
|
||||
where macOS assets are ultimately attached.
|
||||
- `openclaw/openclaw` `macos-release.yml` is public handoff validation only.
|
||||
It never signs, notarizes, or uploads macOS assets, regardless of
|
||||
`preflight_only`.
|
||||
- `openclaw/releases` is the restricted release-ops repo. Its macOS workflows
|
||||
sign, notarize, validate, and promote assets onto the
|
||||
`openclaw/openclaw` GitHub release.
|
||||
- Public release branch may carry mac-only packaging fixes after the stable tag/npm are already live.
|
||||
- Use `source_ref=release/YYYY.M.PATCH` for private mac preflight/validation when building that branch variation.
|
||||
- Use `source_ref=release/YYYY.M.PATCH` for release-ops mac preflight/validation when building that branch variation.
|
||||
- Keep `tag=vYYYY.M.PATCH` pointing at the original stable release commit.
|
||||
- Real mac publish must reuse:
|
||||
- a successful private mac preflight run for the same tag/source SHA
|
||||
- a successful private mac validation run for the same tag/source SHA
|
||||
- a successful release-ops mac preflight run for the same tag/source SHA
|
||||
- a successful release-ops mac validation run for the same tag/source SHA
|
||||
- Release-ops preflight and real publish enter the protected `mac-release`
|
||||
environment in the `build_sign_and_package` job. Operators may be able to
|
||||
trigger the workflow while Vincent or another environment reviewer approves
|
||||
the paused deployment before signing/notarization/promotion proceeds.
|
||||
- If preflight source SHA differs from tag SHA, validation must also use the same `source_ref`; promotion rejects mismatched proof.
|
||||
|
||||
## Notarization
|
||||
@@ -52,10 +64,25 @@ Do not update these from mixed sources. All three ASC fields must come from the
|
||||
|
||||
## Dispatch
|
||||
|
||||
Private preflight:
|
||||
Public handoff validation:
|
||||
|
||||
```bash
|
||||
gh workflow run openclaw-macos-publish.yml --repo openclaw/releases-private --ref main \
|
||||
gh workflow run macos-release.yml --repo openclaw/openclaw \
|
||||
--ref release/YYYY.M.PATCH \
|
||||
-f tag=vYYYY.M.PATCH \
|
||||
-f preflight_only=true \
|
||||
-f public_release_branch=release/YYYY.M.PATCH
|
||||
```
|
||||
|
||||
- Use the public release branch as the workflow ref so the Actions list displays
|
||||
`release/YYYY.M.PATCH`, matching prior stable macOS handoff runs.
|
||||
- Do not use `--ref main` or `--ref vYYYY.M.PATCH` for this public handoff
|
||||
validation. The workflow checks out the tag from the `tag` input internally.
|
||||
|
||||
Release-ops preflight:
|
||||
|
||||
```bash
|
||||
gh workflow run openclaw-macos-publish.yml --repo openclaw/releases --ref main \
|
||||
-f tag=vYYYY.M.PATCH \
|
||||
-f source_ref=release/YYYY.M.PATCH \
|
||||
-f preflight_only=true \
|
||||
@@ -64,18 +91,24 @@ gh workflow run openclaw-macos-publish.yml --repo openclaw/releases-private --re
|
||||
-f public_release_branch=release/YYYY.M.PATCH
|
||||
```
|
||||
|
||||
Private validation for a branch-variation preflight:
|
||||
Wait for the run to reach the `mac-release` environment approval if GitHub
|
||||
pauses it, then get approval from Vincent or another configured environment
|
||||
reviewer. Record the successful preflight run id.
|
||||
|
||||
Release-ops validation for a branch-variation preflight:
|
||||
|
||||
```bash
|
||||
gh workflow run openclaw-macos-validate.yml --repo openclaw/releases-private --ref main \
|
||||
gh workflow run openclaw-macos-validate.yml --repo openclaw/releases --ref main \
|
||||
-f tag=vYYYY.M.PATCH \
|
||||
-f source_ref=release/YYYY.M.PATCH
|
||||
```
|
||||
|
||||
Record the successful validation run id.
|
||||
|
||||
Real publish:
|
||||
|
||||
```bash
|
||||
gh workflow run openclaw-macos-publish.yml --repo openclaw/releases-private --ref main \
|
||||
gh workflow run openclaw-macos-publish.yml --repo openclaw/releases --ref main \
|
||||
-f tag=vYYYY.M.PATCH \
|
||||
-f preflight_only=false \
|
||||
-f smoke_test_only=false \
|
||||
@@ -85,6 +118,14 @@ gh workflow run openclaw-macos-publish.yml --repo openclaw/releases-private --re
|
||||
-f public_release_branch=release/YYYY.M.PATCH
|
||||
```
|
||||
|
||||
Wait for the `mac-release` environment approval again if GitHub pauses the real
|
||||
publish run before it promotes assets.
|
||||
|
||||
- Release-ops `openclaw/releases` publish/validate workflows run from their own
|
||||
trusted `main` workflow ref. Real publish has a guard that rejects any other
|
||||
workflow ref. That displayed `main` ref is expected; the public OpenClaw
|
||||
source is selected by `tag` and optional `source_ref`.
|
||||
|
||||
## Verify
|
||||
|
||||
- `gh release view vYYYY.M.PATCH --repo openclaw/openclaw` shows zip, dmg, dSYM zip, not draft, not prerelease.
|
||||
|
||||
@@ -203,8 +203,9 @@ Stable publication is not complete until `main` carries the actual shipped relea
|
||||
validation-only release machinery. If mac packaging needs release-branch-only
|
||||
fixes after the stable npm package or GitHub tag is already published, do not
|
||||
create a `vYYYY.M.PATCH-N` correction tag just to change the workflow source.
|
||||
Dispatch the private mac workflows for the original `tag=vYYYY.M.PATCH` with
|
||||
`source_ref=release/YYYY.M.PATCH` and `public_release_branch=release/YYYY.M.PATCH`;
|
||||
Dispatch the release-ops mac workflows for the original `tag=vYYYY.M.PATCH`
|
||||
with `source_ref=release/YYYY.M.PATCH` and
|
||||
`public_release_branch=release/YYYY.M.PATCH`;
|
||||
provenance checks must prove the source SHA descends from the tag and
|
||||
validation/preflight use the same source. Reserve `vYYYY.M.PATCH-N` correction
|
||||
tags for emergency hotfixes that must publish a new npm package/release
|
||||
@@ -579,8 +580,8 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
|
||||
- Actual npm install/update phases are capped at 5 minutes. If `npm install -g`, installer package install, or `openclaw update` takes longer than 300s in release e2e, stop treating the run as healthy progress and debug the installer/updater or harness.
|
||||
- Serialize host build/package mutations ahead of VM lanes. Finish `pnpm build`, `pnpm ui:build`, `pnpm release:check`, install smoke, and any Docker/package-prep lanes before starting Parallels `npm pack` lanes; otherwise `dist` can disappear during VM pack prep and produce false failures.
|
||||
- Include mac release readiness in preflight by running the public validation
|
||||
workflow in `openclaw/openclaw` and the real mac preflight in
|
||||
`openclaw/releases-private` for every release.
|
||||
workflow in `openclaw/openclaw` and the release-ops mac preflight in
|
||||
`openclaw/releases` for every release.
|
||||
- Treat the `appcast.xml` update on `main` as part of mac release readiness, not an optional follow-up.
|
||||
- The workflows remain tag-based. The agent is responsible for making sure
|
||||
preflight runs complete successfully before any publish run starts.
|
||||
@@ -608,16 +609,16 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
|
||||
## Use the right auth flow
|
||||
|
||||
- OpenClaw publish uses GitHub trusted publishing.
|
||||
- Stable npm promotion from `beta` to `latest` uses the private
|
||||
`openclaw/releases-private/.github/workflows/openclaw-npm-dist-tags.yml`
|
||||
workflow because `npm dist-tag` management needs `NPM_TOKEN`, while the
|
||||
public npm release workflow stays OIDC-only.
|
||||
- Prefer fixing the private workflow token path over any local 1Password
|
||||
fallback. The desired setup is a granular npm token stored as the private
|
||||
- Stable npm promotion from `beta` to `latest` uses the restricted release-ops
|
||||
`openclaw/releases/.github/workflows/openclaw-npm-dist-tags.yml` workflow
|
||||
because `npm dist-tag` management needs `NPM_TOKEN`, while the public npm
|
||||
release workflow stays OIDC-only.
|
||||
- Prefer fixing the release-ops workflow token path over any local 1Password
|
||||
fallback. The desired setup is a granular npm token stored as the release-ops
|
||||
repo's `NPM_TOKEN` secret, scoped to the `openclaw` package with read/write
|
||||
and 2FA bypass for automation.
|
||||
- If the private dist-tag workflow cannot promote because `NPM_TOKEN` is absent
|
||||
or stale, use the local tmux + 1Password fallback:
|
||||
- If the release-ops dist-tag workflow cannot promote because `NPM_TOKEN` is
|
||||
absent or stale, use the local tmux + 1Password fallback:
|
||||
- Start or reuse a tmux session so interactive `npm login` and OTP prompts
|
||||
are observable and recoverable.
|
||||
- Hard rule: never run `op` directly in the main agent shell during release
|
||||
@@ -635,21 +636,21 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
|
||||
- Verify with a cache-bypassed registry read, for example:
|
||||
`npm view openclaw dist-tags --json --prefer-online --cache /tmp/openclaw-npm-cache-verify-$$`
|
||||
and `npm view openclaw@latest version dist.tarball --json --prefer-online`.
|
||||
- Direct stable publishes can also use that private dist-tag workflow to point
|
||||
`beta` at the already-published `latest` version when the operator wants both
|
||||
tags aligned immediately.
|
||||
- Direct stable publishes can also use that release-ops dist-tag workflow to
|
||||
point `beta` at the already-published `latest` version when the operator wants
|
||||
both tags aligned immediately.
|
||||
- The publish run must be started manually with `workflow_dispatch`.
|
||||
- The npm workflow and the private mac publish workflow accept
|
||||
- The npm workflow and the release-ops mac publish workflow accept
|
||||
`preflight_only=true` to run validation/build/package steps without uploading
|
||||
public release assets.
|
||||
- Real npm publish requires a prior successful npm preflight run id and the
|
||||
successful Full Release Validation run id for the same tag/SHA so the publish
|
||||
job promotes the prepared tarball instead of rebuilding it and attaches the
|
||||
correct release evidence.
|
||||
- Real private mac publish requires a prior successful private mac preflight
|
||||
run id so the publish job promotes the prepared artifacts instead of
|
||||
- Real release-ops mac publish requires a prior successful release-ops mac
|
||||
preflight run id so the publish job promotes the prepared artifacts instead of
|
||||
rebuilding or renotarizing them again.
|
||||
- The private mac workflow also accepts `smoke_test_only=true` for branch-safe
|
||||
- The release-ops mac workflow also accepts `smoke_test_only=true` for branch-safe
|
||||
workflow smoke tests that use ad-hoc signing, skip notarization, skip shared
|
||||
appcast generation, and do not prove release readiness.
|
||||
- `preflight_only=true` on the npm workflow is also the right way to validate an
|
||||
@@ -670,27 +671,27 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
|
||||
use only `main` or `release/YYYY.M.PATCH`.
|
||||
- `.github/workflows/macos-release.yml` in `openclaw/openclaw` is now a
|
||||
public validation-only handoff. It validates the tag/release state and points
|
||||
operators to the private repo. It still rebuilds the JS outputs needed for
|
||||
operators to the release-ops repo. It still rebuilds the JS outputs needed for
|
||||
release validation, but it does not sign, notarize, or publish macOS
|
||||
artifacts.
|
||||
- `openclaw/releases-private/.github/workflows/openclaw-macos-validate.yml`
|
||||
is the required private mac validation lane for `swift test`; keep it green
|
||||
- `openclaw/releases/.github/workflows/openclaw-macos-validate.yml` is the
|
||||
required release-ops mac validation lane for `swift test`; keep it green
|
||||
before any real stable mac publish run starts.
|
||||
- Real mac preflight and real mac publish both use
|
||||
`openclaw/releases-private/.github/workflows/openclaw-macos-publish.yml`.
|
||||
- The private mac validation lane runs on GitHub's standard macOS runner.
|
||||
- The private mac preflight path runs on GitHub's xlarge macOS runner and uses
|
||||
`openclaw/releases/.github/workflows/openclaw-macos-publish.yml`.
|
||||
- The release-ops mac validation lane runs on GitHub's standard macOS runner.
|
||||
- The release-ops mac preflight path runs on GitHub's xlarge macOS runner and uses
|
||||
a SwiftPM cache because the build/sign/notarize/package path is CPU-heavy.
|
||||
- Private mac preflight uploads notarized build artifacts as workflow artifacts
|
||||
instead of uploading public GitHub release assets.
|
||||
- Private smoke-test runs upload ad-hoc, non-notarized build artifacts as
|
||||
- Release-ops mac preflight uploads notarized build artifacts as workflow
|
||||
artifacts instead of uploading public GitHub release assets.
|
||||
- Release-ops smoke-test runs upload ad-hoc, non-notarized build artifacts as
|
||||
workflow artifacts and intentionally skip stable `appcast.xml` generation.
|
||||
- For stable releases, npm preflight, Full Release Validation, public mac
|
||||
validation, private mac validation, and private mac preflight must all pass
|
||||
before any real publish run starts. For beta releases, npm preflight and Full
|
||||
Release Validation must pass before npm publish unless the operator explicitly
|
||||
waives the full gate; mac beta validation is still only required when
|
||||
requested.
|
||||
validation, release-ops mac validation, and release-ops mac preflight must all
|
||||
pass before any real publish run starts. For beta releases, npm preflight and
|
||||
Full Release Validation must pass before npm publish unless the operator
|
||||
explicitly waives the full gate; mac beta validation is still only required
|
||||
when requested.
|
||||
- Real publish runs may be dispatched from `main` or from a
|
||||
`release/YYYY.M.PATCH` branch. For release-branch runs, the tag must be contained
|
||||
in that release branch, and the real publish must reuse a successful preflight
|
||||
@@ -699,21 +700,21 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
|
||||
rather than workflow-level SHA pinning.
|
||||
- The `npm-release` environment must be approved by `@openclaw/openclaw-release-managers` before publish continues.
|
||||
- Mac publish uses
|
||||
`openclaw/releases-private/.github/workflows/openclaw-macos-publish.yml` for
|
||||
private mac preflight artifact preparation and real publish artifact
|
||||
`openclaw/releases/.github/workflows/openclaw-macos-publish.yml` for
|
||||
release-ops mac preflight artifact preparation and real publish artifact
|
||||
promotion.
|
||||
- Real private mac publish uploads the packaged `.zip`, `.dmg`, and
|
||||
- Real release-ops mac publish uploads the packaged `.zip`, `.dmg`, and
|
||||
`.dSYM.zip` assets to the existing GitHub release in `openclaw/openclaw`
|
||||
automatically when `OPENCLAW_PUBLIC_REPO_RELEASE_TOKEN` is present in the
|
||||
private repo `mac-release` environment.
|
||||
release-ops repo `mac-release` environment.
|
||||
- For stable releases, the agent must also download the signed
|
||||
`macos-appcast-<tag>` artifact from the successful private mac workflow and
|
||||
then update `appcast.xml` on `main`.
|
||||
`macos-appcast-<tag>` artifact from the successful release-ops mac workflow
|
||||
and then update `appcast.xml` on `main`.
|
||||
- For beta mac releases, do not update the shared production `appcast.xml`
|
||||
unless a separate beta Sparkle feed exists.
|
||||
- The private repo targets a dedicated `mac-release` environment. If the GitHub
|
||||
plan does not yet support required reviewers there, do not assume the
|
||||
environment alone is the approval boundary; rely on private repo access and
|
||||
- The release-ops repo targets a dedicated `mac-release` environment. If the
|
||||
GitHub plan does not yet support required reviewers there, do not assume the
|
||||
environment alone is the approval boundary; rely on restricted repo access and
|
||||
CODEOWNERS until those settings can be enabled.
|
||||
- Do not use `NPM_TOKEN` or the plugin OTP flow for the OpenClaw package
|
||||
publish path; package publishing uses trusted publishing.
|
||||
@@ -800,12 +801,12 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
|
||||
18. For stable releases, start `.github/workflows/macos-release.yml` in
|
||||
`openclaw/openclaw` and wait for the public validation-only run to pass.
|
||||
19. For stable releases, start
|
||||
`openclaw/releases-private/.github/workflows/openclaw-macos-validate.yml`
|
||||
with the same tag and wait for the private mac validation lane to pass.
|
||||
`openclaw/releases/.github/workflows/openclaw-macos-validate.yml` with the
|
||||
same tag and wait for the release-ops mac validation lane to pass.
|
||||
20. For stable releases, start
|
||||
`openclaw/releases-private/.github/workflows/openclaw-macos-publish.yml`
|
||||
with `preflight_only=true` and wait for it to pass. Save that run id because
|
||||
the real publish requires it to reuse the notarized mac artifacts.
|
||||
`openclaw/releases/.github/workflows/openclaw-macos-publish.yml` with
|
||||
`preflight_only=true` and wait for it to pass. Save that run id because the
|
||||
real publish requires it to reuse the notarized mac artifacts.
|
||||
21. If any preflight or validation run fails, fix the issue on a new commit,
|
||||
delete the tag and any accidental draft/incomplete GitHub release, recreate
|
||||
the tag from the fixed commit, and rerun all relevant preflights from
|
||||
@@ -861,22 +862,23 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
|
||||
promotion roster when the matching beta already carried the full confidence
|
||||
pass: published npm postpublish verify, Docker install/update smoke,
|
||||
macOS-only Parallels install/update smoke, and required QA signal.
|
||||
Then start the private
|
||||
`openclaw/releases-private/.github/workflows/openclaw-npm-dist-tags.yml`
|
||||
workflow to promote that stable version from `beta` to `latest`, then
|
||||
verify `latest` now points at that version.
|
||||
Then start the restricted release-ops
|
||||
`openclaw/releases/.github/workflows/openclaw-npm-dist-tags.yml` workflow
|
||||
to promote that stable version from `beta` to `latest`, then verify
|
||||
`latest` now points at that version.
|
||||
29. If the stable release was published directly to `latest` and `beta` should
|
||||
follow it, start that same private dist-tag workflow to point `beta` at the
|
||||
stable version, then verify both `latest` and `beta` point at that version.
|
||||
follow it, start that same release-ops dist-tag workflow to point `beta` at
|
||||
the stable version, then verify both `latest` and `beta` point at that
|
||||
version.
|
||||
30. For stable releases, start
|
||||
`openclaw/releases-private/.github/workflows/openclaw-macos-publish.yml`
|
||||
for the real publish with the successful private mac `preflight_run_id` and
|
||||
wait for success.
|
||||
31. Verify the successful real private mac run uploaded the `.zip`, `.dmg`,
|
||||
`openclaw/releases/.github/workflows/openclaw-macos-publish.yml` for the
|
||||
real publish with the successful release-ops mac `preflight_run_id` and wait
|
||||
for success.
|
||||
31. Verify the successful real release-ops mac run uploaded the `.zip`, `.dmg`,
|
||||
and `.dSYM.zip` artifacts to the existing GitHub release in
|
||||
`openclaw/openclaw`.
|
||||
32. For stable releases, download `macos-appcast-<tag>` from the successful
|
||||
private mac run, update `appcast.xml` on `main`, verify the feed, then
|
||||
release-ops mac run, update `appcast.xml` on `main`, verify the feed, then
|
||||
complete the **Close stable releases on main** gate.
|
||||
33. For beta releases, publish the mac assets only when intentionally requested;
|
||||
expect no shared production
|
||||
|
||||
48
.github/workflows/ci.yml
vendored
48
.github/workflows/ci.yml
vendored
@@ -251,7 +251,6 @@ jobs:
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
const createMatrix = (include) => ({ include });
|
||||
const outputPath = process.env.GITHUB_OUTPUT;
|
||||
const isCanonicalRepository = process.env.OPENCLAW_CI_REPOSITORY === "openclaw/openclaw";
|
||||
@@ -285,6 +284,7 @@ jobs:
|
||||
if (runNodeFull) {
|
||||
checksFastCoreTasks.push(
|
||||
{ check_name: "checks-fast-bundled-protocol", runtime: "node", task: "bundled-protocol" },
|
||||
{ check_name: "QA Smoke CI", runtime: "node", task: "qa-smoke-ci" },
|
||||
{ check_name: "checks-fast-bun-launcher", runtime: "bun", task: "bun-launcher" },
|
||||
);
|
||||
} else {
|
||||
@@ -858,7 +858,7 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 8
|
||||
max-parallel: 12
|
||||
matrix: ${{ fromJson(needs.preflight.outputs.checks_fast_core_matrix) }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -922,6 +922,26 @@ jobs:
|
||||
pnpm test:bundled
|
||||
pnpm protocol:check
|
||||
;;
|
||||
qa-smoke-ci)
|
||||
output_dir=".artifacts/qa-e2e/smoke-ci-profile"
|
||||
export OPENCLAW_BUILD_PRIVATE_QA=1
|
||||
export OPENCLAW_ENABLE_PRIVATE_QA_CLI=1
|
||||
export OPENCLAW_DISABLE_BUNDLED_PLUGINS=0
|
||||
export OPENCLAW_QA_REDACT_PUBLIC_METADATA=1
|
||||
export OPENCLAW_QA_TRANSPORT_READY_TIMEOUT_MS=180000
|
||||
NODE_OPTIONS=--max-old-space-size=8192 node scripts/build-all.mjs qaRuntime
|
||||
qa_exit_code=0
|
||||
pnpm openclaw qa run \
|
||||
--repo-root . \
|
||||
--qa-profile smoke-ci \
|
||||
--concurrency 8 \
|
||||
--output-dir "$output_dir" || qa_exit_code=$?
|
||||
echo "QA smoke profile evidence: \`${output_dir}\`" >> "$GITHUB_STEP_SUMMARY"
|
||||
if [ "$qa_exit_code" -ne 0 ]; then
|
||||
echo "::error title=QA smoke profile failed::smoke-ci exited ${qa_exit_code}; evidence upload will still run"
|
||||
exit "$qa_exit_code"
|
||||
fi
|
||||
;;
|
||||
contracts-plugins-ci-routing)
|
||||
pnpm test:contracts:plugins
|
||||
pnpm test src/commands/status.scan-result.test.ts src/scripts/ci-changed-scope.test.ts test/scripts/changed-lanes.test.ts test/scripts/ci-workflow-guards.test.ts test/scripts/run-vitest.test.ts test/scripts/test-projects.test.ts
|
||||
@@ -938,6 +958,15 @@ jobs:
|
||||
;;
|
||||
esac
|
||||
|
||||
- name: Upload QA smoke profile evidence
|
||||
if: always() && matrix.task == 'qa-smoke-ci'
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: qa-smoke-profile-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
path: .artifacts/qa-e2e/smoke-ci-profile/
|
||||
if-no-files-found: warn
|
||||
retention-days: 7
|
||||
|
||||
checks-fast-plugin-contracts-shard:
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -948,7 +977,7 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 8
|
||||
max-parallel: 12
|
||||
matrix: ${{ fromJson(needs.preflight.outputs.plugin_contracts_matrix) }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -1029,7 +1058,7 @@ jobs:
|
||||
timeout-minutes: 60
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 8
|
||||
max-parallel: 12
|
||||
matrix: ${{ fromJson(needs.preflight.outputs.channel_contracts_matrix) }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -1183,8 +1212,8 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
# The canonical main path waits for the admission debounce above, so
|
||||
# modestly widen this large matrix without recreating registration bursts.
|
||||
max-parallel: 16
|
||||
# widen this large matrix within the current runner-registration budget.
|
||||
max-parallel: 24
|
||||
matrix: ${{ fromJson(needs.preflight.outputs.checks_node_core_nondist_matrix) }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -1322,7 +1351,7 @@ jobs:
|
||||
timeout-minutes: 20
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 8
|
||||
max-parallel: 12
|
||||
matrix:
|
||||
include:
|
||||
- check_name: check-guards
|
||||
@@ -1464,7 +1493,7 @@ jobs:
|
||||
timeout-minutes: 20
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 8
|
||||
max-parallel: 12
|
||||
matrix:
|
||||
include:
|
||||
- check_name: check-additional-boundaries-a
|
||||
@@ -2390,7 +2419,8 @@ jobs:
|
||||
- macos-swift
|
||||
- ios-build
|
||||
- android
|
||||
if: ${{ !cancelled() && always() && github.event_name != 'push' && (github.event_name != 'pull_request' || !github.event.pull_request.draft) }}
|
||||
# Re-enable this job when we want to collect CI timing data for timing optimization.
|
||||
if: ${{ false && !cancelled() && always() && github.event_name != 'push' && (github.event_name != 'pull_request' || !github.event.pull_request.draft) }}
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
|
||||
14
.github/workflows/maturity-scorecard.yml
vendored
14
.github/workflows/maturity-scorecard.yml
vendored
@@ -134,7 +134,7 @@ jobs:
|
||||
with:
|
||||
ref: ${{ inputs.ref }}
|
||||
expected_sha: ${{ needs.validate_selected_ref.outputs.selected_revision }}
|
||||
qa_profile: release
|
||||
qa_profile: all
|
||||
secrets:
|
||||
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
||||
|
||||
@@ -238,8 +238,8 @@ jobs:
|
||||
}
|
||||
|
||||
const evidence = JSON.parse(fs.readFileSync(evidencePath, "utf8"));
|
||||
if (evidence.profile !== "release") {
|
||||
throw new Error(`qa-evidence.json profile must be release, got ${JSON.stringify(evidence.profile)}`);
|
||||
if (evidence.profile !== "all") {
|
||||
throw new Error(`qa-evidence.json profile must be all, got ${JSON.stringify(evidence.profile)}`);
|
||||
}
|
||||
|
||||
const artifactDir = path.dirname(evidencePath);
|
||||
@@ -256,8 +256,8 @@ jobs:
|
||||
const manifestPath = path.join(artifactDir, manifestNames[0]);
|
||||
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
||||
const manifestProfile = manifest.qaProfile ?? evidence.profile;
|
||||
if (manifestProfile !== "release") {
|
||||
throw new Error(`QA evidence manifest profile must be release, got ${JSON.stringify(manifestProfile)}`);
|
||||
if (manifestProfile !== "all") {
|
||||
throw new Error(`QA evidence manifest profile must be all, got ${JSON.stringify(manifestProfile)}`);
|
||||
}
|
||||
if (manifest.targetSha !== targetSha) {
|
||||
throw new Error(`QA evidence manifest targetSha ${manifest.targetSha} does not match selected ref ${targetSha}`);
|
||||
@@ -428,14 +428,14 @@ jobs:
|
||||
cat > "$body_file" <<BODY
|
||||
## Summary
|
||||
|
||||
- render maturity scorecard docs from \`qa/maturity-scores.yaml\` and release QA evidence
|
||||
- render maturity scorecard docs from \`qa/maturity-scores.yaml\` and full taxonomy QA evidence
|
||||
- maturity source ref: ${REF_INPUT}
|
||||
- QA evidence run: ${evidence_run_id}
|
||||
|
||||
## Verification
|
||||
|
||||
- QA Lab maturity score validation passed
|
||||
- Maturity scorecard workflow rendered docs from release profile qa-evidence.json artifacts with strict inputs
|
||||
- Maturity scorecard workflow rendered docs from all profile qa-evidence.json artifacts with strict inputs
|
||||
BODY
|
||||
|
||||
pr_url="$(gh pr list --head "$branch" --state open --json url --jq '.[0].url // ""')"
|
||||
|
||||
51
.github/workflows/plugin-init-scaffold-validation.yml
vendored
Normal file
51
.github/workflows/plugin-init-scaffold-validation.yml
vendored
Normal file
@@ -0,0 +1,51 @@
|
||||
name: Plugin Init Scaffold Validation
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- ".github/workflows/plugin-init-scaffold-validation.yml"
|
||||
- "package.json"
|
||||
- "pnpm-lock.yaml"
|
||||
- "scripts/validate-plugin-init-provider-scaffold.ts"
|
||||
- "src/cli/plugins-authoring-command.ts"
|
||||
- "src/cli/plugins-authoring-command.test.ts"
|
||||
- "src/cli/plugins-cli.ts"
|
||||
- "src/plugin-sdk/**"
|
||||
pull_request:
|
||||
types: [opened, reopened, synchronize, ready_for_review]
|
||||
paths:
|
||||
- ".github/workflows/plugin-init-scaffold-validation.yml"
|
||||
- "package.json"
|
||||
- "pnpm-lock.yaml"
|
||||
- "scripts/validate-plugin-init-provider-scaffold.ts"
|
||||
- "src/cli/plugins-authoring-command.ts"
|
||||
- "src/cli/plugins-authoring-command.test.ts"
|
||||
- "src/cli/plugins-cli.ts"
|
||||
- "src/plugin-sdk/**"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.event.pull_request.number || github.ref }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
jobs:
|
||||
validate-provider-scaffold:
|
||||
name: Validate provider scaffold
|
||||
if: github.event_name != 'pull_request' || !github.event.pull_request.draft
|
||||
runs-on: ${{ github.repository == 'openclaw/openclaw' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04' }}
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
||||
|
||||
- name: Setup Node environment
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
install-bun: "false"
|
||||
|
||||
- name: Generate and validate provider scaffold
|
||||
run: pnpm test:plugins:init-provider-scaffold
|
||||
2
.github/workflows/qa-profile-evidence.yml
vendored
2
.github/workflows/qa-profile-evidence.yml
vendored
@@ -18,7 +18,7 @@ on:
|
||||
qa_profile:
|
||||
description: Taxonomy QA profile id to run (for example release or all)
|
||||
required: true
|
||||
default: release
|
||||
default: all
|
||||
type: string
|
||||
workflow_call:
|
||||
inputs:
|
||||
|
||||
5
.github/workflows/sandbox-common-smoke.yml
vendored
5
.github/workflows/sandbox-common-smoke.yml
vendored
@@ -57,11 +57,10 @@ jobs:
|
||||
BASE_IMAGE="openclaw-sandbox-smoke-base:bookworm-slim" \
|
||||
TARGET_IMAGE="openclaw-sandbox-common-smoke:bookworm-slim" \
|
||||
PACKAGES="ca-certificates" \
|
||||
INSTALL_PNPM=0 \
|
||||
INSTALL_BUN=0 \
|
||||
INSTALL_BREW=0 \
|
||||
FINAL_USER=sandbox \
|
||||
scripts/sandbox-common-setup.sh
|
||||
|
||||
u="$(timeout --kill-after=30s 2m docker run --rm openclaw-sandbox-common-smoke:bookworm-slim sh -lc 'id -un')"
|
||||
test "$u" = "sandbox"
|
||||
timeout --kill-after=30s 2m docker run --rm openclaw-sandbox-common-smoke:bookworm-slim sh -lc \
|
||||
'set -e; test "$(id -un)" = sandbox; node --version; pnpm --version'
|
||||
|
||||
@@ -118,11 +118,11 @@ Skills own workflows; root owns hard policy and routing.
|
||||
- Tests in a normal source checkout: `pnpm test <path-or-filter> [vitest args...]`, `pnpm test:changed`, `pnpm test:serial`, `pnpm test:coverage`; never raw `vitest`.
|
||||
- If raw Vitest is unavoidable, use `vitest run ...`; bare `vitest ...` starts local watch mode and will not exit on its own.
|
||||
- Tests in a Codex worktree or linked/sparse checkout: avoid direct local `pnpm test*`; use `node scripts/run-vitest.mjs <path-or-filter>` for tiny explicit-file proof, or Crabbox/Testbox for anything broader.
|
||||
- Checks in a normal source checkout: `pnpm check:changed` delegates to Crabbox/Testbox; lanes: `pnpm changed:lanes --json`; staged: `pnpm check:changed --staged`; full: `pnpm check`.
|
||||
- Checks/lint in a normal source checkout: `pnpm check:changed` delegates to Crabbox/Testbox; lanes: `pnpm changed:lanes --json`; staged/path-scoped: `pnpm check:changed --staged` or `pnpm check:changed -- <files...>`; full `pnpm check`/`pnpm lint` only when required.
|
||||
- Checks in a Codex worktree or linked/sparse checkout: avoid direct local `pnpm check*`; use `node scripts/crabbox-wrapper.mjs run ... -- env OPENCLAW_CHECK_CHANGED_REMOTE_CHILD=1 OPENCLAW_CHANGED_LANES_RAW_SYNC=1 corepack pnpm check:changed` so pnpm runs inside Testbox, not locally.
|
||||
- Extension tests: `pnpm test:extensions`, `pnpm test extensions`, `pnpm test extensions/<id>`.
|
||||
- Typecheck: `tsgo` lanes only (`pnpm tsgo*`, `pnpm check:test-types`); never add `tsc --noEmit`, `typecheck`, `check:types`.
|
||||
- Formatting: `oxfmt`, not Prettier. Use repo wrappers (`pnpm format:*`, `pnpm lint:*`, `scripts/run-oxlint.mjs`).
|
||||
- Formatting: `oxfmt`, not Prettier. Use repo wrappers (`pnpm format:*`, `scripts/run-oxlint.mjs`; full `pnpm lint:*` only when scope requires).
|
||||
- Build before push when build output, packaging, lazy/module boundaries, dynamic imports, or published surfaces can change.
|
||||
|
||||
## Validation
|
||||
@@ -143,6 +143,9 @@ Skills own workflows; root owns hard policy and routing.
|
||||
|
||||
## GitHub / PRs
|
||||
|
||||
- Fresh GitHub items: read `CONTRIBUTING.md`, the issue chooser/form, PR template, and `.github/CODEOWNERS`; blank issues are disabled; preserve templates and evidence requirements.
|
||||
- Agent-authored/non-trivial work: create or reuse the issue first; tiny fixes may go direct. PRs use the template, link context, and keep durable problem/impact/evidence sections.
|
||||
- Route support to Discord and security through `SECURITY.md`. Use listed maintainer areas/`CODEOWNERS`; never guess mentions.
|
||||
- Use `$openclaw-pr-maintainer` immediately for maintainer-side OpenClaw issue/PR review, triage, duplicates, labels, comments, close, land, or evidence. Contributor PR creation/refresh follows the requested contributor workflow; linked refs alone do not require maintainer archive tooling.
|
||||
- Issue/PR start: `git status -sb`; if clean, `git pull --ff-only`; if dirty, yell before pull/rebase.
|
||||
- PR refs: `gh pr view/diff` or `gh api`, not web search. Prefer `gitcrawl` for maintainer discovery; missing/stale `gitcrawl` falls through to live `gh`, not contributor setup. Verify live with `gh` before mutation.
|
||||
|
||||
118
CHANGELOG.md
118
CHANGELOG.md
@@ -2,44 +2,100 @@
|
||||
|
||||
Docs: https://docs.openclaw.ai
|
||||
|
||||
## 2026.6.10
|
||||
|
||||
### Highlights
|
||||
|
||||
- **Automatic fast mode for talks:** OpenClaw can enable fast mode for short conversational turns, then return to normal mode for longer runs with bounded fallback and delivery behavior. (#85104) Thanks @alexph-dev and @vincentkoc.
|
||||
- **More reliable model routing:** Zai model synthesis, GLM overload failover, and native reasoning-level selection now follow the active model catalog more consistently. (#94461, #93241, #94067, #94136) Thanks @Pandah97, @chrysb, @0xghost42, @zhengli0922, @openperf, @civiltox, and @BorClaw.
|
||||
- **Safer session and channel state:** channel switches reset stale origin fields, and cron delivery awareness stays attached to the target session. (#95328, #93580) Thanks @ZengWen-DT, @jalehman, @gorkem2020, and @scotthuang.
|
||||
- **Trusted policies survive hook composition:** composed hook registries keep the trusted tool policies required by approval-sensitive flows. (#94545) Thanks @jesse-merhi.
|
||||
|
||||
### Changes
|
||||
|
||||
- **Agent and channel runtime:** fast-mode state now survives retries, fallback transitions, progress events, and embedded/CLI/ACP normalization; session and channel routing retain the current target and delivery context. (#85104, #93580, #95328) Thanks @alexph-dev, @vincentkoc, @scotthuang, @ZengWen-DT, @jalehman, and @gorkem2020.
|
||||
- **Provider behavior:** model catalogs now supply the correct Zai base URL, overload classification, and native reasoning controls for live-discovered models. (#94461, #93241, #94067, #94136) Thanks @Pandah97, @chrysb, @0xghost42, @zhengli0922, @openperf, @civiltox, and @BorClaw.
|
||||
## Unreleased
|
||||
|
||||
### Fixes
|
||||
|
||||
- **Fast-mode and policy correctness:** fallback cutoffs and reset notices are bounded, repeated progress events remain visible, Codex service-tier state is normalized, and trusted policies are not lost when hook registries are composed. (#85104, #94545) Thanks @alexph-dev, @vincentkoc, and @jesse-merhi.
|
||||
- **Model and delivery edge cases:** Zai and GLM failover paths use the right runtime metadata, while stale channel-origin state no longer leaks across session changes. (#94461, #93241, #95328) Thanks @Pandah97, @chrysb, @0xghost42, @zhengli0922, @ZengWen-DT, @jalehman, and @gorkem2020.
|
||||
- **Provider plugin onboarding:** setup refreshes provider plugin registry metadata after installing setup-selected provider plugins, so auth continuation uses the newly installed provider instead of stale registry state. (#95792) Thanks @snowzlmbot.
|
||||
- **WeChat account routing:** `startAccount` preserves session routing by resolving manifest channel account config from raw account keys with opaque provider ids, while still ignoring manifest account keys that normalize to blocked object keys. (#93686) Thanks @zhangguiping-xydt.
|
||||
|
||||
### Complete contribution record
|
||||
## 2026.6.10
|
||||
|
||||
This audited record covers the complete v2026.6.9..HEAD history: 12 merged PRs. The generation manifest also supplies direct commits as editorial input; the grouped notes above prioritize user impact.
|
||||
Automatic fast mode starts short conversations quickly, then returns longer or fallback work to normal mode without losing visible state. Provider routing, channel progress, session identity, and trusted tool policies are more reliable, with smaller improvements spanning provider setup, diagnostics, and transcript tooling.
|
||||
|
||||
#### Pull requests
|
||||
### Highlights
|
||||
|
||||
- **PR #86627** Keep core doctor health in contribution order. Thanks @giodl73-repo.
|
||||
- **PR #93580** fix: preserve cron delivery awareness for target sessions. Thanks @scotthuang and @jalehman.
|
||||
- **PR #95030** refactor: add SDK transcript identity target API. Thanks @jalehman.
|
||||
- **PR #94838** refactor(copilot): complete harness lifecycle parity. Thanks @vincentkoc.
|
||||
- **PR #95328** fix(sessions): reset stale per-channel origin fields on channel switch. Related #95325. Thanks @ZengWen-DT and @jalehman and @gorkem2020.
|
||||
- **PR #94461** fix(zai): fall back to manifest baseUrl for synthesized GLM-5 models. Related #94269. Thanks @Pandah97 and @chrysb.
|
||||
- **PR #93241** fix(agents): classify Zhipu GLM overload as overloaded for failover. Related #93211. Thanks @0xghost42 and @zhengli0922.
|
||||
- **PR #94067** fix(channels): resolve native /think menu levels via runtime catalog for live-discovered models. Related #93835. Thanks @openperf and @civiltox.
|
||||
- **PR #94136** fix(zai): expose GLM-5.2 reasoning levels [AI-assisted]. Thanks @BorClaw.
|
||||
- **PR #85104** feat: fast talks auto mode. Related #85087. Thanks @alexph-dev.
|
||||
- **PR #94545** fix: keep trusted policies with hook registry. Thanks @jesse-merhi.
|
||||
- **PR #95792** fix(onboard): refresh provider plugin registry after setup installs. Related #95765. Thanks @snowzlmbot.
|
||||
#### Automatic fast mode
|
||||
|
||||
- Adds [`/fast auto`](https://docs.openclaw.ai/tools/thinking) so short conversational calls can start quickly, while longer or fallback work returns to normal mode with the effective state still visible. [PR #85104](https://github.com/openclaw/openclaw/pull/85104), [Issue #85087](https://github.com/openclaw/openclaw/issues/85087). Thanks @alexph-dev and @vincentkoc.
|
||||
- Shows the effective automatic fast-mode state in status instead of reducing it to on/off, and avoids carrying a cleared Codex service-tier choice into later runs. [8845f2f](https://github.com/openclaw/openclaw/commit/8845f2fd6143becc37110ab5021dd5e1517f0cdc). Thanks @vincentkoc.
|
||||
- Keeps automatic fast-mode timing consistent when a turn switches to a fallback model. [075091d](https://github.com/openclaw/openclaw/commit/075091d0cab94053ff094268efc0acb225d514f4). Thanks @vincentkoc.
|
||||
- Keeps the original fast-mode timing and progress behavior when a live model switch retries a turn. [d1e190f](https://github.com/openclaw/openclaw/commit/d1e190fbe822ad6ae4e660ce376b60ec9fdb0fba). Thanks @vincentkoc.
|
||||
- Keeps automatic fast-mode progress and reset behavior distinct from explicit fast mode after a run switches modes. [20aec98](https://github.com/openclaw/openclaw/commit/20aec985545db7a24ea066e5bff1c47b789cbded). Thanks @vincentkoc.
|
||||
- Shows the effective fast-mode value in connected-agent sessions instead of the configured value, so status reflects what the session is actually using. [9509aa0](https://github.com/openclaw/openclaw/commit/9509aa063c0ef3e32be1516fcb0c23606b6d5c7b). Thanks @vincentkoc.
|
||||
- Keeps the effective automatic fast-mode setting visible through fallback transitions in connected-agent sessions. [7f5423c](https://github.com/openclaw/openclaw/commit/7f5423ca97174a3f16c211db54a6c96e5b3a6089). Thanks @vincentkoc.
|
||||
- Keeps automatic fast-mode timing and progress consistent when reply and [scheduled-agent runs](https://docs.openclaw.ai/automation/cron-jobs) retry or switch models. [6c29f88](https://github.com/openclaw/openclaw/commit/6c29f88913796bfe05696556cd82246670b126f0). Thanks @vincentkoc.
|
||||
- Keeps fast-mode cleanup and status consistent when a run switches between fallback models. [c4694f8](https://github.com/openclaw/openclaw/commit/c4694f84ffd52064f89609098cc4f8570fb72e1b). Thanks @vincentkoc.
|
||||
- Shows the automatic fast-mode reset only when fallback work is finished, so status messages match the end of the transition. [f4d93c8](https://github.com/openclaw/openclaw/commit/f4d93c855bff6930f5e5d739b95e0c2612ec4899). Thanks @vincentkoc.
|
||||
- Shows reset and delivery progress at the right time when auto-reply or other follow-up runs retry or leave automatic fast mode. [684e440](https://github.com/openclaw/openclaw/commit/684e44013778bd47d159e64b2595e4d09a92ebea). Thanks @vincentkoc.
|
||||
|
||||
### Channels and Messaging
|
||||
|
||||
#### Channel delivery and progress updates
|
||||
|
||||
- Prevents the next turn after a [scheduled message](https://docs.openclaw.ai/automation/cron-jobs) from losing what was delivered or whether delivery failed, so replies can use that context without exposing cron details in the channel. [PR #93580](https://github.com/openclaw/openclaw/pull/93580). Thanks @jalehman and @scotthuang.
|
||||
- Prevents streamed channel progress from dropping a repeated status that represents a separate step, so each meaningful step remains visible in the draft. [2d42e52](https://github.com/openclaw/openclaw/commit/2d42e52ac5513e0bd824b8a0e069db83e04bc056). Thanks @vincentkoc.
|
||||
- Prevents keyed streamed progress from staying on an older status, so viewers see the latest state instead of stale text. [8bb6472](https://github.com/openclaw/openclaw/commit/8bb6472c4de2eea06f1ba31d6ed679e2ac4581b0). Thanks @vincentkoc.
|
||||
|
||||
### Providers and Models
|
||||
|
||||
#### Provider model catalogs and reasoning controls
|
||||
|
||||
- Treats Zhipu/GLM overload responses as overloads, so a configured fallback is selected for the right reason instead of following the wrong failover path. [PR #93241](https://github.com/openclaw/openclaw/pull/93241), [Issue #93211](https://github.com/openclaw/openclaw/issues/93211). Thanks @0xghost42 and @zhengli0922.
|
||||
- Prevents Telegram, Slack, and Discord `/think` menus for live Ollama models from hiding supported levels, so users can choose valid reasoning settings without guessing. [PR #94067](https://github.com/openclaw/openclaw/pull/94067), [Issue #93835](https://github.com/openclaw/openclaw/issues/93835). Thanks @civiltox and @openperf.
|
||||
- Expands [`zai/glm-5.2` thinking choices](https://docs.openclaw.ai/tools/thinking) beyond binary on/off and sends high or max requests as the intended Z.AI reasoning effort. [PR #94136](https://github.com/openclaw/openclaw/pull/94136). Thanks @borclaw.
|
||||
- Prevents bundled [Z.ai GLM-5 models](https://docs.openclaw.ai/providers/zai) from falling through to OpenAI and producing misleading API-key errors, so they use Z.AI by default. [PR #94461](https://github.com/openclaw/openclaw/pull/94461), [Issue #94269](https://github.com/openclaw/openclaw/issues/94269). Thanks @chrysb and @pandah97.
|
||||
- Adds GLM-5.2 and Kimi K2.7 Code to the [OpenCode Go catalog](https://docs.openclaw.ai/providers/opencode-go) with current limits, so users can select the models from OpenClaw. [66f84a9](https://github.com/openclaw/openclaw/commit/66f84a9bf1082de26f92b2b3741cc2f34aba34fa). Thanks @samson1357924.
|
||||
- Corrects `kimi-k2.7-code` capability listings so OpenCode Go users are not offered unsupported video prompts when the model accepts text and images. [715dc71](https://github.com/openclaw/openclaw/commit/715dc718fc5a2a5d6f7e9ec16e0269382b726e83).
|
||||
|
||||
#### Provider plugin onboarding
|
||||
|
||||
- Prevents first-run setup from skipping the selected provider's credential prompt after plugin installation, so onboarding continues with that provider instead of falling back to OpenAI. [PR #95792](https://github.com/openclaw/openclaw/pull/95792), [Issue #95765](https://github.com/openclaw/openclaw/issues/95765). Thanks @snowzlmbot.
|
||||
|
||||
### Memory, Sessions, and State
|
||||
|
||||
#### Session transcript SDK helpers
|
||||
|
||||
- Adds a durable [session-transcript SDK contract](https://docs.openclaw.ai/plugins/sdk-runtime) so plugins can read, append, publish, and lock the intended transcript without treating [legacy file paths](https://docs.openclaw.ai/plugins/sdk-subpaths) as identity. [PR #95030](https://github.com/openclaw/openclaw/pull/95030). Thanks @jalehman.
|
||||
|
||||
#### Cross-channel session identity
|
||||
|
||||
- Prevents a shared direct-message [session](https://docs.openclaw.ai/concepts/session) from carrying the previous [channel's identity](https://docs.openclaw.ai/channels/channel-routing) after a switch, so status, reactions, threads, and message references target the current channel. [PR #95328](https://github.com/openclaw/openclaw/pull/95328), [Issue #95325](https://github.com/openclaw/openclaw/issues/95325). Thanks @gorkem2020, @jalehman, and @zengwen-dt.
|
||||
|
||||
### Gateway, Security, and Trust
|
||||
|
||||
#### Prompt context boundaries
|
||||
|
||||
- Keeps empty prompts separate from hook-added context during compaction or session reuse in [Copilot and Codex sessions](https://docs.openclaw.ai/plugins/copilot), so prompt boundaries remain consistent. [PR #94838](https://github.com/openclaw/openclaw/pull/94838). Thanks @vincentkoc.
|
||||
|
||||
#### Trusted tool policy enforcement
|
||||
|
||||
- Keeps [approval-sensitive Gateway and plugin tools](https://docs.openclaw.ai/plugins/hooks) protected when connected extensions change, so configured safeguards continue to apply. [PR #94545](https://github.com/openclaw/openclaw/pull/94545). Thanks @jesse-merhi.
|
||||
|
||||
#### Trusted package redirects
|
||||
|
||||
- Prevents authenticated package-source tokens from being sent to an allowed redirect on another origin, while the valid redirected download still completes. [b0df6dc](https://github.com/openclaw/openclaw/commit/b0df6dc10eb5b9e9fdca93063a16316f8589954e).
|
||||
|
||||
### Clients and Interfaces
|
||||
|
||||
#### Docker and Podman setup timeouts
|
||||
|
||||
- Prevents [Docker](https://docs.openclaw.ai/install/docker) and [Podman](https://docs.openclaw.ai/install/podman) setup from running unbounded on hosts where GNU timeout is installed as `gtimeout`, so image pulls, builds, and detached startup receive the intended guard. [62b2e9e](https://github.com/openclaw/openclaw/commit/62b2e9ef14b4be6fd396621c8e5e248331f08695).
|
||||
|
||||
### Plugins, Packaging, and QA
|
||||
|
||||
#### Codex service-tier clearing
|
||||
|
||||
- Prevents cleared [Codex service tiers](https://docs.openclaw.ai/tools/thinking) from being persisted as explicit stale state, so resumed or switched conversations use the normal default instead. [cd32d9f](https://github.com/openclaw/openclaw/commit/cd32d9ff91caf84c0ead38796ef096cdc5bea06e). Thanks @vincentkoc.
|
||||
|
||||
#### StepFun provider installation
|
||||
|
||||
- Restores [ClawHub discovery](https://docs.openclaw.ai/plugins/reference/stepfun) for the [StepFun provider](https://docs.openclaw.ai/providers/stepfun) plugin, so operators can install it through either ClawHub or npm. [ecb82f1](https://github.com/openclaw/openclaw/commit/ecb82f1be93024be23c1b191ebea92c63230b6c0). Thanks @vincentkoc.
|
||||
|
||||
### Docs and Operator Workflows
|
||||
|
||||
#### Doctor check ordering
|
||||
|
||||
- Keeps core [`openclaw doctor`](https://docs.openclaw.ai/gateway/doctor) diagnostics in their normal order before extension checks, making lint and repair output easier to follow. [PR #86627](https://github.com/openclaw/openclaw/pull/86627). Thanks @giodl73-repo.
|
||||
|
||||
## 2026.6.9
|
||||
|
||||
|
||||
@@ -97,6 +97,23 @@ Welcome to the lobster tank! 🦞
|
||||
4. **Test/CI-only PRs for known `main` failures** → Don't open a PR. The Maintainer team is already tracking those failures, and PRs that only tweak tests or CI to chase them will be closed unless they are required to validate a new fix.
|
||||
5. **Questions** → Discord [#help](https://discord.com/channels/1456350064065904867/1459642797895319552) / [#users-helping-users](https://discord.com/channels/1456350064065904867/1459007081603403828)
|
||||
|
||||
## Issue, PR, and Contact Routing
|
||||
|
||||
Start from this routing map before creating GitHub items:
|
||||
|
||||
| Situation | Use | Required evidence |
|
||||
| -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------- |
|
||||
| Product bug, regression, crash, or behavior defect | [Bug report](https://github.com/openclaw/openclaw/issues/new?template=bug_report.yml) | Repro steps, expected vs actual behavior, version, OS, model/provider route when relevant, logs/screenshots, impact |
|
||||
| Documentation bug or missing/contradictory docs | [Docs bug report](https://github.com/openclaw/openclaw/issues/new?template=docs_bug_report.yml) | Affected docs path or URL, verification steps, expected docs content, actual docs content, impact, evidence |
|
||||
| New feature, architecture change, or product improvement | [Feature request](https://github.com/openclaw/openclaw/issues/new?template=feature_request.yml) or Discord first | Problem, proposed solution, alternatives, impact, examples or prior art |
|
||||
| Onboarding, setup help, or general support question | Discord [#help](https://discord.com/channels/1456350064065904867/1459642797895319552) / [#users-helping-users](https://discord.com/channels/1456350064065904867/1459007081603403828) | Do not open a GitHub issue unless there is a concrete product defect or docs gap |
|
||||
| Security vulnerability | See [Report a Vulnerability](#report-a-vulnerability) below | Do not file public issues for private security reports |
|
||||
| PR for an existing or newly filed issue | Use the [PR template](.github/pull_request_template.md) | Visible `Closes #<issue>` or `Related: #<issue>`, problem, shipped solution, user impact, validation evidence |
|
||||
|
||||
For agent-authored or otherwise non-trivial work, create or reuse the issue first, then open the PR against it. Bugs and very small fixes may go straight to PR, but still link existing context when it exists and fill out the PR template.
|
||||
|
||||
Do not guess who to tag. Let issue forms, labels/automation, `.github/CODEOWNERS`, and the maintainer areas above route the work. Mention a maintainer only when their listed area or owned path is directly relevant and you need a decision; otherwise rely on normal review. For coordinated change sets, ask in **#clawtributors** before opening more than the PR limit.
|
||||
|
||||
## PR Limits
|
||||
|
||||
We cap at **20 open PRs per author**. If you exceed this, the `r: too-many-prs` label is added and your PR is auto-closed. This is a hard limit.
|
||||
|
||||
@@ -304,6 +304,9 @@ by Peter Steinberger and the community.
|
||||
## Community
|
||||
|
||||
See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines, maintainers, and how to submit PRs.
|
||||
Use the [issue chooser](https://github.com/openclaw/openclaw/issues/new/choose) for bugs, docs bugs, and feature requests;
|
||||
ask setup/support questions in [Discord](https://discord.gg/clawd); and report vulnerabilities through [SECURITY.md](SECURITY.md).
|
||||
PRs should link the relevant issue when possible and follow the [PR template](.github/pull_request_template.md) with problem, impact, and evidence.
|
||||
AI/vibe-coded PRs welcome! 🤖
|
||||
|
||||
Special thanks to [Mario Zechner](https://mariozechner.at/) for his support and for
|
||||
|
||||
174
appcast.xml
174
appcast.xml
@@ -2,6 +2,53 @@
|
||||
<rss xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle" version="2.0">
|
||||
<channel>
|
||||
<title>OpenClaw</title>
|
||||
<item>
|
||||
<title>2026.6.10</title>
|
||||
<pubDate>Fri, 26 Jun 2026 23:37:36 +0000</pubDate>
|
||||
<link>https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml</link>
|
||||
<sparkle:version>2606001090</sparkle:version>
|
||||
<sparkle:shortVersionString>2026.6.10</sparkle:shortVersionString>
|
||||
<sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>
|
||||
<description><![CDATA[<h2>OpenClaw 2026.6.10</h2>
|
||||
<h3>Highlights</h3>
|
||||
<ul>
|
||||
<li><strong>Automatic fast mode for talks:</strong> OpenClaw can enable fast mode for short conversational turns, then return to normal mode for longer runs with bounded fallback and delivery behavior. (#85104) Thanks @alexph-dev and @vincentkoc.</li>
|
||||
<li><strong>More reliable model routing:</strong> Zai model synthesis, GLM overload failover, and native reasoning-level selection now follow the active model catalog more consistently. (#94461, #93241, #94067, #94136) Thanks @Pandah97, @chrysb, @0xghost42, @zhengli0922, @openperf, @civiltox, and @BorClaw.</li>
|
||||
<li><strong>Safer session and channel state:</strong> channel switches reset stale origin fields, and cron delivery awareness stays attached to the target session. (#95328, #93580) Thanks @ZengWen-DT, @jalehman, @gorkem2020, and @scotthuang.</li>
|
||||
<li><strong>Trusted policies survive hook composition:</strong> composed hook registries keep the trusted tool policies required by approval-sensitive flows. (#94545) Thanks @jesse-merhi.</li>
|
||||
</ul>
|
||||
<h3>Changes</h3>
|
||||
<ul>
|
||||
<li><strong>Agent and channel runtime:</strong> fast-mode state now survives retries, fallback transitions, progress events, and embedded/CLI/ACP normalization; session and channel routing retain the current target and delivery context. (#85104, #93580, #95328) Thanks @alexph-dev, @vincentkoc, @scotthuang, @ZengWen-DT, @jalehman, and @gorkem2020.</li>
|
||||
<li><strong>Provider behavior:</strong> model catalogs now supply the correct Zai base URL, overload classification, and native reasoning controls for live-discovered models. (#94461, #93241, #94067, #94136) Thanks @Pandah97, @chrysb, @0xghost42, @zhengli0922, @openperf, @civiltox, and @BorClaw.</li>
|
||||
</ul>
|
||||
<h3>Fixes</h3>
|
||||
<ul>
|
||||
<li><strong>Fast-mode and policy correctness:</strong> fallback cutoffs and reset notices are bounded, repeated progress events remain visible, Codex service-tier state is normalized, and trusted policies are not lost when hook registries are composed. (#85104, #94545) Thanks @alexph-dev, @vincentkoc, and @jesse-merhi.</li>
|
||||
<li><strong>Model and delivery edge cases:</strong> Zai and GLM failover paths use the right runtime metadata, while stale channel-origin state no longer leaks across session changes. (#94461, #93241, #95328) Thanks @Pandah97, @chrysb, @0xghost42, @zhengli0922, @ZengWen-DT, @jalehman, and @gorkem2020.</li>
|
||||
<li><strong>Provider plugin onboarding:</strong> setup refreshes provider plugin registry metadata after installing setup-selected provider plugins, so auth continuation uses the newly installed provider instead of stale registry state. (#95792) Thanks @snowzlmbot.</li>
|
||||
</ul>
|
||||
<h3>Complete contribution record</h3>
|
||||
This audited record covers the complete v2026.6.9..HEAD history: 12 merged PRs. The generation manifest also supplies direct commits as editorial input; the grouped notes above prioritize user impact.
|
||||
<h4>Pull requests</h4>
|
||||
<ul>
|
||||
<li><strong>PR #86627</strong> Keep core doctor health in contribution order. Thanks @giodl73-repo.</li>
|
||||
<li><strong>PR #93580</strong> fix: preserve cron delivery awareness for target sessions. Thanks @scotthuang and @jalehman.</li>
|
||||
<li><strong>PR #95030</strong> refactor: add SDK transcript identity target API. Thanks @jalehman.</li>
|
||||
<li><strong>PR #94838</strong> refactor(copilot): complete harness lifecycle parity. Thanks @vincentkoc.</li>
|
||||
<li><strong>PR #95328</strong> fix(sessions): reset stale per-channel origin fields on channel switch. Related #95325. Thanks @ZengWen-DT and @jalehman and @gorkem2020.</li>
|
||||
<li><strong>PR #94461</strong> fix(zai): fall back to manifest baseUrl for synthesized GLM-5 models. Related #94269. Thanks @Pandah97 and @chrysb.</li>
|
||||
<li><strong>PR #93241</strong> fix(agents): classify Zhipu GLM overload as overloaded for failover. Related #93211. Thanks @0xghost42 and @zhengli0922.</li>
|
||||
<li><strong>PR #94067</strong> fix(channels): resolve native /think menu levels via runtime catalog for live-discovered models. Related #93835. Thanks @openperf and @civiltox.</li>
|
||||
<li><strong>PR #94136</strong> fix(zai): expose GLM-5.2 reasoning levels [AI-assisted]. Thanks @BorClaw.</li>
|
||||
<li><strong>PR #85104</strong> feat: fast talks auto mode. Related #85087. Thanks @alexph-dev.</li>
|
||||
<li><strong>PR #94545</strong> fix: keep trusted policies with hook registry. Thanks @jesse-merhi.</li>
|
||||
<li><strong>PR #95792</strong> fix(onboard): refresh provider plugin registry after setup installs. Related #95765. Thanks @snowzlmbot.</li>
|
||||
</ul>
|
||||
<p><a href="https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md">View full changelog</a></p>
|
||||
]]></description>
|
||||
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.6.10/OpenClaw-2026.6.10.zip" length="56115790" type="application/octet-stream" sparkle:edSignature="MEeGG8+WePhUg9uDShznmdhhAgy/WWe7bAwr4XRTauNdrM441iziQYIlwhfNrtHDHX+uE1/tkRtIMcELfuekAg=="/>
|
||||
</item>
|
||||
<item>
|
||||
<title>2026.6.8</title>
|
||||
<pubDate>Tue, 16 Jun 2026 17:17:20 +0000</pubDate>
|
||||
@@ -124,132 +171,5 @@
|
||||
]]></description>
|
||||
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.6.5/OpenClaw-2026.6.5.zip" length="55725877" type="application/octet-stream" sparkle:edSignature="EKr7gCfpEVStis9HSADJk1CWYbmH2MHMqSgNfZvLbBFCBWmk3pjBJS6K2qkxkq5lIbTj4H+Lo7Iri6ip/xTGDA=="/>
|
||||
</item>
|
||||
<item>
|
||||
<title>2026.6.1</title>
|
||||
<pubDate>Wed, 03 Jun 2026 21:26:22 +0000</pubDate>
|
||||
<link>https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml</link>
|
||||
<sparkle:version>2026060190</sparkle:version>
|
||||
<sparkle:shortVersionString>2026.6.1</sparkle:shortVersionString>
|
||||
<sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>
|
||||
<description><![CDATA[<h2>OpenClaw 2026.6.1</h2>
|
||||
<h3>Highlights</h3>
|
||||
<ul>
|
||||
<li>Agents and CLI-backed runtimes recover more cleanly from interrupted tool calls, stale session bindings, compaction handoffs, and media delivery retries. (#88129, #88136, #88141, #88162, #88182)</li>
|
||||
<li>Channels and mobile delivery are steadier across Telegram, WhatsApp, iMessage, Slack, Discord, Microsoft Teams, Google Chat, Google Meet, and iOS realtime Talk. (#88096, #88105, #88183, #88231)</li>
|
||||
<li>Provider and plugin requests now bound more timers, retries, OAuth/device-code lifetimes, media downloads, local service probes, and generated-content polling paths before they can hang a run.</li>
|
||||
<li>Skills, session metadata, gateway runtime state, plugin metadata, memory watchers, and store writes do less repeated work on hot paths while keeping config, dispatch, and Linux file-watch behavior stable. (#89185, #89188, #85351) Thanks @RomneyDa and @NianJiuZst.</li>
|
||||
<li>Skills and plugin loading now handle stale disabled snapshots and loader failures more clearly, so channel turns avoid disabled SecretRefs and operators get better recovery guidance. (#79072, #79173) Thanks @zeus1959.</li>
|
||||
<li>Workboard, SecretRef plugin manifests, hosted iOS push relay, and external Copilot/Tokenjuice packaging add broader orchestration, integration, and plugin delivery surfaces. (#82326, #87469, #87796, #88107, #88117)</li>
|
||||
<li>Skill Workshop now has a fuller Control UI flow with proposal lists, today actions, revision handoff, searchable file previews, review states, locale coverage, and reusable session routing.</li>
|
||||
<li>Chat and Control UI startup paths keep sends alive through history loading, stream deltas incrementally, skip markdown work while streaming, keep drafts local while typing, clear the composer after sends, trace first-output latency, prioritize first connect, and expose calmer composer controls. (#88772, #88825, #88998, #89030, #89106) Thanks @vincentkoc and @sallyom.</li>
|
||||
<li>Provider coverage and model metadata now include MiniMax M3, account OAuth endpoints, Google/Vertex catalog fixes, OpenRouter SQLite model caching, Copilot Claude 1M capabilities, Foundry reasoning alignment, and OpenAI response replay guards. (#88480, #88512, #88851, #88860)</li>
|
||||
<li>iMessage monitor state, inbound queues, and plugin install ledgers moved toward SQLite-backed state so restarts and local monitors recover with less duplicate filesystem scanning. (#88794, #88797)</li>
|
||||
<li>Release, CI, Docker, E2E, plugin install, and diagnostics lanes now cap more logs, response bodies, readiness probes, artifact checks, status polling, child workflow waits, docker package cleanup, quiet test stalls, and rollback snapshots so failures report bounded proof instead of stalling. (#88966) Thanks @RomneyDa.</li>
|
||||
</ul>
|
||||
<h3>Changes</h3>
|
||||
<ul>
|
||||
<li>Docs: add a dedicated Skill Workshop guide covering governed skill creation, reviewable proposals, CLI, Gateway, agent tool behavior, approval policy, support files, and recovery, and refresh the ClawHub showcase cards. (#88734) Thanks @shakkernerd and @vyctorbrzezowski.</li>
|
||||
<li>Skills: let the <code>skill_workshop</code> agent tool apply, reject, and quarantine explicit proposals through the guarded review flow. Thanks @shakkernerd.</li>
|
||||
<li>Skills: let proposals carry approved support files under standard skill folders, with scanner, hash, and rollback safeguards. Thanks @shakkernerd.</li>
|
||||
<li>Skills: let pending proposals be revised in place with versioned, dated proposal frontmatter before approval. Thanks @shakkernerd.</li>
|
||||
<li>Skills: add Skill Workshop with pending proposals, CLI/Gateway review actions, rollback metadata, and the <code>skill_workshop</code> agent tool. Thanks @shakkernerd.</li>
|
||||
<li>Skill Workshop: add the Control UI navigation, styled dashboard, proposal today view, revision dialog, file preview modal, searchable preview files, reusable session handoff, and localized strings.</li>
|
||||
<li>Plugins: externalize Tokenjuice as the official <code>@openclaw/tokenjuice</code> plugin with npm and ClawHub publish metadata.</li>
|
||||
<li>Plugins: externalize the GitHub Copilot agent runtime as the official <code>@openclaw/copilot</code> plugin with npm and ClawHub publish metadata.</li>
|
||||
<li>iOS: add hosted push relay defaults, realtime Talk playback, and a guarded WebSocket ping path for more reliable mobile sessions. (#88096, #88105, #88231)</li>
|
||||
<li>iOS: support native iPad display layouts.</li>
|
||||
<li>Workboard: add orchestration primitives and agent coordination tools for multi-agent planning and run tracking. (#87469)</li>
|
||||
<li>Workboard: wire task-backed board runs and show task comments in the edit modal.</li>
|
||||
<li>Code mode: add internal namespaces for scoped agent/global sessions and exact namespace tool dispatch. (#88043)</li>
|
||||
<li>Code mode: add MCP API files and docs for code-mode integrations.</li>
|
||||
<li>Control UI: add a Dreaming-tab agent selector and propagate the selected agent through Dreaming status, diary, and diary actions. (#78748) Thanks @stevenepalmer.</li>
|
||||
<li>Control UI: add calmer chat composer controls, local draft typing state, and first-output latency instrumentation for active chat entry. (#88772, #88998) Thanks @vincentkoc.</li>
|
||||
<li>Plugins: add a SecretRef provider integration manifest contract and extract shared LLM core packages for provider/plugin reuse. (#82326, #88117)</li>
|
||||
<li>Plugins: persist the plugin install index in SQLite so installed package lookup survives reloads with less filesystem scanning. (#88794)</li>
|
||||
<li>Providers: add MiniMax M3 model support. (#88860)</li>
|
||||
<li>Doctor: add disk space health checks and stabilize post-upgrade JSON probes.</li>
|
||||
<li>Channels: store inbound queues in SQLite and migrate iMessage monitor state to SQLite-backed tracking. (#88797)</li>
|
||||
<li>Skills: add the core skills index and centralize skills runtime loading, status, filtering, and prompt formatting.</li>
|
||||
</ul>
|
||||
<h3>Fixes</h3>
|
||||
<ul>
|
||||
<li>Release/CI/E2E: fail early when Crabbox sparse-sync full checkouts do not have enough local disk, with guidance for moving the sync root.</li>
|
||||
<li>Build: render independent CLI startup metadata help snapshots concurrently to cut cold build-all metadata time.</li>
|
||||
<li>Plugins: stop timed-out package-boundary prep steps by process group so descendant TypeScript/helper processes do not survive local check cleanup.</li>
|
||||
<li>Control UI: serve static assets asynchronously after safe-open checks so large UI files do not block Gateway request handling.</li>
|
||||
<li>Scripts/UI: forward direct wrapper SIGHUP shutdown to child processes so terminal hangups do not leave wrapped dev commands running.</li>
|
||||
<li>Gateway: return the post-expiration pending-work revision from node drains so reconnecting nodes do not observe stale queue revisions after expired items are pruned.</li>
|
||||
<li>Release/CI/E2E: keep temporary full-sync checkouts alive while slow Crabbox leases boot, so sparse worktree runs do not lose their sync source before file-list generation.</li>
|
||||
<li>Release/CI/E2E: normalize inherited Linux <code>C.UTF-8</code> locale settings before raw AWS macOS Crabbox bootstrap commands, avoiding macOS locale warnings during package-manager hydration.</li>
|
||||
<li>Release/CI/E2E: keep gateway watch regression checks from copying large static plugin assets inside the measured idle window.</li>
|
||||
<li>Update: keep core updates nonblocking when a missing external plugin repair download stalls, while still blocking installed active plugin payload smoke failures.</li>
|
||||
<li>Agents/providers: keep streaming tool-call argument parsing record-shaped when providers emit valid non-object JSON such as <code>null</code> or arrays.</li>
|
||||
<li>Release/CI/E2E: reset incremental log readers when watched log files rotate without shrinking, so same-size replacements do not hide new readiness or RPC lines.</li>
|
||||
<li>Talk: preserve explicit <code>null</code> payloads on controller-created turn and output-audio lifecycle events.</li>
|
||||
<li>Agents/TUI: keep local custom provider runs from loading plugin runtime and auth alias metadata when plugins are disabled.</li>
|
||||
<li>Agents/TUI: restore in-flight TUI run switch-back behavior, keep no-policy native hook fallback available, guard vanished workspaces, and keep lightweight isolated subagents lightweight.</li>
|
||||
<li>Agents/media: keep async image, music, and video generation starts from ending the Codex turn, so mixed requests can continue with summaries or other work while media renders in the background.</li>
|
||||
<li>Agents/Codex: keep public OpenAI API-key profiles from being treated as native Codex app-server auth while preserving persisted Codex OAuth sessions.</li>
|
||||
<li>Agents/Codex: stream Codex app-server final-answer partials to live reply previews, preserve ACP metadata in SQLite, prefer real tool results over synthetic repair output, prevent aborted app-server turn handles from lingering, migrate legacy OpenAI Codex <code>lastGood</code> auth state, and preserve workspace/session metadata through ACP runtime refactors. (#88405, #88724, #88730) Thanks @vincentkoc.</li>
|
||||
<li>Control UI: keep collapsed tool cards labeled with the tool name and action instead of generic output text. Thanks @shakkernerd.</li>
|
||||
<li>Agents/Codex: surface Skill Workshop guidance in Codex app-server prompts when <code>skill_workshop</code> is available. Thanks @shakkernerd.</li>
|
||||
<li>Skill Workshop: restore and localize the Control UI board/today view switcher so review workflows keep their intended layout toggle across locales. Thanks @shakkernerd.</li>
|
||||
<li>Agents/auth: write auth profiles atomically, dispatch auth failures by type, add force re-login recovery, preserve workspaces during state-only uninstall, and compact before oversized turns so recovery paths avoid partial state. (#89181) Thanks @RomneyDa.</li>
|
||||
<li>Skills: skip disabled skill env overrides from stale persisted snapshots so disabled skill <code>apiKey</code> SecretRefs cannot abort embedded or channel turns. (#79072, #79173) Thanks @zeus1959.</li>
|
||||
<li>Skill Workshop: render the Control UI tab from filtered navigation state and keep filtered fallback routing stable.</li>
|
||||
<li>CLI: avoid live catalog validation during <code>openclaw agents add</code>, so adding a secondary agent no longer depends on provider catalog availability. (#76284, #88314) Thanks @zhangguiping-xydt.</li>
|
||||
<li>CLI: keep <code>plugins list --json</code> on the snapshot-only path so plugin sweeps avoid loading the full runtime status graph.</li>
|
||||
<li>CLI/desktop: bridge WSL clipboard operations through the shell, recognize manual-update launchd jobs, and keep machine-readable startup output parseable during progress setup. (#88764, #88689) Thanks @alexzhu0.</li>
|
||||
<li>Plugins: make PixVerse external-plugin ClawHub metadata explicit and keep it out of bundled dist builds.</li>
|
||||
<li>Plugins: clarify plugin loader failure guidance so missing or incompatible plugin packages point operators at the right repair path.</li>
|
||||
<li>Plugins: preserve npm plugin roots after blocked installs, skip plugin-local <code>openclaw</code> peer symlinks during rollback snapshots, relink those peers after restore, isolate cached tool runtime siblings, and isolate web-provider factory failures so one bad plugin does not poison sibling runtime paths. (#77237, #88807)</li>
|
||||
<li>Cron: keep SQLite cron migrations compatible with legacy run-log tables, archived job stores, diagnostic cron names, and legacy one-shot delete-after-run behavior. (#88285)</li>
|
||||
<li>Cron: keep update delivery validation scoped, harden restart state, and retire MCP runtimes on isolated cron cleanup.</li>
|
||||
<li>Memory: serialize QMD update/embed writes per store, reduce Linux watcher fan-out, retry transient FileProvider-backed reads, preserve phase signals on read errors, harden envelope metadata sanitization, reattach Linux native watchers when directories are recreated, and rewrite generated transcript paths on rollover so memory/search state survives concurrent gateway and CLI activity. (#66339, #85931, #89185, #89188, #85351) Thanks @openperf, @amittell, @RomneyDa, and @NianJiuZst.</li>
|
||||
<li>Memory: keep vector-disabled FTS indexes from resolving embedding providers during sync and search.</li>
|
||||
<li>Providers: bound generated media downloads from OpenAI, Runway, xAI, MiniMax, BytePlus, DashScope-compatible, FAL, OpenRouter, Google, Vydra, and Comfy providers.</li>
|
||||
<li>Providers: resolve Google defaults to <code>google-generative-ai</code>, register Vertex static catalog rows, align Foundry reasoning metadata, skip DeepSeek V4 thinking params on Foundry fallback, use MiniMax account OAuth endpoints, preserve Copilot Claude 1M capabilities, suppress disabled Ollama reasoning output, forward Gemini stop sequences, strip Kimi-incompatible Anthropic cache markers, keep OpenAI stop-finished tool calls, and avoid replay ids when the Responses store is disabled. (#88480, #88512, #76612) Thanks @coder999999999, @BryanTegomoh, and @vliuyt.</li>
|
||||
<li>Providers: cap GitHub Copilot OAuth request timeouts before creating abort signals.</li>
|
||||
<li>Cron: retry recurring jobs after transient model rate limits before waiting for the next scheduled slot.</li>
|
||||
<li>Agents/Codex: keep live session locks during cleanup, recover interrupted CLI tool transcripts, preserve Codex auth and compaction session identity, clear orphan tool state, cap app-server idle timers, and keep media completion delivery retryable. (#88129, #88136, #88141, #88162, #88182)</li>
|
||||
<li>Chat/UI: show Gateway chat failures as visible assistant messages in the Control UI instead of only setting an invisible error state.</li>
|
||||
<li>Channels: cap Telegram, Discord, WhatsApp, Signal, Feishu, Google Chat, Microsoft Teams, QQBot, Nostr, Zalo, Zalouser, and Nextcloud-style request/retry timers; preserve SMS approval reply routes; and retry WhatsApp QR login 408 timeouts. (#88183)</li>
|
||||
<li>Security/config parsing: reject unsafe OAuth/token lifetimes, retry-after delays, inbound timestamps, response body sizes, command timeout config, sandbox observer token TTLs, and gateway WebSocket calls after close.</li>
|
||||
<li>Providers/media: cap local service, model, usage, queue, generated media, TTS, music, workflow polling, and provider OAuth request timers across hosted and local providers.</li>
|
||||
<li>Release/CI/E2E: bound release candidate reads, beta smoke REST calls, plugin npm verification commands, changelog restore, cross-OS process groups, kitchen-sink and bundled plugin readiness probes, secret-provider probes, Telegram credential timeouts, Control UI i18n and CLI startup metadata generation, Vitest routing, dependency guard admin approvals, child workflow failure detection, quiet Node test shard stalls, docker package cleanup, and mainline test flakes. (#88127, #88137, #88155, #88160, #88966) Thanks @RomneyDa.</li>
|
||||
<li>Release/CI/E2E: keep Kitchen Sink live plugin MCP probes resolving source-checkout workspace packages and align the live gauntlet with current Kitchen Sink diagnostics.</li>
|
||||
<li>Release/CI/E2E: run the secret-provider integration proof through the repo pnpm runner so native macOS and Windows validation use the hydrated package-manager shim.</li>
|
||||
<li>Release/CI/E2E: run the Telegram desktop proof gateway through the repo pnpm runner so native macOS proof uses the hydrated package-manager shim.</li>
|
||||
<li>Docs/CI: run Mintlify anchor checks through the repo pnpm runner so docs link validation works when pnpm is only available through the hydrated package-manager shim.</li>
|
||||
<li>Agents: keep configured fallback model metadata typed so provider params, context-token caps, and media input limits do not break changed-gate typechecks.</li>
|
||||
<li>Agents: accept hidden <code>sessions_send</code> body aliases before validation while keeping the model-facing <code>message</code> schema canonical. (#88229) Thanks @zhangguiping-xydt.</li>
|
||||
<li>Chat/UI: preserve startup chat sends during history loading, unblock the initial Control UI chat send, stream chat deltas incrementally, skip markdown parsing while streaming, keep drafts local while typing, guard composer rerenders, honor Chromium executable overrides, and detect system Chromium for E2E. (#88998) Thanks @vincentkoc.</li>
|
||||
<li>Channels: stop schema-padded poll modifiers from turning normal <code>send</code> actions into invalid poll sends. (#89601) Thanks @codezz.</li>
|
||||
<li>Channels: preserve long Feishu streaming replies, send visible fallbacks when accepted Feishu turns produce no final reply, tolerate iMessage self-chat timestamp skew, preserve colon-prefixed slash commands in mention parsing, decode Nostr <code>npub</code> allowlists correctly, and suppress raw provider errors during channel delivery. (#87896)</li>
|
||||
<li>Config/status/doctor: skip unresolved shell references in state-dir dotenv files, resolve gateway auth secrets during deep status audits, respect explicit PI runtime policy, report runtime tool-schema errors, and keep post-upgrade JSON stable. (#88288)</li>
|
||||
<li>Gateway/session state: list commands from the Gateway plugin registry, harden MCP loopback tool schemas, hide phantom agent-store rows from <code>sessions.list</code>, make task persistence failures explicit, and carry session UUIDs on interactive dispatch events.</li>
|
||||
<li>Gateway/plugins: narrow plugin lookup memoization to the stable plugin/runtime inputs, avoiding repeated lookup work without mixing disabled or filtered plugin state.</li>
|
||||
<li>OpenAI/TTS: handle speed directives for OpenAI TTS voices. (#74089)</li>
|
||||
<li>CI/Crabbox: keep default runner capacity on the Azure credit-backed on-demand D4 lane with the Azure SSH port and a Git-independent full check job, so broad validation avoids low-priority spot quota stalls, hydrate port mismatches, non-Git hydrated workspaces, and stale AWS region hints.</li>
|
||||
<li>CI/Crabbox: route Crabbox wrapper and Testbox workflow edits to their regression tests so changed-test gates do not silently run zero specs.</li>
|
||||
<li>CI/workflows: route workflow sanity helper edits to their guard tests and cover composite-action input interpolation checks.</li>
|
||||
<li>CI/tooling: route CI scope, dependency, changelog, and docs helper edits to their owner tests instead of silently skipping changed-test coverage.</li>
|
||||
<li>CI/tooling: route package, release, and install helper edits to their owner tests so changed-test gates cover publish and installer script changes.</li>
|
||||
<li>CI/tooling: route shared script library edits through their owner tests so lock, process, safety, and scan helpers do not skip changed-test coverage.</li>
|
||||
<li>CI/tooling: skip expensive import-graph scans once a changed diff already requires broad fallback, keeping local changed-test planning fast while still collecting explicit owner tests.</li>
|
||||
<li>CI/tooling: route script edits through conventional owner tests when matching <code>test/scripts</code> or <code>src/scripts</code> coverage already exists.</li>
|
||||
<li>CI/tooling: honor option terminators in the memory FD repro script so follow-on arguments are not reparsed.</li>
|
||||
<li>Release/CI/E2E: assert plugin lifecycle runtime inspect output instead of only capturing it.</li>
|
||||
<li>Release/CI/E2E: make gateway-network prove the advertised health RPC and retry early WebSocket closes without burning full open timeouts.</li>
|
||||
<li>Release/CI/E2E: honor option terminators across release, Parallels smoke, plugin gauntlet, and extension-memory scripts.</li>
|
||||
<li>Release/CI/E2E: fail plugin gateway gauntlet QA chunks when the requested suite summary is missing or invalid.</li>
|
||||
<li>Performance: prebuild QA runtime probes with generated plugin assets but without CLI startup metadata.</li>
|
||||
<li>Performance: skip declaration bundling for runtime-only CLI startup and gateway watch build profiles.</li>
|
||||
<li>Performance: reuse prepared provider handles, strict tool schemas, gateway runtime metadata, session maintenance config, plugin metadata, bundled skill allowlists, package-local plugin artifacts, single-entry store writes, and validated/serialized session prompt blobs.</li>
|
||||
</ul>
|
||||
<p><a href="https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md">View full changelog</a></p>
|
||||
]]></description>
|
||||
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.6.1/OpenClaw-2026.6.1.zip" length="55062100" type="application/octet-stream" sparkle:edSignature="PVp8E2HBCvikB/0LCr36lFEyHPAzoFA2ScT6LW27FlzvP+m4r1AEuVN2UrtgWlpkGSsn4Eav0kPJe32u4ObNBw=="/>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
@@ -56,6 +56,38 @@ Recommended workflow:
|
||||
|
||||
The third-party flavor is archived as a signed APK for non-Play distribution. It is not uploaded by the Play release lane.
|
||||
|
||||
## Release SHA tracking
|
||||
|
||||
Successful Play build uploads create a non-tag Git ref that records the source
|
||||
commit for the uploaded store build:
|
||||
|
||||
```text
|
||||
refs/openclaw/mobile-releases/android/<versionName>-<versionCode>
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```text
|
||||
refs/openclaw/mobile-releases/android/2026.6.10-2026061008
|
||||
```
|
||||
|
||||
These refs are intentionally outside `refs/tags/*` and `refs/heads/*`. They do
|
||||
not appear on GitHub release or tag pages, and they do not participate in the
|
||||
core OpenClaw release machinery.
|
||||
|
||||
`pnpm android:release:upload` checks the ref before uploading the Play build and
|
||||
records it only after `upload_to_play_store` succeeds. Existing refs are
|
||||
immutable: the same ref at the same SHA is accepted, while the same ref at a
|
||||
different SHA fails. `GOOGLE_PLAY_VALIDATE_ONLY=1` still checks the ref but does
|
||||
not record it because no Play build is published.
|
||||
|
||||
Useful direct commands:
|
||||
|
||||
```bash
|
||||
pnpm mobile:release:preflight -- --platform android --version 2026.6.10 --version-code 2026061008
|
||||
pnpm mobile:release:resolve -- --platform android --version 2026.6.10 --version-code 2026061008
|
||||
```
|
||||
|
||||
## Signing model
|
||||
|
||||
`apps/android/Config/ReleaseSigning.json` pins the Android signing assets in the shared private `apps-signing` repo. The Android pipeline uses the same `MATCH_PASSWORD` release-owner secret as iOS, but the Android files are managed by `scripts/android-release-signing.mjs` instead of Fastlane `match`.
|
||||
|
||||
@@ -198,6 +198,58 @@ def capture_android_screenshots!
|
||||
sh(shell_join(["bash", File.join(repo_root, "scripts", "android-screenshots.sh")]))
|
||||
end
|
||||
|
||||
def mobile_release_ref_script
|
||||
File.join(repo_root, "scripts", "mobile-release-ref.ts")
|
||||
end
|
||||
|
||||
def release_git_sha
|
||||
stdout, stderr, status = Open3.capture3("git", "rev-parse", "HEAD", chdir: repo_root)
|
||||
UI.user_error!("Unable to resolve release Git SHA: #{stderr.strip}") unless status.success?
|
||||
stdout.strip
|
||||
end
|
||||
|
||||
def mobile_release_ref_command(command, platform:, version:, build: nil, version_code: nil, sha: nil)
|
||||
args = [
|
||||
"node",
|
||||
"--import",
|
||||
"tsx",
|
||||
mobile_release_ref_script,
|
||||
command,
|
||||
"--platform",
|
||||
platform,
|
||||
"--version",
|
||||
version,
|
||||
"--root",
|
||||
repo_root,
|
||||
]
|
||||
args.push("--build", build.to_s) if build
|
||||
args.push("--version-code", version_code.to_s) if version_code
|
||||
args.push("--sha", sha.to_s) if sha
|
||||
sh(shell_join(args))
|
||||
end
|
||||
|
||||
def ensure_mobile_release_ref_available!(platform:, version:, build: nil, version_code: nil, sha: nil)
|
||||
mobile_release_ref_command(
|
||||
"preflight",
|
||||
platform: platform,
|
||||
version: version,
|
||||
build: build,
|
||||
version_code: version_code,
|
||||
sha: sha
|
||||
)
|
||||
end
|
||||
|
||||
def record_mobile_release_ref!(platform:, version:, build: nil, version_code: nil, sha: nil)
|
||||
mobile_release_ref_command(
|
||||
"record",
|
||||
platform: platform,
|
||||
version: version,
|
||||
build: build,
|
||||
version_code: version_code,
|
||||
sha: sha
|
||||
)
|
||||
end
|
||||
|
||||
def read_android_release_signing_properties!(path)
|
||||
UI.user_error!("Missing materialized Android release signing properties at #{path}.") unless File.exist?(path)
|
||||
|
||||
@@ -282,6 +334,13 @@ def upload_play_store_metadata!(version_metadata)
|
||||
end
|
||||
|
||||
def upload_play_store_build!(version_metadata, upload_metadata: false, upload_images: false, upload_screenshots: false)
|
||||
release_sha = release_git_sha
|
||||
ensure_mobile_release_ref_available!(
|
||||
platform: "android",
|
||||
version: version_metadata.fetch(:version),
|
||||
version_code: version_metadata.fetch(:version_code),
|
||||
sha: release_sha
|
||||
)
|
||||
ENV["SUPPLY_UPLOAD_SCREENSHOTS"] = "1" if upload_screenshots
|
||||
validate_android_screenshots!
|
||||
sync_android_changelog!(version_metadata.fetch(:version_code))
|
||||
@@ -302,6 +361,15 @@ def upload_play_store_build!(version_metadata, upload_metadata: false, upload_im
|
||||
skip_upload_screenshots: !upload_screenshots,
|
||||
validate_only: play_validate_only?
|
||||
)
|
||||
|
||||
unless play_validate_only?
|
||||
record_mobile_release_ref!(
|
||||
platform: "android",
|
||||
version: version_metadata.fetch(:version),
|
||||
version_code: version_metadata.fetch(:version_code),
|
||||
sha: release_sha
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
load_env_file(File.join(ANDROID_FASTLANE_ROOT, ".env"))
|
||||
|
||||
@@ -105,6 +105,19 @@ Reopen OpenClaw, confirm Talk is still active, then tap `Stop Talk`.
|
||||
4. Confirm at least one `agent` row is connected.
|
||||
5. Confirm the iPhone review device appears in the connected instances list.
|
||||
|
||||
## Live Activity / Dynamic Island
|
||||
|
||||
1. Tap `Settings`.
|
||||
2. Tap `Reconnect`.
|
||||
3. Immediately send OpenClaw to the background by returning to the Home Screen
|
||||
or locking the iPhone.
|
||||
4. Watch the Lock Screen or Dynamic Island while the Gateway reconnects.
|
||||
|
||||
Expected result: while reconnecting, iOS can show an `OpenClaw` Live Activity
|
||||
with connection status such as `Connecting...` or `Reconnecting...`. On a fast
|
||||
network this status may be brief because OpenClaw ends the Live Activity after
|
||||
the Gateway reconnects successfully.
|
||||
|
||||
## Push Notification
|
||||
|
||||
1. Tap the `Chat` tab.
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
<key>NSCalendarsWriteOnlyAccessUsageDescription</key>
|
||||
<string>OpenClaw uses your calendars to add events when you enable calendar access.</string>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>OpenClaw can capture photos or short video clips when requested via the gateway.</string>
|
||||
<string>OpenClaw uses the camera when you scan a Gateway setup QR code or ask your paired Gateway or assistant to capture a photo or short video from this iPhone, for example to connect to your Gateway or show your assistant a document, device screen, or workspace.</string>
|
||||
<key>NSContactsUsageDescription</key>
|
||||
<string>OpenClaw uses your contacts so you can search and reference people while using the assistant.</string>
|
||||
<key>NSLocalNetworkUsageDescription</key>
|
||||
|
||||
@@ -129,6 +129,37 @@ pnpm ios:version:pin -- --version 2026.4.10
|
||||
|
||||
This keeps the TestFlight version stable while review is in flight.
|
||||
|
||||
## Release SHA tracking
|
||||
|
||||
Successful App Store Connect uploads create a non-tag Git ref that records the
|
||||
source commit for the uploaded store build:
|
||||
|
||||
```text
|
||||
refs/openclaw/mobile-releases/ios/<CFBundleShortVersionString>-<CFBundleVersion>
|
||||
```
|
||||
|
||||
Example:
|
||||
|
||||
```text
|
||||
refs/openclaw/mobile-releases/ios/2026.6.10-8
|
||||
```
|
||||
|
||||
These refs are intentionally outside `refs/tags/*` and `refs/heads/*`. They do
|
||||
not appear on GitHub release or tag pages, and they do not participate in the
|
||||
core OpenClaw release machinery.
|
||||
|
||||
`pnpm ios:release:upload` checks the ref before archive/upload work and records
|
||||
it only after `upload_to_testflight` succeeds. Existing refs are immutable: the
|
||||
same ref at the same SHA is accepted, while the same ref at a different SHA
|
||||
fails.
|
||||
|
||||
Useful direct commands:
|
||||
|
||||
```bash
|
||||
pnpm mobile:release:preflight -- --platform ios --version 2026.6.10 --build 8
|
||||
pnpm mobile:release:resolve -- --platform ios --version 2026.6.10 --build 8
|
||||
```
|
||||
|
||||
## New release promotion workflow
|
||||
|
||||
When you want the next production iOS release to align with the current gateway release:
|
||||
|
||||
@@ -1128,6 +1128,58 @@ def prepare_app_store_release!(version:, build_number:)
|
||||
release_xcconfig
|
||||
end
|
||||
|
||||
def mobile_release_ref_script
|
||||
File.join(repo_root, "scripts", "mobile-release-ref.ts")
|
||||
end
|
||||
|
||||
def release_git_sha
|
||||
stdout, stderr, status = Open3.capture3("git", "rev-parse", "HEAD", chdir: repo_root)
|
||||
UI.user_error!("Unable to resolve release Git SHA: #{stderr.strip}") unless status.success?
|
||||
stdout.strip
|
||||
end
|
||||
|
||||
def mobile_release_ref_command(command, platform:, version:, build: nil, version_code: nil, sha: nil)
|
||||
args = [
|
||||
"node",
|
||||
"--import",
|
||||
"tsx",
|
||||
mobile_release_ref_script,
|
||||
command,
|
||||
"--platform",
|
||||
platform,
|
||||
"--version",
|
||||
version,
|
||||
"--root",
|
||||
repo_root,
|
||||
]
|
||||
args.push("--build", build.to_s) if build
|
||||
args.push("--version-code", version_code.to_s) if version_code
|
||||
args.push("--sha", sha.to_s) if sha
|
||||
sh(shell_join(args))
|
||||
end
|
||||
|
||||
def ensure_mobile_release_ref_available!(platform:, version:, build: nil, version_code: nil, sha: nil)
|
||||
mobile_release_ref_command(
|
||||
"preflight",
|
||||
platform: platform,
|
||||
version: version,
|
||||
build: build,
|
||||
version_code: version_code,
|
||||
sha: sha
|
||||
)
|
||||
end
|
||||
|
||||
def record_mobile_release_ref!(platform:, version:, build: nil, version_code: nil, sha: nil)
|
||||
mobile_release_ref_command(
|
||||
"record",
|
||||
platform: platform,
|
||||
version: version,
|
||||
build: build,
|
||||
version_code: version_code,
|
||||
sha: sha
|
||||
)
|
||||
end
|
||||
|
||||
def validate_app_store_ipa!(ipa_path)
|
||||
script_path = File.join(repo_root, "scripts", "ios-validate-app-store-ipa.sh")
|
||||
sh(shell_join(["bash", script_path, "--ipa", ipa_path]))
|
||||
@@ -1309,15 +1361,22 @@ platform :ios do
|
||||
UI.user_error!("Use `pnpm ios:release:upload`; direct Fastlane TestFlight upload is disabled.")
|
||||
end
|
||||
|
||||
release_sha = release_git_sha
|
||||
release_signing_check!
|
||||
preserve_local_signing do
|
||||
screenshots
|
||||
end
|
||||
context = prepare_app_store_context(require_api_key: true)
|
||||
ensure_mobile_release_ref_available!(
|
||||
platform: "ios",
|
||||
version: context[:short_version],
|
||||
build: context[:build_number],
|
||||
sha: release_sha
|
||||
)
|
||||
ENV["DELIVER_SCREENSHOTS"] = "1"
|
||||
ENV["DELIVER_RELEASE_NOTES"] = "1"
|
||||
metadata
|
||||
|
||||
context = prepare_app_store_context(require_api_key: true)
|
||||
build = build_app_store_release(context)
|
||||
|
||||
upload_to_testflight(
|
||||
@@ -1326,6 +1385,12 @@ platform :ios do
|
||||
skip_waiting_for_build_processing: true,
|
||||
uses_non_exempt_encryption: false
|
||||
)
|
||||
record_mobile_release_ref!(
|
||||
platform: "ios",
|
||||
version: build[:short_version],
|
||||
build: build[:build_number],
|
||||
sha: release_sha
|
||||
)
|
||||
|
||||
UI.success("Uploaded iOS App Store build: version=#{build[:version]} short=#{build[:short_version]} build=#{build[:build_number]}")
|
||||
UI.important("App Review submission remains manual in App Store Connect.")
|
||||
|
||||
@@ -156,7 +156,7 @@ targets:
|
||||
NSAllowsLocalNetworking: true
|
||||
NSBonjourServices:
|
||||
- _openclaw-gw._tcp
|
||||
NSCameraUsageDescription: OpenClaw can capture photos or short video clips when requested via the gateway.
|
||||
NSCameraUsageDescription: OpenClaw uses the camera when you scan a Gateway setup QR code or ask your paired Gateway or assistant to capture a photo or short video from this iPhone, for example to connect to your Gateway or show your assistant a document, device screen, or workspace.
|
||||
NSCalendarsUsageDescription: OpenClaw uses your calendars to show events and scheduling context when you enable calendar access.
|
||||
NSCalendarsFullAccessUsageDescription: OpenClaw uses your calendars to show events and scheduling context when you enable calendar access.
|
||||
NSCalendarsWriteOnlyAccessUsageDescription: OpenClaw uses your calendars to add events when you enable calendar access.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
1b953a19c347a27a0f9e856f23769b0c48d051354be4c88778c215231817fe8a config-baseline.json
|
||||
f3fcfb358d8b8a1f0fa8676090339ff8df1b28ef6c7e80705a979a5c70e2a323 config-baseline.core.json
|
||||
f5a5855ddd7aa8c23a732f257eceaa20fd163b1d5f342c909f4aef15aa8643cf config-baseline.json
|
||||
b8dffdb1a328aaf728a0707ab04d21c65f1a225a2360042e10832aa608699716 config-baseline.core.json
|
||||
671979e86e4c4f59415d0a20879e838f9bbd883b3d29eeb02cb5131db8d187fe config-baseline.channel.json
|
||||
94529978588d6e3776a86780b22cf9ff46a6f9957f2f178d3829403fad451ca7 config-baseline.plugin.json
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
0418a175983d6e17f535ebb49d07371ceed57c7002f8991113d548f02b1d17d1 plugin-sdk-api-baseline.json
|
||||
319e947cff12d9c2c5781b6f97f9b6b1c4f8a251dc1e87703c534a37614325cf plugin-sdk-api-baseline.jsonl
|
||||
d6953afecef50712face2a38f54744af6121bab670695f2ad85ae0048b1105a3 plugin-sdk-api-baseline.json
|
||||
67485084391dada9372630d8cefcd0562461b2af906f5312763a7f9d77a4a29d plugin-sdk-api-baseline.jsonl
|
||||
|
||||
@@ -11,7 +11,7 @@ Generated locale trees and live translation memory now live in the publish repo:
|
||||
|
||||
- English docs are authored in `openclaw/openclaw`.
|
||||
- The source docs tree lives under `docs/`.
|
||||
- The source repo no longer keeps committed generated locale trees such as `docs/zh-CN/**`, `docs/zh-TW/**`, `docs/ja-JP/**`, `docs/es/**`, `docs/pt-BR/**`, `docs/ko/**`, `docs/de/**`, `docs/fr/**`, `docs/ar/**`, `docs/it/**`, `docs/vi/**`, `docs/nl/**`, `docs/fa/**`, `docs/tr/**`, `docs/uk/**`, `docs/id/**`, `docs/pl/**`, or `docs/th/**`.
|
||||
- The source repo no longer keeps committed generated locale trees such as `docs/zh-CN/**`, `docs/zh-TW/**`, `docs/ja-JP/**`, `docs/es/**`, `docs/pt-BR/**`, `docs/ko/**`, `docs/de/**`, `docs/fr/**`, `docs/hi/**`, `docs/ar/**`, `docs/it/**`, `docs/vi/**`, `docs/nl/**`, `docs/fa/**`, `docs/ru/**`, `docs/tr/**`, `docs/uk/**`, `docs/id/**`, `docs/pl/**`, or `docs/th/**`.
|
||||
|
||||
## End-to-end flow
|
||||
|
||||
@@ -32,10 +32,10 @@ Generated locale trees and live translation memory now live in the publish repo:
|
||||
|
||||
## Locale visibility
|
||||
|
||||
- Control UI supports `en`, `zh-CN`, `zh-TW`, `pt-BR`, `de`, `es`, `ja-JP`, `ko`, `fr`, `ar`, `it`, `tr`, `uk`, `id`, `pl`, `th`, `vi`, `nl`, and `fa`.
|
||||
- Control UI supports `en`, `zh-CN`, `zh-TW`, `pt-BR`, `de`, `es`, `ja-JP`, `ko`, `fr`, `hi`, `ar`, `it`, `vi`, `nl`, `fa`, `ru`, `tr`, `uk`, `id`, `pl`, and `th`.
|
||||
- Docs translation workflows generate the same non-English locale set in `openclaw/docs`.
|
||||
- The Mintlify docs language picker can expose only the locales accepted by Mintlify `navigation.languages`; today that includes Vietnamese (`vi`) and Dutch (`nl`), but not Thai (`th`) or Persian (`fa`).
|
||||
- Do not treat missing `th` or `fa` entries in generated `docs/docs.json` as a pipeline failure. Verify their generated folders in `openclaw/docs` instead.
|
||||
- The Mintlify docs language picker can expose only the locales accepted by Mintlify `navigation.languages`; Russian (`ru`) and Hindi (`hi`) are now included in the publish configuration.
|
||||
- Do not treat locale visibility in generated `docs/docs.json` as proof that translation artifacts exist. Verify each generated locale folder and its translation memory in `openclaw/docs`.
|
||||
|
||||
## Files in this folder
|
||||
|
||||
|
||||
82
docs/.i18n/glossary.hi.json
Normal file
82
docs/.i18n/glossary.hi.json
Normal file
@@ -0,0 +1,82 @@
|
||||
[
|
||||
{
|
||||
"source": "ACP",
|
||||
"target": "ACP"
|
||||
},
|
||||
{
|
||||
"source": "Active Memory",
|
||||
"target": "Active Memory"
|
||||
},
|
||||
{
|
||||
"source": "ClawHub",
|
||||
"target": "ClawHub"
|
||||
},
|
||||
{
|
||||
"source": "CLI",
|
||||
"target": "CLI"
|
||||
},
|
||||
{
|
||||
"source": "Compaction",
|
||||
"target": "Compaction"
|
||||
},
|
||||
{
|
||||
"source": "Cron",
|
||||
"target": "Cron"
|
||||
},
|
||||
{
|
||||
"source": "Dreaming",
|
||||
"target": "Dreaming"
|
||||
},
|
||||
{
|
||||
"source": "Gateway",
|
||||
"target": "Gateway"
|
||||
},
|
||||
{
|
||||
"source": "Heartbeat",
|
||||
"target": "Heartbeat"
|
||||
},
|
||||
{
|
||||
"source": "LINE",
|
||||
"target": "LINE"
|
||||
},
|
||||
{
|
||||
"source": "Mintlify",
|
||||
"target": "Mintlify"
|
||||
},
|
||||
{
|
||||
"source": "Node",
|
||||
"target": "Node"
|
||||
},
|
||||
{
|
||||
"source": "OpenClaw",
|
||||
"target": "OpenClaw"
|
||||
},
|
||||
{
|
||||
"source": "Pi",
|
||||
"target": "Pi"
|
||||
},
|
||||
{
|
||||
"source": "Plugin",
|
||||
"target": "Plugin"
|
||||
},
|
||||
{
|
||||
"source": "Skills",
|
||||
"target": "Skills"
|
||||
},
|
||||
{
|
||||
"source": "Tailscale",
|
||||
"target": "Tailscale"
|
||||
},
|
||||
{
|
||||
"source": "TaskFlow",
|
||||
"target": "TaskFlow"
|
||||
},
|
||||
{
|
||||
"source": "TUI",
|
||||
"target": "TUI"
|
||||
},
|
||||
{
|
||||
"source": "Webhook",
|
||||
"target": "Webhook"
|
||||
}
|
||||
]
|
||||
82
docs/.i18n/glossary.ru.json
Normal file
82
docs/.i18n/glossary.ru.json
Normal file
@@ -0,0 +1,82 @@
|
||||
[
|
||||
{
|
||||
"source": "ACP",
|
||||
"target": "ACP"
|
||||
},
|
||||
{
|
||||
"source": "Active Memory",
|
||||
"target": "Active Memory"
|
||||
},
|
||||
{
|
||||
"source": "ClawHub",
|
||||
"target": "ClawHub"
|
||||
},
|
||||
{
|
||||
"source": "CLI",
|
||||
"target": "CLI"
|
||||
},
|
||||
{
|
||||
"source": "Compaction",
|
||||
"target": "Compaction"
|
||||
},
|
||||
{
|
||||
"source": "Cron",
|
||||
"target": "Cron"
|
||||
},
|
||||
{
|
||||
"source": "Dreaming",
|
||||
"target": "Dreaming"
|
||||
},
|
||||
{
|
||||
"source": "Gateway",
|
||||
"target": "Gateway"
|
||||
},
|
||||
{
|
||||
"source": "Heartbeat",
|
||||
"target": "Heartbeat"
|
||||
},
|
||||
{
|
||||
"source": "LINE",
|
||||
"target": "LINE"
|
||||
},
|
||||
{
|
||||
"source": "Mintlify",
|
||||
"target": "Mintlify"
|
||||
},
|
||||
{
|
||||
"source": "Node",
|
||||
"target": "Node"
|
||||
},
|
||||
{
|
||||
"source": "OpenClaw",
|
||||
"target": "OpenClaw"
|
||||
},
|
||||
{
|
||||
"source": "Pi",
|
||||
"target": "Pi"
|
||||
},
|
||||
{
|
||||
"source": "Plugin",
|
||||
"target": "Plugin"
|
||||
},
|
||||
{
|
||||
"source": "Skills",
|
||||
"target": "Skills"
|
||||
},
|
||||
{
|
||||
"source": "Tailscale",
|
||||
"target": "Tailscale"
|
||||
},
|
||||
{
|
||||
"source": "TaskFlow",
|
||||
"target": "TaskFlow"
|
||||
},
|
||||
{
|
||||
"source": "TUI",
|
||||
"target": "TUI"
|
||||
},
|
||||
{
|
||||
"source": "Webhook",
|
||||
"target": "Webhook"
|
||||
}
|
||||
]
|
||||
@@ -579,7 +579,7 @@ When `imsg launch` is running and `openclaw channels status --probe` reports `pr
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Read receipts and typing">
|
||||
When the private API bridge is up, accepted inbound chats are marked read before dispatch and a typing bubble is shown to the sender while the agent generates. Disable read-marking with:
|
||||
When the private API bridge is up, accepted inbound chats are marked read and direct chats show a typing bubble as soon as the turn is accepted, while the agent prepares context and generates. Disable read-marking with:
|
||||
|
||||
```json5
|
||||
{
|
||||
|
||||
18
docs/ci.md
18
docs/ci.md
@@ -30,7 +30,7 @@ or an explicit manual dispatch.
|
||||
| `security-fast` | Private key detection, changed-workflow audit via `zizmor`, and production lockfile audit | Always on non-draft pushes and PRs |
|
||||
| `check-dependencies` | Production Knip dependency-only pass plus the unused-file allowlist guard | Node-relevant changes |
|
||||
| `build-artifacts` | Build `dist/`, Control UI, built-CLI smoke checks, embedded built-artifact checks, and reusable artifacts | Node-relevant changes |
|
||||
| `checks-fast-core` | Fast Linux correctness lanes such as bundled, protocol, and CI-routing checks | Node-relevant changes |
|
||||
| `checks-fast-core` | Fast Linux correctness lanes such as bundled, protocol, QA Smoke CI, and CI-routing checks | Node-relevant changes |
|
||||
| `checks-fast-contracts-plugins-*` | Two sharded plugin contract checks | Node-relevant changes |
|
||||
| `checks-fast-contracts-channels-*` | Two sharded channel contract checks | Node-relevant changes |
|
||||
| `checks-node-core-*` | Core Node test shards, excluding channel, bundled, contract, and extension lanes | Node-relevant changes |
|
||||
@@ -90,9 +90,9 @@ Scope logic lives in `scripts/ci-changed-scope.mjs` and is covered by unit tests
|
||||
|
||||
The slowest Node test families are split or balanced so each job stays small without over-reserving runners: plugin contracts and channel contracts each run as two weighted Blacksmith-backed shards with the standard GitHub runner fallback, core unit fast/support lanes run separately, core runtime infra is split between state, process/config, shared, and three cron domain shards, auto-reply runs as balanced workers (with the reply subtree split into agent-runner, dispatch, and commands/state-routing shards), and agentic gateway/server configs are split across chat/auth/model/http-plugin/runtime/startup lanes instead of waiting on built artifacts. Normal CI then packs only isolated infra include-pattern shards into deterministic bundles of at most 64 test files, reducing the Node matrix without merging non-isolated command/cron, stateful agents-core, or gateway/server suites; heavy fixed suites stay on 8 vCPU while the bundled and lower-weight lanes use 4 vCPU. Pull requests on the canonical repository use an additional compact admission plan: the same per-config groups run in isolated subprocesses inside the current 34-job Linux Node plan, so a single PR does not register the full 70-plus-job Node matrix. `main` pushes, manual dispatches, and release gates retain the full matrix. Broad browser, QA, media, and miscellaneous plugin tests use their dedicated Vitest configs instead of the shared plugin catch-all. Include-pattern shards record timing entries using the CI shard name, so `.artifacts/vitest-shard-timings.json` can distinguish a whole config from a filtered shard. `check-additional-*` keeps package-boundary compile/canary work together and separates runtime topology architecture from gateway watch coverage; the boundary guard list is striped into one prompt-heavy shard and one combined shard for the remaining guard stripes, each running selected independent guards concurrently and printing per-check timings. The expensive Codex happy-path prompt snapshot drift check runs as its own additional job for manual CI and for prompt-affecting changes only, so normal unrelated Node changes do not wait behind cold prompt snapshot generation and the boundary shards stay balanced while prompt drift is still pinned to the PR that caused it; the same flag skips prompt snapshot Vitest generation inside the built-artifact core support-boundary shard. Gateway watch, channel tests, and the core support-boundary shard run concurrently inside `build-artifacts` after `dist/` and `dist-runtime/` are already built.
|
||||
|
||||
Once admitted, canonical Linux CI permits up to 12 concurrent Node jobs and 8 for
|
||||
the smaller fast/check lanes; Windows and Android stay at two because those
|
||||
runner pools are narrower.
|
||||
Once admitted, canonical Linux CI permits up to 24 concurrent Node test jobs and
|
||||
12 for the smaller fast/check lanes; Windows and Android stay at two because
|
||||
those runner pools are narrower.
|
||||
|
||||
The compact PR plan emits 18 Node jobs for the current suite: whole-config
|
||||
groups are batched in isolated subprocesses with a 120-minute batch timeout,
|
||||
@@ -145,17 +145,17 @@ gh workflow run full-release-validation.yml --ref main -f ref=<branch-or-sha>
|
||||
|
||||
## Runner registration budget
|
||||
|
||||
GitHub caps self-hosted runner registrations at 1,500 runners per 5 minutes per
|
||||
repository, organization, or enterprise. The limit is shared by all Blacksmith
|
||||
runner registrations in the `openclaw` organization, so adding another
|
||||
Blacksmith installation does not add a new bucket.
|
||||
OpenClaw's current GitHub runner-registration bucket allows 3,000 self-hosted
|
||||
runner registrations per 5 minutes. The limit is shared by all Blacksmith runner
|
||||
registrations in the `openclaw` organization, so adding another Blacksmith
|
||||
installation does not add a new bucket.
|
||||
|
||||
Treat Blacksmith labels as the scarce resource for burst control. Jobs that
|
||||
only route, notify, summarize, select shards, or run short CodeQL scans should
|
||||
stay on GitHub-hosted runners unless they have measured Blacksmith-specific
|
||||
needs. Any new Blacksmith matrix, larger `max-parallel`, or high-frequency
|
||||
workflow must show its worst-case registration count and keep the org-level
|
||||
target below 1,000 registrations per 5 minutes, leaving headroom for concurrent
|
||||
target below 2,000 registrations per 5 minutes, leaving headroom for concurrent
|
||||
repositories and retried jobs.
|
||||
|
||||
Canonical-repo CI keeps Blacksmith as the default runner path for normal push and pull-request runs. `workflow_dispatch` and non-canonical repository runs use GitHub-hosted runners, but normal canonical runs do not currently probe Blacksmith queue health or automatically fall back to GitHub-hosted labels when Blacksmith is unavailable.
|
||||
|
||||
@@ -24,17 +24,31 @@ OpenClaw agent or Gateway.
|
||||
```bash
|
||||
openclaw skills search "calendar"
|
||||
openclaw skills install @owner/<slug>
|
||||
openclaw skills install @owner/<slug> --acknowledge-clawhub-risk
|
||||
openclaw skills update @owner/<slug>
|
||||
openclaw skills update @owner/<slug> --acknowledge-clawhub-risk
|
||||
openclaw skills verify @owner/<slug>
|
||||
|
||||
openclaw plugins search "calendar"
|
||||
openclaw plugins install clawhub:<package>
|
||||
openclaw plugins install clawhub:<package> --acknowledge-clawhub-risk
|
||||
openclaw plugins update <id-or-npm-spec>
|
||||
```
|
||||
|
||||
Skill installs target the active workspace `skills/` directory by default. Add
|
||||
`--global` to install into the shared managed skills directory.
|
||||
|
||||
OpenClaw checks the selected community ClawHub skill or plugin trust state
|
||||
before downloading it. Versioned community skill and plugin releases use
|
||||
exact-release trust metadata; resolver-backed GitHub skills rely on ClawHub's
|
||||
install resolver to enforce scan and force-install policy before it returns a
|
||||
pinned commit. Malicious or blocked community releases are refused. Risky
|
||||
community releases require review and `--acknowledge-clawhub-risk` when a
|
||||
non-interactive command should continue after that review.
|
||||
|
||||
Official ClawHub publishers/packages and bundled OpenClaw sources bypass this
|
||||
release-trust prompt and security-verdict fetch during install and update.
|
||||
|
||||
Plugin installs use the `clawhub:` prefix when you want ClawHub resolution
|
||||
instead of npm or another install source.
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ openclaw doctor
|
||||
openclaw doctor --lint
|
||||
openclaw doctor --lint --json
|
||||
openclaw doctor --lint --severity-min warning
|
||||
openclaw doctor --lint --all
|
||||
openclaw doctor --lint --allow-exec
|
||||
openclaw doctor --deep
|
||||
openclaw doctor --fix
|
||||
@@ -73,6 +74,7 @@ The targeted Discord capabilities probe reports the bot's effective channel perm
|
||||
- `--post-upgrade`: run post-upgrade plugin compatibility probes; emits findings to stdout; exits with code 1 if any error-level findings are present
|
||||
- `--json`: with `--lint`, emit JSON findings instead of human output; with `--post-upgrade`, emit a machine-readable JSON envelope (`{ probesRun, findings }`)
|
||||
- `--severity-min <level>`: with `--lint`, drop findings below `info`, `warning`, or `error`
|
||||
- `--all`: with `--lint`, run all registered checks, including opt-in checks excluded from the default automation set
|
||||
- `--skip <id>`: with `--lint`, skip a check id; repeat to skip more than one
|
||||
- `--only <id>`: with `--lint`, run only a check id; repeat to run a small selected set
|
||||
|
||||
@@ -82,13 +84,14 @@ The targeted Discord capabilities probe reports the bot's effective channel perm
|
||||
It uses the structured health-check path, does not prompt, and does not repair
|
||||
or rewrite config/state. Use it in CI, preflight scripts, and review workflows
|
||||
when you want machine-readable findings instead of guided repair prompts.
|
||||
Lint-output options such as `--json`, `--severity-min`, `--only`, and `--skip`
|
||||
Lint-output options such as `--json`, `--severity-min`, `--all`, `--only`, and `--skip`
|
||||
are only accepted with `--lint`.
|
||||
|
||||
```bash
|
||||
openclaw doctor --lint
|
||||
openclaw doctor --lint --severity-min warning
|
||||
openclaw doctor --lint --json
|
||||
openclaw doctor --lint --all
|
||||
openclaw doctor --lint --allow-exec
|
||||
openclaw doctor --lint --only core/doctor/gateway-config --json
|
||||
```
|
||||
@@ -130,6 +133,13 @@ Exit behavior:
|
||||
example, `openclaw doctor --lint --severity-min error` can print no findings and
|
||||
exit `0` even when lower-severity `info` or `warning` findings exist.
|
||||
|
||||
`--all` controls which checks are selected before severity filtering. The
|
||||
default lint run is the stable automation gate and excludes checks that are
|
||||
intentionally opt-in because they are deep, historical, or more likely to
|
||||
surface repairable legacy residue. Use `--all` when you want the complete lint
|
||||
inventory without listing each check id. `--only <id>` remains the most precise
|
||||
selector and can run any registered check by id.
|
||||
|
||||
## Structured Health Checks
|
||||
|
||||
Modern doctor checks use a small structured contract:
|
||||
@@ -186,6 +196,7 @@ Use `--only` and `--skip` when a workflow wants a focused gate:
|
||||
```bash
|
||||
openclaw doctor --lint --only core/doctor/gateway-config --json
|
||||
openclaw doctor --lint --skip core/doctor/skills-readiness
|
||||
openclaw doctor --lint --all --skip core/doctor/session-locks
|
||||
```
|
||||
|
||||
`--only` and `--skip` accept full check ids and may be repeated. If an `--only`
|
||||
|
||||
@@ -54,8 +54,9 @@ openclaw plugins update <id-or-npm-spec>
|
||||
openclaw plugins update --all
|
||||
openclaw plugins marketplace list <marketplace>
|
||||
openclaw plugins marketplace list <marketplace> --json
|
||||
openclaw plugins init <id>
|
||||
openclaw plugins init <id> --directory ./my-plugin --name "My Plugin"
|
||||
openclaw plugins init my-tool --name "My Tool"
|
||||
openclaw plugins init my-provider --name "My Provider" --type provider
|
||||
openclaw plugins init my-provider --name "My Provider" --type provider --directory ./my-provider
|
||||
openclaw plugins build --entry ./dist/index.js
|
||||
openclaw plugins build --entry ./dist/index.js --check
|
||||
openclaw plugins validate --entry ./dist/index.js
|
||||
@@ -86,12 +87,15 @@ npm run plugin:build
|
||||
npm run plugin:validate
|
||||
```
|
||||
|
||||
`plugins init` creates a minimal TypeScript tool plugin that uses
|
||||
`defineToolPlugin`. `plugins build` imports that entry, reads its static tool
|
||||
metadata, writes `openclaw.plugin.json`, and keeps `package.json`
|
||||
`openclaw.extensions` aligned. `plugins validate` checks that the generated
|
||||
manifest, package metadata, and current entry export still agree. See
|
||||
[Tool Plugins](/plugins/tool-plugins) for the full authoring workflow.
|
||||
`plugins init` creates a minimal TypeScript tool plugin by default. The first
|
||||
argument is the plugin id; pass `--name` for the display name. OpenClaw uses the
|
||||
id for the default output directory and package naming. Tool scaffolds use
|
||||
`defineToolPlugin`.
|
||||
`plugins build` imports the built entry, reads its static tool metadata, writes
|
||||
`openclaw.plugin.json`, and keeps `package.json` `openclaw.extensions` aligned.
|
||||
`plugins validate` checks that the generated manifest, package metadata, and
|
||||
current entry export still agree. See [Tool Plugins](/plugins/tool-plugins) for
|
||||
the full tool-authoring workflow.
|
||||
|
||||
The scaffold writes TypeScript source but generates metadata from the built
|
||||
`./dist/index.js` entry so the workflow also works with the published CLI. Use
|
||||
@@ -99,6 +103,29 @@ The scaffold writes TypeScript source but generates metadata from the built
|
||||
`plugins build --check` in CI to fail when generated metadata is stale without
|
||||
rewriting files.
|
||||
|
||||
### Provider Scaffold
|
||||
|
||||
```bash
|
||||
openclaw plugins init acme-models --name "Acme Models" --type provider
|
||||
cd acme-models
|
||||
npm install
|
||||
npm run build
|
||||
npm test
|
||||
npm run validate
|
||||
```
|
||||
|
||||
Provider scaffolds create a generic text/model provider plugin with OpenAI-compatible
|
||||
API-key plumbing, a built-in `npm run validate` script for `clawhub package
|
||||
validate`, ClawHub package metadata, and a manually dispatched GitHub workflow
|
||||
for future trusted publishing through GitHub Actions OIDC. Provider scaffolds do
|
||||
not generate skills and do not use `openclaw plugins build` or
|
||||
`openclaw plugins validate`; those commands are for the tool scaffold's
|
||||
generated metadata path.
|
||||
|
||||
Before publishing, replace the placeholder API base URL, model catalog, docs
|
||||
route, credential text, and README copy with real provider details. Use the
|
||||
generated README for first-time ClawHub publishing and trusted publisher setup.
|
||||
|
||||
### Install
|
||||
|
||||
```bash
|
||||
@@ -111,6 +138,7 @@ openclaw plugins install git:github.com/<owner>/<repo> # git repo
|
||||
openclaw plugins install git:github.com/<owner>/<repo>@<ref>
|
||||
openclaw plugins install <package> --force # overwrite existing install
|
||||
openclaw plugins install <package> --pin # pin version
|
||||
openclaw plugins install clawhub:<package> --acknowledge-clawhub-risk
|
||||
openclaw plugins install <package> --dangerously-force-unsafe-install
|
||||
openclaw plugins install <path> # local path
|
||||
openclaw plugins install <plugin>@<marketplace> # marketplace
|
||||
@@ -163,6 +191,12 @@ is available, then fall back to `latest`.
|
||||
|
||||
If a plugin you published on ClawHub is hidden or blocked by a registry scan, use the publisher steps in [ClawHub publishing](/clawhub/publishing). `--dangerously-force-unsafe-install` does not ask ClawHub to rescan the plugin or make a blocked release public.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="--acknowledge-clawhub-risk">
|
||||
Community ClawHub installs check the selected release trust record before downloading the package. If ClawHub disables download for the release, reports malicious scan findings, or puts the release in a blocking moderation state such as quarantine, OpenClaw refuses the release. For non-blocking risky scan statuses, risky moderation states, or registry reasons, OpenClaw shows the trust details and asks for confirmation before continuing.
|
||||
|
||||
Use `--acknowledge-clawhub-risk` only after reviewing the ClawHub warning and deciding to continue without an interactive prompt. Pending or stale clean trust records warn but do not require acknowledgement. Official ClawHub packages and bundled OpenClaw plugin sources bypass this release-trust prompt.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Hook packs and npm specs">
|
||||
`plugins install` is also the install surface for hook packs that expose `openclaw.hooks` in `package.json`. Use `openclaw hooks` for filtered hook visibility and per-hook enablement, not package installation.
|
||||
@@ -390,6 +424,7 @@ openclaw plugins update <id-or-npm-spec>
|
||||
openclaw plugins update --all
|
||||
openclaw plugins update <id-or-npm-spec> --dry-run
|
||||
openclaw plugins update @openclaw/voice-call
|
||||
openclaw plugins update openclaw-codex-app-server --acknowledge-clawhub-risk
|
||||
openclaw plugins update openclaw-codex-app-server --dangerously-force-unsafe-install
|
||||
```
|
||||
|
||||
@@ -399,13 +434,17 @@ Updates apply to tracked plugin installs in the managed plugin index and tracked
|
||||
<Accordion title="Resolving plugin id vs npm spec">
|
||||
When you pass a plugin id, OpenClaw reuses the recorded install spec for that plugin. That means previously stored dist-tags such as `@beta` and exact pinned versions continue to be used on later `update <id>` runs.
|
||||
|
||||
That targeted-update rule is different from the bulk `openclaw plugins update --all` maintenance path. Bulk updates still respect ordinary tracked install specs, but trusted official OpenClaw plugin records can sync to the current official catalog target instead of staying on a stale exact official package. Use targeted `update <id>` when you intentionally want to keep an exact or tagged official spec untouched.
|
||||
|
||||
For npm installs, you can also pass an explicit npm package spec with a dist-tag or exact version. OpenClaw resolves that package name back to the tracked plugin record, updates that installed plugin, and records the new npm spec for future id-based updates.
|
||||
|
||||
Passing the npm package name without a version or tag also resolves back to the tracked plugin record. Use this when a plugin was pinned to an exact version and you want to move it back to the registry's default release line.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Beta channel updates">
|
||||
`openclaw plugins update` reuses the tracked plugin spec unless you pass a new spec. `openclaw update` additionally knows the active OpenClaw update channel: on the beta channel, default-line npm and ClawHub plugin records try `@beta` first. They fall back to the recorded default/latest spec if no plugin beta release exists; npm plugins also fall back when the beta package exists but fails install validation. That fallback is reported as a warning and does not fail the core update. Exact versions and explicit tags stay pinned to that selector.
|
||||
Targeted `openclaw plugins update <id-or-npm-spec>` reuses the tracked plugin spec unless you pass a new spec. Bulk `openclaw plugins update --all` uses the configured `update.channel` when it syncs trusted official plugin records to the official catalog target, so beta-channel installs can stay on the beta release line instead of being silently normalized to stable/latest.
|
||||
|
||||
`openclaw update` also knows the active OpenClaw update channel: on the beta channel, default-line npm and ClawHub plugin records try `@beta` first. They fall back to the recorded default/latest spec if no plugin beta release exists; npm plugins also fall back when the beta package exists but fails install validation. That fallback is reported as a warning and does not fail the core update. Exact versions and explicit tags stay pinned to that selector for targeted updates.
|
||||
|
||||
</Accordion>
|
||||
<Accordion title="Version checks and integrity drift">
|
||||
@@ -417,6 +456,9 @@ Updates apply to tracked plugin installs in the managed plugin index and tracked
|
||||
<Accordion title="--dangerously-force-unsafe-install on update">
|
||||
`--dangerously-force-unsafe-install` is also accepted on `plugins update` for compatibility, but it is deprecated and no longer changes plugin update behavior. Operator `security.installPolicy` can still block updates; plugin `before_install` hooks only apply in processes where plugin hooks are loaded.
|
||||
</Accordion>
|
||||
<Accordion title="--acknowledge-clawhub-risk on update">
|
||||
Community ClawHub-backed plugin updates run the same exact-release trust check as installs before downloading the replacement package. Use `--acknowledge-clawhub-risk` for reviewed automation that should continue when the selected ClawHub release has a risky trust warning. Official ClawHub packages and bundled OpenClaw plugin sources bypass this release-trust prompt.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
### Inspect
|
||||
|
||||
@@ -120,6 +120,7 @@ openclaw sessions cleanup --json
|
||||
|
||||
- Scope note: `openclaw sessions cleanup` maintains session stores, transcripts, and trajectory sidecars. It does not prune cron run history, which is managed by `cron.runLog.keepLines` in [Cron configuration](/automation/cron-jobs#configuration) and explained in [Cron maintenance](/automation/cron-jobs#maintenance).
|
||||
- Cleanup also prunes unreferenced primary transcripts, compaction checkpoints, and trajectory sidecars older than `session.maintenance.pruneAfter`; files still referenced by `sessions.json` are preserved.
|
||||
- Cleanup reports short-lived gateway model-run probe cleanup separately as `modelRunPruned`. This only matches strict explicit keys shaped like `agent:*:explicit:model-run-<uuid>`. The fixed retention is `24h`, but it is pressure-gated: it only removes stale probe rows when session-entry maintenance/cap pressure is reached. When it runs, model-run cleanup happens before global stale cleanup and capping.
|
||||
|
||||
- `--dry-run`: preview how many entries would be pruned/capped without writing.
|
||||
- In text mode, dry-run prints a per-session action table (`Action`, `Key`, `Age`, `Model`, `Flags`) plus a summary grouped by session label so you can see what would be kept vs removed.
|
||||
|
||||
@@ -31,9 +31,11 @@ openclaw skills install git:owner/repo
|
||||
openclaw skills install git:owner/repo@main
|
||||
openclaw skills install ./path/to/skill --as custom-name
|
||||
openclaw skills install @owner/<slug> --force
|
||||
openclaw skills install @owner/<slug> --acknowledge-clawhub-risk
|
||||
openclaw skills install @owner/<slug> --agent <id>
|
||||
openclaw skills install @owner/<slug> --global
|
||||
openclaw skills update @owner/<slug>
|
||||
openclaw skills update @owner/<slug> --acknowledge-clawhub-risk
|
||||
openclaw skills update @owner/<slug> --global
|
||||
openclaw skills update --all
|
||||
openclaw skills update --all --agent <id>
|
||||
@@ -97,6 +99,14 @@ Notes:
|
||||
- `install --version <version>` applies only to ClawHub skill refs.
|
||||
- `install --force` overwrites an existing workspace skill folder for the same
|
||||
slug.
|
||||
- Community ClawHub skill installs and updates check trust before downloading.
|
||||
Versioned community archive releases use exact-release trust metadata.
|
||||
Resolver-backed GitHub skills rely on ClawHub's install resolver to enforce
|
||||
scan and force-install policy before it returns a pinned commit. Malicious or
|
||||
blocked community releases are refused. Risky community releases require
|
||||
review and `--acknowledge-clawhub-risk` when a non-interactive command should
|
||||
continue after that review. Official ClawHub skill publishers and bundled
|
||||
OpenClaw skill sources bypass this release-trust prompt.
|
||||
- `--global` targets the shared managed skills directory and cannot be combined
|
||||
with `--agent <id>`.
|
||||
- `--agent <id>` targets one configured agent workspace and overrides current
|
||||
|
||||
@@ -28,6 +28,7 @@ openclaw update --tag main
|
||||
openclaw update --dry-run
|
||||
openclaw update --no-restart
|
||||
openclaw update --yes
|
||||
openclaw update --acknowledge-clawhub-risk
|
||||
openclaw update --json
|
||||
openclaw --update
|
||||
```
|
||||
@@ -45,6 +46,11 @@ openclaw --update
|
||||
when npm plugin artifact drift is detected during post-update plugin sync.
|
||||
- `--timeout <seconds>`: per-step timeout (default is 1800s).
|
||||
- `--yes`: skip confirmation prompts (for example downgrade confirmation).
|
||||
- `--acknowledge-clawhub-risk`: after reviewing community ClawHub trust
|
||||
warnings, allow post-update plugin sync to continue without an interactive
|
||||
prompt. Without this, risky community ClawHub plugin releases are skipped and
|
||||
left unchanged when OpenClaw cannot prompt. Official ClawHub packages and
|
||||
bundled OpenClaw plugin sources bypass this release-trust prompt.
|
||||
|
||||
`openclaw update` does not have a `--verbose` flag. Use `--dry-run` to preview
|
||||
the planned channel/tag/install/restart actions, `--json` for machine-readable
|
||||
@@ -88,6 +94,7 @@ converge.
|
||||
```bash
|
||||
openclaw update repair
|
||||
openclaw update repair --channel beta
|
||||
openclaw update repair --acknowledge-clawhub-risk
|
||||
openclaw update repair --json
|
||||
```
|
||||
|
||||
@@ -98,6 +105,10 @@ Options:
|
||||
- `--json`: print machine-readable finalization JSON.
|
||||
- `--timeout <seconds>`: timeout for repair steps (default `1800`).
|
||||
- `--yes`: skip confirmation prompts.
|
||||
- `--acknowledge-clawhub-risk`: after reviewing community ClawHub trust
|
||||
warnings, allow repair-time plugin convergence to continue without an
|
||||
interactive prompt. Official ClawHub packages and bundled OpenClaw plugin
|
||||
sources bypass this release-trust prompt.
|
||||
- `--no-restart`: accepted for update command parity; repair never restarts the
|
||||
Gateway.
|
||||
|
||||
|
||||
@@ -167,7 +167,7 @@ surfaces, while Codex native hooks remain a separate lower-level Codex mechanism
|
||||
- Agent runtime: `agents.defaults.timeoutSeconds` default 172800s (48 hours); enforced in `runEmbeddedAgent` abort timer.
|
||||
- Cron runtime: isolated agent-turn `timeoutSeconds` is owned by cron. The scheduler starts that timer when execution begins, aborts the underlying run at the configured deadline, then runs bounded cleanup before recording the timeout so a stale child session cannot keep the lane stuck.
|
||||
- Session liveness diagnostics: with diagnostics enabled, `diagnostics.stuckSessionWarnMs` classifies long `processing` sessions that have no observed reply, tool, status, block, or ACP progress. Active embedded runs, model calls, and tool calls report as `session.long_running`; owned silent model calls also stay `session.long_running` until `diagnostics.stuckSessionAbortMs` so slow or non-streaming providers are not reported as stalled too early. Active work with no recent progress reports as `session.stalled`; owned model calls switch to `session.stalled` at or after the abort threshold, and ownerless stale model/tool activity is not hidden as long-running. `session.stuck` is reserved for recoverable stale session bookkeeping, including idle queued sessions with stale ownerless model/tool activity. Stale session bookkeeping releases the affected session lane immediately after recovery gates pass; stalled embedded runs are abort-drained only after `diagnostics.stuckSessionAbortMs` (default: at least 5 minutes and 3x the warning threshold) so queued work can resume without cutting off merely slow runs. Recovery emits structured requested/completed outcomes, and diagnostic state is marked idle only if the same processing generation is still current. Repeated `session.stuck` diagnostics back off while the session remains unchanged.
|
||||
- Model idle timeout: OpenClaw aborts a model request when no response chunks arrive before the idle window. `models.providers.<id>.timeoutSeconds` extends this idle watchdog for slow local/self-hosted providers, but it is still bounded by any lower `agents.defaults.timeoutSeconds` or run-specific timeout because those control the whole agent run. Otherwise OpenClaw uses `agents.defaults.timeoutSeconds` when configured, capped at 120s by default. Cron-triggered cloud model runs with no explicit model or agent timeout use the same default idle watchdog; cron-triggered local or self-hosted model runs disable the implicit watchdog unless an explicit timeout is configured, so slow local providers should set `models.providers.<id>.timeoutSeconds`.
|
||||
- Model idle timeout: OpenClaw aborts a model request when no response chunks arrive before the idle window. `models.providers.<id>.timeoutSeconds` extends this idle watchdog for slow local/self-hosted providers, but it is still bounded by any lower `agents.defaults.timeoutSeconds` or run-specific timeout because those control the whole agent run. Otherwise OpenClaw uses `agents.defaults.timeoutSeconds` when configured, capped at 120s by default. Cron-triggered cloud model runs with no explicit model or agent timeout use the same default idle watchdog; with an explicit cron run timeout, cloud model stream stalls are capped at 60s so configured model fallbacks can run before the outer cron deadline. Cron-triggered local or self-hosted model runs disable the implicit watchdog unless an explicit timeout is configured, and explicit cron run timeouts remain the idle window for local/self-hosted providers, so slow local providers should set `models.providers.<id>.timeoutSeconds`.
|
||||
- Provider HTTP request timeout: `models.providers.<id>.timeoutSeconds` applies to that provider's model HTTP fetches, including connect, headers, body, SDK request timeout, total guarded-fetch abort handling, and model stream idle watchdog. Use this for slow local/self-hosted providers such as Ollama before raising the whole agent runtime timeout, and keep the agent/runtime timeout at least as high when the model request needs to run longer.
|
||||
|
||||
## Where things can end early
|
||||
|
||||
@@ -127,6 +127,14 @@ in `enforce` mode and applies cleanup during maintenance. Set
|
||||
|
||||
For production-sized `maxEntries` limits, Gateway runtime writes use a small high-water buffer and clean back down to the configured cap in batches. Session store reads do not prune or cap entries during Gateway startup. This avoids running full store cleanup on every startup or isolated cron session. `openclaw sessions cleanup --enforce` applies the cap immediately.
|
||||
|
||||
Gateway model-run probe sessions are short-lived by default. Matching rows with
|
||||
strict explicit keys like `agent:*:explicit:model-run-<uuid>` use fixed `24h`
|
||||
retention, but cleanup is pressure-gated: it only removes stale probe rows when
|
||||
session-entry maintenance/cap pressure is reached. When model-run cleanup runs,
|
||||
it runs before the broader stale-entry age cutoff and entry cap. Normal direct,
|
||||
group, thread, cron, hook, heartbeat, ACP, and sub-agent sessions do not inherit
|
||||
this 24h retention.
|
||||
|
||||
Maintenance preserves durable external conversation pointers, including group
|
||||
sessions and thread-scoped chat sessions, while still allowing synthetic cron,
|
||||
hook, heartbeat, ACP, and sub-agent entries to age out.
|
||||
|
||||
@@ -15,7 +15,8 @@ When `agents.defaults.typingMode` is **unset**, OpenClaw keeps the legacy behavi
|
||||
|
||||
- **Direct chats**: typing starts immediately once the model loop begins.
|
||||
- **Group chats with a mention**: typing starts immediately.
|
||||
- **Group chats without a mention**: typing starts only when message text begins streaming.
|
||||
- **Group chats without a mention**: typing starts when the admitted run has
|
||||
user-visible activity, such as harness execution activity or message text.
|
||||
- **Heartbeat runs**: typing starts when the heartbeat run begins if the
|
||||
resolved heartbeat target is a typing-capable chat and typing is not disabled.
|
||||
|
||||
@@ -26,13 +27,14 @@ Set `agents.defaults.typingMode` to one of:
|
||||
- `never` - no typing indicator, ever.
|
||||
- `instant` - start typing **as soon as the model loop begins**, even if the run
|
||||
later returns only the silent reply token.
|
||||
- `thinking` - start typing on the **first reasoning delta** (requires
|
||||
`reasoningLevel: "stream"` for the run).
|
||||
- `message` - start typing on the **first non-silent text delta** (ignores
|
||||
the `NO_REPLY` silent token).
|
||||
- `thinking` - start typing on the **first reasoning delta** or on active
|
||||
harness execution after the turn is accepted.
|
||||
- `message` - start typing on the **first user-visible reply activity**, such as
|
||||
active harness execution or a non-silent text delta. Silent reply tokens such
|
||||
as `NO_REPLY` do not count as text activity.
|
||||
|
||||
Order of "how early it fires":
|
||||
`never` → `message` → `thinking` → `instant`
|
||||
`never` → `message`/`thinking` → `instant`
|
||||
|
||||
## Configuration
|
||||
|
||||
@@ -62,11 +64,10 @@ Override mode or cadence per session:
|
||||
|
||||
## Notes
|
||||
|
||||
- `message` mode won't show typing for silent-only replies when the whole
|
||||
payload is the exact silent token (for example `NO_REPLY` / `no_reply`,
|
||||
matched case-insensitively).
|
||||
- `thinking` only fires if the run streams reasoning (`reasoningLevel: "stream"`).
|
||||
If the model doesn't emit reasoning deltas, typing won't start.
|
||||
- `message` mode does not start from silent reply tokens, but active execution
|
||||
can still show typing before any assistant text is available.
|
||||
- `thinking` still reacts to streamed reasoning (`reasoningLevel: "stream"`),
|
||||
and it can also start from active execution before reasoning deltas arrive.
|
||||
- Heartbeat typing is a liveness signal for the resolved delivery target. It
|
||||
starts at heartbeat run start instead of following `message` or `thinking`
|
||||
stream timing. Set `typingMode: "never"` to disable it.
|
||||
|
||||
@@ -1316,6 +1316,7 @@ See [Multi-Agent Sandbox & Tools](/tools/multi-agent-sandbox-tools) for preceden
|
||||
- `mode`: `enforce` applies cleanup and is the default; `warn` emits warnings only.
|
||||
- `pruneAfter`: age cutoff for stale entries (default `30d`).
|
||||
- `maxEntries`: maximum number of entries in `sessions.json` (default `500`). Runtime writes batch cleanup with a small high-water buffer for production-sized caps; `openclaw sessions cleanup --enforce` applies the cap immediately.
|
||||
- Short-lived gateway model-run probe sessions use fixed `24h` retention, but cleanup is pressure-gated: it only removes stale strict model-run probe rows when session-entry maintenance/cap pressure is reached. Only strict explicit probe keys matching `agent:*:explicit:model-run-<uuid>` are eligible; normal direct, group, thread, cron, hook, heartbeat, ACP, and sub-agent sessions do not inherit this 24h retention. When model-run cleanup runs, it runs before the broader `pruneAfter` stale-entry cleanup and `maxEntries` cap.
|
||||
- `rotateBytes`: deprecated and ignored; `openclaw doctor --fix` removes it from older configs.
|
||||
- `resetArchiveRetention`: retention for `*.reset.<timestamp>` transcript archives. Defaults to `pruneAfter`; set `false` to disable.
|
||||
- `maxDiskBytes`: optional sessions-directory disk budget. In `warn` mode it logs warnings; in `enforce` mode it removes oldest artifacts/sessions first.
|
||||
|
||||
@@ -204,55 +204,6 @@ Controls elevated exec access outside the sandbox:
|
||||
}
|
||||
```
|
||||
|
||||
Agent entries can inject an environment only into their own `exec` child
|
||||
processes. Use a SecretRef for credentials and set `inheritHostEnv: false` when the
|
||||
Gateway process environment must not be inherited:
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
list: [
|
||||
{
|
||||
id: "referrals",
|
||||
tools: {
|
||||
exec: {
|
||||
inheritHostEnv: false,
|
||||
env: {
|
||||
GREENHOUSE_TOKEN: {
|
||||
source: "env",
|
||||
provider: "default",
|
||||
id: "REFERRALS_GREENHOUSE_TOKEN",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
`agents.list[].tools.exec.env` applies to `exec` only; it does not mutate
|
||||
`process.env` or automatically inject credentials into model-provider or plugin
|
||||
APIs. Trusted in-process plugin code can still inspect the materialized runtime
|
||||
config, so this is not a plugin isolation boundary.
|
||||
Configured values override same-named per-call values from the model. Trusted
|
||||
`resolve_exec_env` hook output and channel context are applied afterward. Host
|
||||
exec still rejects `PATH` and dangerous runtime/startup keys. Sandbox exec
|
||||
already starts from a minimal environment. With `inheritHostEnv: false`,
|
||||
Gateway exec also skips login-shell PATH discovery and cached shell-startup
|
||||
state; configure `pathPrepend` or absolute commands when needed. For
|
||||
`host: "node"`, configure scoped environment and inheritance isolation on the
|
||||
node host. Both this map and `inheritHostEnv: false` are rejected because the
|
||||
Gateway cannot clear the remote service environment or safely hold a scoped
|
||||
credential back during remote approval preparation.
|
||||
|
||||
Treat this map as credential-bearing configuration: every command the agent can
|
||||
run can read and exfiltrate these values, and command output can reveal them.
|
||||
Plaintext values are reported by `openclaw secrets audit`; prefer SecretRefs.
|
||||
Already-running background commands retain the environment captured when they
|
||||
started after a config or secret reload.
|
||||
|
||||
### `tools.loopDetection`
|
||||
|
||||
Tool-loop safety checks are **disabled by default**. Set `enabled: true` to activate detection. Settings can be defined globally in `tools.loopDetection` and overridden per-agent at `agents.list[].tools.loopDetection`.
|
||||
|
||||
@@ -104,6 +104,7 @@ Examples:
|
||||
openclaw doctor --lint
|
||||
openclaw doctor --lint --severity-min warning
|
||||
openclaw doctor --lint --json
|
||||
openclaw doctor --lint --all
|
||||
openclaw doctor --lint --only core/doctor/gateway-config --json
|
||||
```
|
||||
|
||||
@@ -111,7 +112,7 @@ JSON output includes:
|
||||
|
||||
- `ok`: whether any visible finding met the selected severity threshold
|
||||
- `checksRun`: number of health checks executed
|
||||
- `checksSkipped`: checks skipped by `--only` or `--skip`
|
||||
- `checksSkipped`: checks skipped by the selected profile, `--only`, or `--skip`
|
||||
- `findings`: structured diagnostics with `checkId`, `severity`, `message`, and
|
||||
optional `path`, `line`, `column`, `ocPath`, and `fixHint`
|
||||
|
||||
@@ -122,11 +123,13 @@ Exit codes:
|
||||
- `2`: command/runtime failure before lint findings could be emitted
|
||||
|
||||
Use `--severity-min info|warning|error` to control both what is printed and what
|
||||
causes a non-zero lint exit. Use `--only <id>` for narrow preflight gates and
|
||||
causes a non-zero lint exit. Use `--all` to run the complete lint inventory,
|
||||
including deeper opt-in checks excluded from the default automation set. Use `--only <id>` for narrow preflight gates and
|
||||
`--skip <id>` to temporarily exclude a noisy check while keeping the rest of the
|
||||
lint run active.
|
||||
Lint-output options such as `--json`, `--severity-min`, `--only`, and `--skip`
|
||||
must be paired with `--lint`; regular doctor and repair runs reject them.
|
||||
Lint-output options such as `--json`, `--severity-min`, `--all`, `--only`, and
|
||||
`--skip` must be paired with `--lint`; regular doctor and repair runs reject
|
||||
them.
|
||||
|
||||
## What it does (summary)
|
||||
|
||||
|
||||
@@ -415,7 +415,7 @@ If you installed OpenClaw via `npm install -g openclaw`, use the inline `docker
|
||||
|
||||
</Step>
|
||||
<Step title="Optional: build the common image">
|
||||
For a more functional sandbox image with common tooling (for example `curl`, `jq`, `nodejs`, `python3`, `git`):
|
||||
For a more functional sandbox image with common tooling (for example `curl`, `jq`, Node 24, pnpm, `python3`, and `git`):
|
||||
|
||||
From a source checkout:
|
||||
|
||||
|
||||
@@ -525,47 +525,6 @@ the config fields that accept SecretRefs.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Per-agent exec environment variables
|
||||
|
||||
`agents.list[].tools.exec.env` supports SecretInput values, so a credential can
|
||||
be resolved during Gateway activation and injected only into that agent's
|
||||
`exec` child processes:
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
list: [
|
||||
{
|
||||
id: "referrals",
|
||||
tools: {
|
||||
exec: {
|
||||
inheritHostEnv: false,
|
||||
env: {
|
||||
GREENHOUSE_TOKEN: {
|
||||
source: "env",
|
||||
provider: "default",
|
||||
id: "REFERRALS_GREENHOUSE_TOKEN",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
This surface is exec-specific. It does not mutate the Gateway process
|
||||
environment or automatically inject credentials into model-provider or plugin
|
||||
APIs. Trusted in-process plugin code can inspect the materialized runtime
|
||||
config. An unresolved active ref fails Gateway activation. SecretRefs are
|
||||
materialized in the Gateway's protected in-memory config snapshot, so this
|
||||
scopes subprocess injection rather than creating a same-process or same-OS-user
|
||||
security boundary. Every command available to the agent can read these values,
|
||||
command output can reveal them, and plaintext entries are reported by
|
||||
`openclaw secrets audit`. Configure scoped environment on a node host itself;
|
||||
agent exec env is rejected for `host: "node"`.
|
||||
|
||||
## MCP server environment variables
|
||||
|
||||
MCP server env vars configured via `plugins.entries.acpx.config.mcpServers` support SecretInput. This keeps API keys and tokens out of plaintext config:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -308,7 +308,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
|
||||
Normal setup and repair paths are documented across install, CLI, and gateway docs. Platform-specific Windows paths are tracked in the Windows via WSL2 and Native Windows rows.
|
||||
|
||||
<div className="maturity-surface-rollup"><span>Coverage Experimental - 2%</span><span>Quality Stable - 83%</span><span>Completeness Stable - 90%</span><span><span className="maturity-lts maturity-lts-partial">Partial - 6</span></span></div>
|
||||
<div className="maturity-surface-rollup"><span>Coverage Experimental - 4%</span><span>Quality Stable - 83%</span><span>Completeness Stable - 90%</span><span><span className="maturity-lts maturity-lts-partial">Partial - 6</span></span></div>
|
||||
|
||||
<div className="maturity-category-list">
|
||||
<div className="maturity-category-row maturity-category-row-header"><span>Area</span><span>Coverage</span><span>Quality</span><span>Completeness</span><span>Docs</span></div>
|
||||
@@ -317,7 +317,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">CLI Setup</span>
|
||||
<span>6 capabilities / LTS-supported</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>2%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "2%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>17%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "17%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-stable"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-stable">Stable</span><span>89%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "89%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-stable"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-stable">Stable</span><span>90%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "90%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Index](/install/index), [Installer](/install/installer), [Node](/install/node), [Updating](/install/updating)</div>
|
||||
@@ -327,7 +327,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Onboarding and Auth Setup</span>
|
||||
<span>5 capabilities / LTS-supported</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>2%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "2%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>75%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "75%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-stable"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-stable">Stable</span><span>89%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "89%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Onboard](/cli/onboard), [Configure](/cli/configure), [Onboarding Overview](/start/onboarding-overview)</div>
|
||||
@@ -337,7 +337,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Plugin and Channel Setup</span>
|
||||
<span>5 capabilities</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>2%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "2%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>75%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "75%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-stable"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-stable">Stable</span><span>89%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "89%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Onboard](/cli/onboard), [Plugins](/cli/plugins), [Channels](/cli/channels)</div>
|
||||
@@ -347,7 +347,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Gateway Service Management</span>
|
||||
<span>5 capabilities / LTS-supported</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>2%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "2%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>14%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "14%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-stable"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-stable">Stable</span><span>87%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "87%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-stable"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-stable">Stable</span><span>90%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "90%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Gateway](/cli/gateway), [Updating](/install/updating), [Troubleshooting](/gateway/troubleshooting)</div>
|
||||
@@ -357,7 +357,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">CLI Observability</span>
|
||||
<span>5 capabilities / LTS-supported</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>2%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "2%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-stable"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-stable">Stable</span><span>89%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "89%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-stable"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-stable">Stable</span><span>90%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "90%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Status](/cli/status), [Health](/cli/health), [Logs](/cli/logs), [Diagnostics](/gateway/diagnostics)</div>
|
||||
@@ -367,7 +367,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Doctor</span>
|
||||
<span>10 capabilities / LTS-supported</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>2%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "2%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-stable"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-stable">Stable</span><span>89%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "89%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-stable"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-stable">Stable</span><span>90%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "90%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Doctor](/cli/doctor), [Doctor](/gateway/doctor), [Secrets](/gateway/secrets), [Troubleshooting](/gateway/troubleshooting)</div>
|
||||
@@ -377,7 +377,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Updates and Upgrades</span>
|
||||
<span>5 capabilities / LTS-supported</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>2%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "2%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>75%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "75%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-stable"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-stable">Stable</span><span>89%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "89%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Updating](/install/updating), [Update](/cli/update), [Troubleshooting](/gateway/troubleshooting)</div>
|
||||
@@ -391,7 +391,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
|
||||
Core architecture, auth, pairing, protocol docs, daemon docs, and CLI runbooks are broad and current.
|
||||
|
||||
<div className="maturity-surface-rollup"><span>Coverage Experimental - 3%</span><span>Quality Stable - 81%</span><span>Completeness Stable - 89%</span><span><span className="maturity-lts maturity-lts-partial">Partial - 12</span></span></div>
|
||||
<div className="maturity-surface-rollup"><span>Coverage Experimental - 6%</span><span>Quality Stable - 81%</span><span>Completeness Stable - 89%</span><span><span className="maturity-lts maturity-lts-partial">Partial - 12</span></span></div>
|
||||
|
||||
<div className="maturity-category-list">
|
||||
<div className="maturity-category-row maturity-category-row-header"><span>Area</span><span>Coverage</span><span>Quality</span><span>Completeness</span><span>Docs</span></div>
|
||||
@@ -400,7 +400,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Approvals and Remote Execution</span>
|
||||
<span>6 capabilities / LTS-supported</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>3%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "3%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>75%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "75%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-stable"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-stable">Stable</span><span>89%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "89%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Protocol](/gateway/protocol), [Index](/gateway/security/index)</div>
|
||||
@@ -410,7 +410,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">HTTP APIs</span>
|
||||
<span>4 capabilities / LTS-supported</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>3%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "3%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>25%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "25%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-stable"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-stable">Stable</span><span>90%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "90%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-stable"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-stable">Stable</span><span>90%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "90%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Index](/gateway/index), [Openai Http Api](/gateway/openai-http-api), [Openresponses Http Api](/gateway/openresponses-http-api), [Tools Invoke Http Api](/gateway/tools-invoke-http-api), [Hooks](/automation/hooks), [Index](/web/index)</div>
|
||||
@@ -420,7 +420,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Hosted Web Surface</span>
|
||||
<span>4 capabilities / LTS-supported</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>3%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "3%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-stable"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-stable">Stable</span><span>89%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "89%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-stable"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-stable">Stable</span><span>90%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "90%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Index](/gateway/index), [Architecture](/concepts/architecture), [Control Ui](/web/control-ui), [Webchat](/web/webchat), [Canvas](/refactor/canvas)</div>
|
||||
@@ -430,7 +430,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Gateway RPC APIs and Events</span>
|
||||
<span>20 capabilities / LTS-supported</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>3%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "3%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>9%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "9%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-stable"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-stable">Stable</span><span>90%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "90%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-stable"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-stable">Stable</span><span>90%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "90%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Protocol](/gateway/protocol), [Index](/gateway/index), [Architecture](/concepts/architecture)</div>
|
||||
@@ -440,7 +440,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Device Auth and Pairing</span>
|
||||
<span>10 capabilities / LTS-supported</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>3%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "3%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>75%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "75%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-stable"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-stable">Stable</span><span>89%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "89%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Protocol](/gateway/protocol), [Pairing](/gateway/pairing), [Index](/gateway/security/index)</div>
|
||||
@@ -450,7 +450,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Network Access and Discovery</span>
|
||||
<span>6 capabilities / LTS-supported</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>3%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "3%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>75%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "75%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-stable"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-stable">Stable</span><span>89%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "89%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Index](/gateway/index), [Discovery](/gateway/discovery), [Protocol](/gateway/protocol)</div>
|
||||
@@ -460,7 +460,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Nodes and Remote Capabilities</span>
|
||||
<span>8 capabilities</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>3%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "3%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>75%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "75%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-stable"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-stable">Stable</span><span>89%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "89%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Protocol](/gateway/protocol), [Architecture](/concepts/architecture), [Index](/nodes/index)</div>
|
||||
@@ -470,7 +470,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Health, Diagnostics, and Repair</span>
|
||||
<span>7 capabilities / LTS-supported</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>3%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "3%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>75%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "75%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-stable"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-stable">Stable</span><span>89%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "89%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Index](/gateway/index), [Diagnostics](/gateway/diagnostics), [Doctor](/gateway/doctor)</div>
|
||||
@@ -480,7 +480,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Protocol Compatibility</span>
|
||||
<span>7 capabilities / LTS-supported</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>3%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "3%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>75%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "75%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-stable"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-stable">Stable</span><span>89%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "89%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Protocol](/gateway/protocol), [Architecture](/concepts/architecture), [Typebox](/concepts/typebox), [Bridge Protocol](/gateway/bridge-protocol)</div>
|
||||
@@ -490,7 +490,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Roles and Permissions</span>
|
||||
<span>5 capabilities / LTS-supported</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>3%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "3%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>75%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "75%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-stable"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-stable">Stable</span><span>89%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "89%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Protocol](/gateway/protocol), [Index](/gateway/security/index)</div>
|
||||
@@ -500,7 +500,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Gateway Lifecycle</span>
|
||||
<span>7 capabilities / LTS-supported</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>3%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "3%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>33%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "33%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-stable"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-stable">Stable</span><span>90%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "90%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-stable"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-stable">Stable</span><span>90%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "90%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Index](/gateway/index), [Architecture](/concepts/architecture)</div>
|
||||
@@ -510,7 +510,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Security Controls</span>
|
||||
<span>6 capabilities / LTS-supported</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>3%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "3%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>75%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "75%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-stable"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-stable">Stable</span><span>89%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "89%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Index](/gateway/security/index), [Protocol](/gateway/protocol), [Discovery](/gateway/discovery)</div>
|
||||
@@ -520,7 +520,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">WebSocket Connection</span>
|
||||
<span>8 capabilities / LTS-supported</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>3%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "3%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>13%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "13%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-stable"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-stable">Stable</span><span>90%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "90%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-stable"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-stable">Stable</span><span>90%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "90%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Protocol](/gateway/protocol), [Architecture](/concepts/architecture)</div>
|
||||
@@ -534,7 +534,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
|
||||
Main loop, models, provider routing, and tool streaming are first-class, but provider behavior shifts weekly and needs scenario proof per release.
|
||||
|
||||
<div className="maturity-surface-rollup"><span>Coverage Experimental - 2%</span><span>Quality Beta - 78%</span><span>Completeness Beta - 79%</span><span><span className="maturity-lts maturity-lts-partial">Partial - 6</span></span></div>
|
||||
<div className="maturity-surface-rollup"><span>Coverage Experimental - 33%</span><span>Quality Beta - 78%</span><span>Completeness Beta - 79%</span><span><span className="maturity-lts maturity-lts-partial">Partial - 6</span></span></div>
|
||||
|
||||
<div className="maturity-category-list">
|
||||
<div className="maturity-category-row maturity-category-row-header"><span>Area</span><span>Coverage</span><span>Quality</span><span>Completeness</span><span>Docs</span></div>
|
||||
@@ -543,7 +543,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Agent Turn Execution</span>
|
||||
<span>3 capabilities / LTS-supported</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>2%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "2%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>29%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "29%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Agent Loop](/concepts/agent-loop), [Agent](/cli/agent), [Agent Runtimes](/concepts/agent-runtimes)</div>
|
||||
@@ -553,7 +553,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">External Runtimes and Subagents</span>
|
||||
<span>4 capabilities</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>2%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "2%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>30%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "30%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Agent Runtimes](/concepts/agent-runtimes), [Anthropic](/providers/anthropic), [Google](/providers/google), [Subagents](/tools/subagents)</div>
|
||||
@@ -563,7 +563,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Hosted Provider Execution</span>
|
||||
<span>5 capabilities / LTS-supported</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>2%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "2%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>20%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "20%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Openai](/providers/openai), [Anthropic](/providers/anthropic), [Google](/providers/google), [Models](/concepts/models)</div>
|
||||
@@ -573,7 +573,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Local and Self-hosted Providers</span>
|
||||
<span>5 capabilities</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>2%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "2%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-alpha"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-alpha">Alpha</span><span>68%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "68%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Ollama](/providers/ollama), [Models](/concepts/models), [Agent](/cli/agent)</div>
|
||||
@@ -583,7 +583,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Model and Runtime Selection</span>
|
||||
<span>4 capabilities / LTS-supported</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>2%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "2%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>25%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "25%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Models](/concepts/models), [Models](/cli/models), [Openai](/providers/openai), [Agent Runtimes](/concepts/agent-runtimes)</div>
|
||||
@@ -593,7 +593,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Provider Auth</span>
|
||||
<span>10 capabilities / LTS-supported</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>2%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "2%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>24%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "24%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Models](/concepts/models), [Agent](/cli/agent), [Models](/cli/models), [Openai](/providers/openai), [Anthropic](/providers/anthropic), [Google](/providers/google), [Subagents](/tools/subagents)</div>
|
||||
@@ -603,7 +603,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Streaming and Progress</span>
|
||||
<span>2 capabilities</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>2%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "2%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-alpha"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-alpha">Alpha</span><span>56%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "56%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Streaming](/concepts/streaming), [Agent Loop](/concepts/agent-loop)</div>
|
||||
@@ -613,7 +613,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Tool Calls and Response Handling</span>
|
||||
<span>3 capabilities / LTS-supported</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>2%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "2%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-alpha"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-alpha">Alpha</span><span>65%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "65%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Agent Loop](/concepts/agent-loop), [Ollama](/providers/ollama)</div>
|
||||
@@ -623,7 +623,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Tool Execution Controls</span>
|
||||
<span>6 capabilities / LTS-supported</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>2%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "2%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-alpha"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-alpha">Alpha</span><span>50%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "50%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Sandbox Vs Tool Policy Vs Elevated](/gateway/sandbox-vs-tool-policy-vs-elevated), [Agent Loop](/concepts/agent-loop), [Subagents](/tools/subagents)</div>
|
||||
@@ -637,7 +637,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
|
||||
Strong docs and active implementation. Maturity depends on transcript durability, compaction quality, and cross-client parity.
|
||||
|
||||
<div className="maturity-surface-rollup"><span>Coverage Experimental - 0%</span><span>Quality Beta - 77%</span><span>Completeness Beta - 79%</span><span><span className="maturity-lts maturity-lts-partial">Partial - 6</span></span></div>
|
||||
<div className="maturity-surface-rollup"><span>Coverage Experimental - 30%</span><span>Quality Beta - 77%</span><span>Completeness Beta - 79%</span><span><span className="maturity-lts maturity-lts-partial">Partial - 6</span></span></div>
|
||||
|
||||
<div className="maturity-category-list">
|
||||
<div className="maturity-category-row maturity-category-row-header"><span>Area</span><span>Coverage</span><span>Quality</span><span>Completeness</span><span>Docs</span></div>
|
||||
@@ -656,7 +656,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Token Management</span>
|
||||
<span>3 capabilities / LTS-supported</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>20%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "20%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Compaction](/concepts/compaction), [Context](/concepts/context), [Session Management Compaction](/reference/session-management-compaction)</div>
|
||||
@@ -666,7 +666,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Context Engine</span>
|
||||
<span>2 capabilities / LTS-supported</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-alpha"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-alpha">Alpha</span><span>57%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "57%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Context](/concepts/context), [Context Engine](/concepts/context-engine), [Codex Context Engine Harness](/plan/codex-context-engine-harness)</div>
|
||||
@@ -676,7 +676,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Cross-client History and Session Parity</span>
|
||||
<span>2 capabilities</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>40%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "40%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Webchat](/web/webchat), [Android](/platforms/android), [Channel Routing](/channels/channel-routing)</div>
|
||||
@@ -686,7 +686,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Diagnostics, Maintenance, and Recovery</span>
|
||||
<span>3 capabilities</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>40%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "40%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Diagnostics](/gateway/diagnostics), [Session Management Compaction](/reference/session-management-compaction), [Flags](/diagnostics/flags)</div>
|
||||
@@ -696,7 +696,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Core Prompts and Context</span>
|
||||
<span>2 capabilities / LTS-supported</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>38%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "38%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Context](/concepts/context), [Transcript Hygiene](/reference/transcript-hygiene), [Discord](/channels/discord)</div>
|
||||
@@ -706,7 +706,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Memory</span>
|
||||
<span>5 capabilities</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>46%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "46%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Memory Config](/reference/memory-config), [Memory Qmd](/concepts/memory-qmd), [Memory](/concepts/memory), [Discord](/channels/discord)</div>
|
||||
@@ -716,7 +716,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Session Routing</span>
|
||||
<span>2 capabilities / LTS-supported</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>25%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "25%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Session](/concepts/session), [Channel Routing](/channels/channel-routing), [Discord](/channels/discord)</div>
|
||||
@@ -740,7 +740,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
|
||||
Many channels share Gateway delivery and routing contracts, but channel behavior varies by upstream API and account-policy constraints.
|
||||
|
||||
<div className="maturity-surface-rollup"><span>Coverage Experimental - 0%</span><span>Quality Beta - 76%</span><span>Completeness Beta - 79%</span><span><span className="maturity-lts maturity-lts-partial">Partial - 5</span></span></div>
|
||||
<div className="maturity-surface-rollup"><span>Coverage Experimental - 13%</span><span>Quality Beta - 76%</span><span>Completeness Beta - 79%</span><span><span className="maturity-lts maturity-lts-partial">Partial - 5</span></span></div>
|
||||
|
||||
<div className="maturity-category-list">
|
||||
<div className="maturity-category-row maturity-category-row-header"><span>Area</span><span>Coverage</span><span>Quality</span><span>Completeness</span><span>Docs</span></div>
|
||||
@@ -759,7 +759,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Channel Setup</span>
|
||||
<span>5 capabilities / LTS-supported</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>14%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "14%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Index](/channels/index), [Pairing](/channels/pairing), [Troubleshooting](/channels/troubleshooting), [Sdk Channel Plugins](/plugins/sdk-channel-plugins)</div>
|
||||
@@ -769,7 +769,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Group Thread and Ambient Room Behavior</span>
|
||||
<span>5 capabilities</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>36%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "36%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Groups](/channels/groups), [Group Messages](/channels/group-messages), [Ambient Room Events](/channels/ambient-room-events), [Broadcast Groups](/channels/broadcast-groups), [Discord](/channels/discord)</div>
|
||||
@@ -799,7 +799,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Outbound Delivery and Reply Pipeline</span>
|
||||
<span>4 capabilities / LTS-supported</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>38%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "38%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Groups](/channels/groups), [Ambient Room Events](/channels/ambient-room-events), [Discord](/channels/discord), [Matrix](/channels/matrix), [Config Channels](/gateway/config-channels)</div>
|
||||
@@ -809,7 +809,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Conversation Routing and Delivery</span>
|
||||
<span>10 capabilities / LTS-supported</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>19%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "19%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Channel Routing](/channels/channel-routing), [Groups](/channels/groups), [Discord](/channels/discord), [Matrix](/channels/matrix), [Troubleshooting](/channels/troubleshooting), [Configuration Reference](/gateway/configuration-reference)</div>
|
||||
@@ -833,7 +833,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
|
||||
OTel, Prometheus, logging, and diagnostics docs exist. Needs a public "what operators should look at first" maturity pass.
|
||||
|
||||
<div className="maturity-surface-rollup"><span>Coverage Experimental - 6%</span><span>Quality Beta - 75%</span><span>Completeness Beta - 79%</span><span><span className="maturity-lts maturity-lts-partial">Partial - 3</span></span></div>
|
||||
<div className="maturity-surface-rollup"><span>Coverage Experimental - 18%</span><span>Quality Beta - 75%</span><span>Completeness Beta - 79%</span><span><span className="maturity-lts maturity-lts-partial">Partial - 3</span></span></div>
|
||||
|
||||
<div className="maturity-category-list">
|
||||
<div className="maturity-category-row maturity-category-row-header"><span>Area</span><span>Coverage</span><span>Quality</span><span>Completeness</span><span>Docs</span></div>
|
||||
@@ -842,7 +842,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Health and Repair</span>
|
||||
<span>12 capabilities / LTS-supported</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>6%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "6%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>28%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "28%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Health](/gateway/health), [Telegram](/channels/telegram), [Doctor](/cli/doctor), [Doctor](/gateway/doctor), [Sdk Subpaths](/plugins/sdk-subpaths), [Health](/cli/health), [Protocol](/gateway/protocol)</div>
|
||||
@@ -852,7 +852,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Logging</span>
|
||||
<span>5 capabilities / LTS-supported</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>6%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "6%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-alpha"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-alpha">Alpha</span><span>68%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "68%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Logging](/logging), [Logging](/gateway/logging), [Logs](/cli/logs)</div>
|
||||
@@ -862,7 +862,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Diagnostic Collection</span>
|
||||
<span>8 capabilities</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>6%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "6%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>30%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "30%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Diagnostics](/gateway/diagnostics), [Health](/gateway/health), [Codex Harness](/plugins/codex-harness), [Protocol](/gateway/protocol)</div>
|
||||
@@ -872,7 +872,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Telemetry Export</span>
|
||||
<span>13 capabilities</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>6%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "6%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>33%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "33%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Hooks](/plugins/hooks), [Opentelemetry](/gateway/opentelemetry), [Logging](/logging), [Sdk Subpaths](/plugins/sdk-subpaths), [Diagnostics Otel](/plugins/reference/diagnostics-otel), [Prometheus](/gateway/prometheus), [Diagnostics Prometheus](/plugins/reference/diagnostics-prometheus)</div>
|
||||
@@ -882,7 +882,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Session Diagnostics</span>
|
||||
<span>4 capabilities / LTS-supported</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>6%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "6%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-alpha"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-alpha">Alpha</span><span>68%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "68%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Opentelemetry](/gateway/opentelemetry), [Prometheus](/gateway/prometheus), [Diagnostics](/gateway/diagnostics), [Protocol](/gateway/protocol)</div>
|
||||
@@ -896,7 +896,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
|
||||
Web UI is documented with pairing, chat, PWA, Talk, push, and remote Gateway flows. Promote after cross-browser and mobile-PWA scorecards.
|
||||
|
||||
<div className="maturity-surface-rollup"><span>Coverage Experimental - 0%</span><span>Quality Beta - 74%</span><span>Completeness Beta - 79%</span><span><span className="maturity-lts maturity-lts-none">None</span></span></div>
|
||||
<div className="maturity-surface-rollup"><span>Coverage Experimental - 4%</span><span>Quality Beta - 74%</span><span>Completeness Beta - 79%</span><span><span className="maturity-lts maturity-lts-none">None</span></span></div>
|
||||
|
||||
<div className="maturity-category-list">
|
||||
<div className="maturity-category-row maturity-category-row-header"><span>Area</span><span>Coverage</span><span>Quality</span><span>Completeness</span><span>Docs</span></div>
|
||||
@@ -935,7 +935,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Browser UI</span>
|
||||
<span>10 capabilities</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>8%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "8%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Control Ui](/web/control-ui), [Index](/web/index), [Dashboard](/web/dashboard), [Protocol](/gateway/protocol)</div>
|
||||
@@ -945,7 +945,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">WebChat Conversations</span>
|
||||
<span>15 capabilities</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>10%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "10%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Control Ui](/web/control-ui), [Webchat](/web/webchat), [Getting Started](/start/getting-started), [Channel Routing](/channels/channel-routing), [Secure File Operations](/gateway/security/secure-file-operations)</div>
|
||||
@@ -955,7 +955,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Operator Console</span>
|
||||
<span>10 capabilities</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>8%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "8%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Control Ui](/web/control-ui), [Health](/gateway/health), [Protocol](/gateway/protocol), [Dashboard](/web/dashboard)</div>
|
||||
@@ -969,7 +969,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
|
||||
Broad docs and strong internal runtime evidence exist across manifests, discovery, loading, provider/tool architecture, and approval boundaries. Keep the row at beta until public SDK API/subpaths and external distribution proof are stronger.
|
||||
|
||||
<div className="maturity-surface-rollup"><span>Coverage Experimental - 2%</span><span>Quality Beta - 72%</span><span>Completeness Beta - 79%</span><span><span className="maturity-lts maturity-lts-partial">Partial - 7</span></span></div>
|
||||
<div className="maturity-surface-rollup"><span>Coverage Experimental - 12%</span><span>Quality Beta - 72%</span><span>Completeness Beta - 79%</span><span><span className="maturity-lts maturity-lts-partial">Partial - 7</span></span></div>
|
||||
|
||||
<div className="maturity-category-list">
|
||||
<div className="maturity-category-row maturity-category-row-header"><span>Area</span><span>Coverage</span><span>Quality</span><span>Completeness</span><span>Docs</span></div>
|
||||
@@ -978,7 +978,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Authoring and Packaging plugins</span>
|
||||
<span>8 capabilities / LTS-supported</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>2%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "2%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-alpha"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-alpha">Alpha</span><span>68%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "68%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Building Plugins](/plugins/building-plugins), [Sdk Overview](/plugins/sdk-overview), [Sdk Entrypoints](/plugins/sdk-entrypoints), [Sdk Subpaths](/plugins/sdk-subpaths), [Manifest](/plugins/manifest), [Reference](/plugins/reference)</div>
|
||||
@@ -988,7 +988,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Bundled plugins</span>
|
||||
<span>5 capabilities / LTS-supported</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>2%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "2%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-alpha"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-alpha">Alpha</span><span>68%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "68%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Plugin Inventory](/plugins/plugin-inventory), [Plugins](/cli/plugins), [Architecture Internals](/plugins/architecture-internals)</div>
|
||||
@@ -998,7 +998,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Canvas plugin</span>
|
||||
<span>6 capabilities</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>2%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "2%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-alpha"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-alpha">Alpha</span><span>68%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "68%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Canvas](/plugins/reference/canvas), [Canvas](/refactor/canvas), [Configuration Reference](/gateway/configuration-reference)</div>
|
||||
@@ -1008,7 +1008,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Installing and running plugins</span>
|
||||
<span>6 capabilities / LTS-supported</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>2%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "2%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>35%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "35%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Architecture](/plugins/architecture), [Architecture Internals](/plugins/architecture-internals), [Plugins](/cli/plugins)</div>
|
||||
@@ -1018,7 +1018,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Channel plugins</span>
|
||||
<span>5 capabilities / LTS-supported</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>2%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "2%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-alpha"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-alpha">Alpha</span><span>68%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "68%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Sdk Channel Plugins](/plugins/sdk-channel-plugins), [Sdk Channel Inbound](/plugins/sdk-channel-inbound), [Sdk Channel Outbound](/plugins/sdk-channel-outbound)</div>
|
||||
@@ -1028,7 +1028,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Provider and tool plugins</span>
|
||||
<span>6 capabilities / LTS-supported</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>2%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "2%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>43%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "43%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Sdk Provider Plugins](/plugins/sdk-provider-plugins), [Tool Plugins](/plugins/tool-plugins), [Adding Capabilities](/plugins/adding-capabilities)</div>
|
||||
@@ -1038,7 +1038,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Plugin approvals</span>
|
||||
<span>6 capabilities / LTS-supported</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>2%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "2%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-alpha"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-alpha">Alpha</span><span>68%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "68%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Plugin Permission Requests](/plugins/plugin-permission-requests), [Exec Approvals](/tools/exec-approvals), [Sdk Channel Plugins](/plugins/sdk-channel-plugins)</div>
|
||||
@@ -1048,7 +1048,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Publishing plugins</span>
|
||||
<span>6 capabilities / LTS-supported</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>2%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "2%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-alpha"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-alpha">Alpha</span><span>68%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "68%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Plugins](/cli/plugins), [Compatibility](/plugins/compatibility), [Publishing](/clawhub/publishing)</div>
|
||||
@@ -1058,7 +1058,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Testing plugins</span>
|
||||
<span>6 capabilities</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>2%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "2%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>27%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "27%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Sdk Testing](/plugins/sdk-testing), [Sdk Setup](/plugins/sdk-setup), [Codex Harness](/plugins/codex-harness)</div>
|
||||
@@ -1072,7 +1072,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
|
||||
Good docs and hardening surfaces exist. Promote after regular upgrade/security scenario runs prove no setup regressions.
|
||||
|
||||
<div className="maturity-surface-rollup"><span>Coverage Experimental - 0%</span><span>Quality Beta - 72%</span><span>Completeness Beta - 79%</span><span><span className="maturity-lts maturity-lts-partial">Partial - 5</span></span></div>
|
||||
<div className="maturity-surface-rollup"><span>Coverage Experimental - 16%</span><span>Quality Beta - 72%</span><span>Completeness Beta - 79%</span><span><span className="maturity-lts maturity-lts-partial">Partial - 5</span></span></div>
|
||||
|
||||
<div className="maturity-category-list">
|
||||
<div className="maturity-category-row maturity-category-row-header"><span>Area</span><span>Coverage</span><span>Quality</span><span>Completeness</span><span>Docs</span></div>
|
||||
@@ -1081,7 +1081,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Approval Policy and Tool Safeguards</span>
|
||||
<span>2 capabilities / LTS-supported</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-alpha"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-alpha">Alpha</span><span>50%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "50%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Exec Approvals](/tools/exec-approvals), [Approvals](/cli/approvals), [Plugin Permission Requests](/plugins/plugin-permission-requests), [Audit Checks](/gateway/security/audit-checks)</div>
|
||||
@@ -1131,7 +1131,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Credential and Secret Hygiene</span>
|
||||
<span>5 capabilities / LTS-supported</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>46%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "46%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Authentication](/gateway/authentication), [Models](/cli/models), [Openai](/providers/openai), [Oauth](/concepts/oauth), [Secrets](/gateway/secrets), [Secrets](/cli/secrets), [Secretref Credential Surface](/reference/secretref-credential-surface), [Audit Checks](/gateway/security/audit-checks)</div>
|
||||
@@ -1145,7 +1145,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
|
||||
Documented and usable, but scenario proof should cover unattended delivery, retries, and failure visibility.
|
||||
|
||||
<div className="maturity-surface-rollup"><span>Coverage Experimental - 0%</span><span>Quality Beta - 72%</span><span>Completeness Beta - 79%</span><span><span className="maturity-lts maturity-lts-none">None</span></span></div>
|
||||
<div className="maturity-surface-rollup"><span>Coverage Experimental - 2%</span><span>Quality Beta - 72%</span><span>Completeness Beta - 79%</span><span><span className="maturity-lts maturity-lts-none">None</span></span></div>
|
||||
|
||||
<div className="maturity-category-list">
|
||||
<div className="maturity-category-row maturity-category-row-header"><span>Area</span><span>Coverage</span><span>Quality</span><span>Completeness</span><span>Docs</span></div>
|
||||
@@ -1194,7 +1194,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Heartbeat</span>
|
||||
<span>5 capabilities</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>14%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "14%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Index](/automation/index), [Heartbeat](/gateway/heartbeat), [Commitments](/concepts/commitments)</div>
|
||||
@@ -1218,7 +1218,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
|
||||
Broad capability surface exists, but provider variance, file limits, and node/app parity make this not stable yet.
|
||||
|
||||
<div className="maturity-surface-rollup"><span>Coverage Experimental - 1%</span><span>Quality Alpha - 64%</span><span>Completeness Alpha - 68%</span><span><span className="maturity-lts maturity-lts-none">None</span></span></div>
|
||||
<div className="maturity-surface-rollup"><span>Coverage Experimental - 2%</span><span>Quality Alpha - 64%</span><span>Completeness Alpha - 68%</span><span><span className="maturity-lts maturity-lts-none">None</span></span></div>
|
||||
|
||||
<div className="maturity-category-list">
|
||||
<div className="maturity-category-row maturity-category-row-header"><span>Area</span><span>Coverage</span><span>Quality</span><span>Completeness</span><span>Docs</span></div>
|
||||
@@ -1227,7 +1227,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Media Intake and Access</span>
|
||||
<span>8 capabilities</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>1%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "1%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-alpha"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-alpha">Alpha</span><span>61%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "61%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-alpha"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-alpha">Alpha</span><span>68%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "68%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Media Overview](/tools/media-overview), [Media Understanding](/nodes/media-understanding), [Secure File Operations](/gateway/security/secure-file-operations), [Pdf](/tools/pdf), [Image Generation](/tools/image-generation), [Qr](/cli/qr), [Line](/channels/line), [Whatsapp](/channels/whatsapp)</div>
|
||||
@@ -1237,7 +1237,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Channel Media Handling</span>
|
||||
<span>5 capabilities</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>1%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "1%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-alpha"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-alpha">Alpha</span><span>61%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "61%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-alpha"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-alpha">Alpha</span><span>68%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "68%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Images](/nodes/images), [Media Overview](/tools/media-overview), [Discord](/channels/discord)</div>
|
||||
@@ -1247,7 +1247,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Media Configuration</span>
|
||||
<span>1 capabilities</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>1%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "1%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-alpha"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-alpha">Alpha</span><span>61%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "61%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-alpha"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-alpha">Alpha</span><span>68%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "68%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Media Overview](/tools/media-overview), [Image Generation](/tools/image-generation), [Manifest](/plugins/manifest), [Codex Harness](/plugins/codex-harness)</div>
|
||||
@@ -1257,7 +1257,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Text-to-Speech Delivery</span>
|
||||
<span>2 capabilities</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>1%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "1%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-alpha"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-alpha">Alpha</span><span>61%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "61%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-alpha"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-alpha">Alpha</span><span>68%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "68%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Tts](/tools/tts), [Media Overview](/tools/media-overview), [Discord](/channels/discord)</div>
|
||||
@@ -1267,7 +1267,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Media Understanding</span>
|
||||
<span>12 capabilities</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>1%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "1%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>7%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "7%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-alpha"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-alpha">Alpha</span><span>69%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "69%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-alpha"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-alpha">Alpha</span><span>69%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "69%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Audio](/nodes/audio), [Media Understanding](/nodes/media-understanding), [Media Overview](/tools/media-overview), [Whatsapp](/channels/whatsapp), [Images](/nodes/images), [Infer](/cli/infer), [Pdf](/tools/pdf)</div>
|
||||
@@ -1277,7 +1277,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Media Generation</span>
|
||||
<span>17 capabilities</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>1%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "1%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>5%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "5%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-alpha"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-alpha">Alpha</span><span>69%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "69%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-alpha"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-alpha">Alpha</span><span>69%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "69%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Image Generation](/tools/image-generation), [Media Overview](/tools/media-overview), [Skills](/tools/skills), [Music Generation](/tools/music-generation), [Video Generation](/tools/video-generation)</div>
|
||||
@@ -1480,7 +1480,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
|
||||
OpenClaw App SDK is a distinct external app contract separate from Gateway runtime and Plugin SDK. Current scoring shows a real `@openclaw/sdk` path with gaps around public packaging, auto-discovery, approvals, helpers, and compatibility.
|
||||
|
||||
<div className="maturity-surface-rollup"><span>Coverage Experimental - 0%</span><span>Quality Alpha - 54%</span><span>Completeness Alpha - 53%</span><span><span className="maturity-lts maturity-lts-none">None</span></span></div>
|
||||
<div className="maturity-surface-rollup"><span>Coverage Experimental - 3%</span><span>Quality Alpha - 54%</span><span>Completeness Alpha - 53%</span><span><span className="maturity-lts maturity-lts-none">None</span></span></div>
|
||||
|
||||
<div className="maturity-category-list">
|
||||
<div className="maturity-category-row maturity-category-row-header"><span>Area</span><span>Coverage</span><span>Quality</span><span>Completeness</span><span>Docs</span></div>
|
||||
@@ -1529,7 +1529,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Resource Helpers</span>
|
||||
<span>5 capabilities</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>17%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "17%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-alpha"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-alpha">Alpha</span><span>62%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "62%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-alpha"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-alpha">Alpha</span><span>53%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "53%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Openclaw Sdk](/gateway/external-apps), [Openclaw Sdk Api Design](/gateway/external-apps)</div>
|
||||
@@ -1704,7 +1704,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
|
||||
Install docs exist and are common deployment paths. Promote after recurring release smoke captures upgrade and volume behavior.
|
||||
|
||||
<div className="maturity-surface-rollup"><span>Coverage Experimental - 5%</span><span>Quality Beta - 71%</span><span>Completeness Beta - 79%</span><span><span className="maturity-lts maturity-lts-none">None</span></span></div>
|
||||
<div className="maturity-surface-rollup"><span>Coverage Experimental - 7%</span><span>Quality Beta - 71%</span><span>Completeness Beta - 79%</span><span><span className="maturity-lts maturity-lts-none">None</span></span></div>
|
||||
|
||||
<div className="maturity-category-list">
|
||||
<div className="maturity-category-row maturity-category-row-header"><span>Area</span><span>Coverage</span><span>Quality</span><span>Completeness</span><span>Docs</span></div>
|
||||
@@ -1713,7 +1713,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Container Setup</span>
|
||||
<span>6 capabilities</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>5%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "5%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-alpha"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-alpha">Alpha</span><span>68%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "68%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Docker](/install/docker), [Podman](/install/podman)</div>
|
||||
@@ -1723,7 +1723,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Container Operations</span>
|
||||
<span>11 capabilities</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>5%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "5%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-alpha"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-alpha">Alpha</span><span>68%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "68%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Podman](/install/podman), [Docker Vm Runtime](/install/docker-vm-runtime), [Docker](/install/docker), [Hetzner](/install/hetzner), [Hostinger](/install/hostinger)</div>
|
||||
@@ -1733,7 +1733,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Image Release and Validation</span>
|
||||
<span>5 capabilities</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>5%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "5%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>29%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "29%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Docker](/install/docker), [Docker Vm Runtime](/install/docker-vm-runtime), [Full Release Validation](/reference/full-release-validation)</div>
|
||||
@@ -1743,7 +1743,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Agent Sandbox and Tooling</span>
|
||||
<span>3 capabilities</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>5%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "5%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-alpha"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-alpha">Alpha</span><span>68%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "68%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Docker](/install/docker), [Docker Vm Runtime](/install/docker-vm-runtime)</div>
|
||||
@@ -1757,7 +1757,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
|
||||
Recommended Windows path with systemd/user-service guidance and boot-chain docs. Promote after repeated install/update scorecards.
|
||||
|
||||
<div className="maturity-surface-rollup"><span>Coverage Experimental - 3%</span><span>Quality Alpha - 69%</span><span>Completeness Beta - 79%</span><span><span className="maturity-lts maturity-lts-partial">Partial - 5</span></span></div>
|
||||
<div className="maturity-surface-rollup"><span>Coverage Experimental - 6%</span><span>Quality Alpha - 69%</span><span>Completeness Beta - 79%</span><span><span className="maturity-lts maturity-lts-partial">Partial - 5</span></span></div>
|
||||
|
||||
<div className="maturity-category-list">
|
||||
<div className="maturity-category-row maturity-category-row-header"><span>Area</span><span>Coverage</span><span>Quality</span><span>Completeness</span><span>Docs</span></div>
|
||||
@@ -1766,7 +1766,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">WSL Setup</span>
|
||||
<span>6 capabilities / LTS-supported</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>3%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "3%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-alpha"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-alpha">Alpha</span><span>67%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "67%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Windows](/platforms/windows), [Getting Started](/start/getting-started)</div>
|
||||
@@ -1776,7 +1776,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">CLI</span>
|
||||
<span>8 capabilities / LTS-supported</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>3%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "3%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-alpha"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-alpha">Alpha</span><span>67%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "67%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Windows](/platforms/windows), [Getting Started](/start/getting-started), [Updating](/install/updating), [Onboard](/cli/onboard), [Doctor](/cli/doctor), [Status](/cli/status), [Logs](/cli/logs)</div>
|
||||
@@ -1786,7 +1786,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Gateway Service Lifecycle</span>
|
||||
<span>10 capabilities / LTS-supported</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>3%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "3%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-alpha"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-alpha">Alpha</span><span>67%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "67%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Windows](/platforms/windows), [Index](/gateway/index), [Doctor](/gateway/doctor)</div>
|
||||
@@ -1796,7 +1796,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Gateway Access and Exposure</span>
|
||||
<span>11 capabilities / LTS-supported</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>3%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "3%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-alpha"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-alpha">Alpha</span><span>67%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "67%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Authentication](/gateway/authentication), [Secrets](/gateway/secrets), [Remote](/gateway/remote), [Exposure Runbook](/gateway/security/exposure-runbook), [Windows](/platforms/windows)</div>
|
||||
@@ -1806,7 +1806,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Diagnostics and Repair</span>
|
||||
<span>6 capabilities / LTS-supported</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>3%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "3%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>38%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "38%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Windows](/platforms/windows), [Status](/cli/status), [Logs](/cli/logs), [Doctor](/cli/doctor), [Doctor](/gateway/doctor)</div>
|
||||
@@ -1816,7 +1816,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Browser and Control UI</span>
|
||||
<span>6 capabilities</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>3%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "3%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-alpha"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-alpha">Alpha</span><span>67%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "67%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Browser Wsl2 Windows Remote Cdp Troubleshooting](/tools/browser-wsl2-windows-remote-cdp-troubleshooting), [Browser](/tools/browser), [Control Ui](/web/control-ui)</div>
|
||||
@@ -3276,7 +3276,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
|
||||
Core tools are documented, but host security and permission UX should stay under active scorecard review.
|
||||
|
||||
<div className="maturity-surface-rollup"><span>Coverage Experimental - 15%</span><span>Quality Beta - 75%</span><span>Completeness Beta - 79%</span><span><span className="maturity-lts maturity-lts-partial">Partial - 2</span></span></div>
|
||||
<div className="maturity-surface-rollup"><span>Coverage Experimental - 21%</span><span>Quality Beta - 75%</span><span>Completeness Beta - 79%</span><span><span className="maturity-lts maturity-lts-partial">Partial - 2</span></span></div>
|
||||
|
||||
<div className="maturity-category-list">
|
||||
<div className="maturity-category-row maturity-category-row-header"><span>Area</span><span>Coverage</span><span>Quality</span><span>Completeness</span><span>Docs</span></div>
|
||||
@@ -3285,7 +3285,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Browser Automation</span>
|
||||
<span>8 capabilities</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>15%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "15%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>13%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "13%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Browser Control](/tools/browser-control), [Testing](/help/testing), [Browser](/tools/browser), [Index](/gateway/security/index), [Audit Checks](/gateway/security/audit-checks)</div>
|
||||
@@ -3295,7 +3295,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Tool Invocation and Execution</span>
|
||||
<span>6 capabilities / LTS-supported</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>15%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "15%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-alpha"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-alpha">Alpha</span><span>50%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "50%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Exec](/tools/exec), [Background Process](/gateway/background-process), [Tools Invoke Http Api](/gateway/tools-invoke-http-api), [Operator Scopes](/gateway/operator-scopes), [Protocol](/gateway/protocol), [Exec Approvals](/tools/exec-approvals), [Exec Approvals Advanced](/tools/exec-approvals-advanced), [Elevated](/tools/elevated)</div>
|
||||
@@ -3305,7 +3305,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Sandbox and Tool Policy</span>
|
||||
<span>6 capabilities / LTS-supported</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>15%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "15%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-alpha"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-alpha">Alpha</span><span>68%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "68%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Sandboxing](/gateway/sandboxing), [Sandbox Vs Tool Policy Vs Elevated](/gateway/sandbox-vs-tool-policy-vs-elevated), [Multi Agent Sandbox Tools](/tools/multi-agent-sandbox-tools), [Codex Harness Reference](/plugins/codex-harness-reference), [Config Tools](/gateway/config-tools)</div>
|
||||
@@ -3319,7 +3319,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
|
||||
Deep docs, OAuth/subscription path, realtime voice, image, and compatibility behavior. Provider churn keeps this from Stable without release-scorecard proof.
|
||||
|
||||
<div className="maturity-surface-rollup"><span>Coverage Experimental - 8%</span><span>Quality Beta - 74%</span><span>Completeness Beta - 79%</span><span><span className="maturity-lts maturity-lts-partial">Partial - 3</span></span></div>
|
||||
<div className="maturity-surface-rollup"><span>Coverage Experimental - 26%</span><span>Quality Beta - 74%</span><span>Completeness Beta - 79%</span><span><span className="maturity-lts maturity-lts-partial">Partial - 3</span></span></div>
|
||||
|
||||
<div className="maturity-category-list">
|
||||
<div className="maturity-category-row maturity-category-row-header"><span>Area</span><span>Coverage</span><span>Quality</span><span>Completeness</span><span>Docs</span></div>
|
||||
@@ -3328,7 +3328,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Model and Auth</span>
|
||||
<span>6 capabilities / LTS-supported</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>8%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "8%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>44%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "44%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Openai](/providers/openai), [Codex Harness](/plugins/codex-harness), [Models](/concepts/models), [Oauth](/concepts/oauth), [Codex Harness Reference](/plugins/codex-harness-reference), [Auth Monitoring](/automation/auth-monitoring)</div>
|
||||
@@ -3338,7 +3338,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Responses and Tool Compatibility</span>
|
||||
<span>4 capabilities / LTS-supported</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>8%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "8%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>40%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "40%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Openai](/providers/openai), [Openresponses Http Api](/gateway/openresponses-http-api), [Openai Http Api](/gateway/openai-http-api), [Codex Native Plugins](/plugins/codex-native-plugins)</div>
|
||||
@@ -3348,7 +3348,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Native Codex Harness</span>
|
||||
<span>2 capabilities / LTS-supported</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>8%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "8%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>44%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "44%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Codex Harness](/plugins/codex-harness), [Codex Harness Runtime](/plugins/codex-harness-runtime), [Codex Harness Reference](/plugins/codex-harness-reference), [Codex Native Plugins](/plugins/codex-native-plugins)</div>
|
||||
@@ -3358,7 +3358,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Image and Multimodal Input</span>
|
||||
<span>2 capabilities</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>8%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "8%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-alpha"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-alpha">Alpha</span><span>67%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "67%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Openai](/providers/openai), [Image Generation](/tools/image-generation), [Images](/nodes/images)</div>
|
||||
@@ -3368,7 +3368,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Voice and Realtime Audio</span>
|
||||
<span>2 capabilities</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>8%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "8%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-alpha"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-alpha">Alpha</span><span>67%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "67%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Openai](/providers/openai), [Discord](/channels/discord), [Voice Call](/plugins/voice-call)</div>
|
||||
@@ -3382,7 +3382,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
|
||||
Multiple providers and docs exist. Needs quota/error/SSRF proof per provider family.
|
||||
|
||||
<div className="maturity-surface-rollup"><span>Coverage Experimental - 7%</span><span>Quality Beta - 74%</span><span>Completeness Beta - 79%</span><span><span className="maturity-lts maturity-lts-none">None</span></span></div>
|
||||
<div className="maturity-surface-rollup"><span>Coverage Experimental - 9%</span><span>Quality Beta - 74%</span><span>Completeness Beta - 79%</span><span><span className="maturity-lts maturity-lts-none">None</span></span></div>
|
||||
|
||||
<div className="maturity-category-list">
|
||||
<div className="maturity-category-row maturity-category-row-header"><span>Area</span><span>Coverage</span><span>Quality</span><span>Completeness</span><span>Docs</span></div>
|
||||
@@ -3391,7 +3391,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Search Providers</span>
|
||||
<span>19 capabilities</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>7%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "7%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>11%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "11%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Web](/tools/web), [Brave Search](/tools/brave-search), [Tavily](/tools/tavily), [Exa Search](/tools/exa-search), [Firecrawl](/tools/firecrawl), [Perplexity Search](/tools/perplexity-search), [Duckduckgo Search](/tools/duckduckgo-search), [Searxng Search](/tools/searxng-search), [Gemini Search](/tools/gemini-search), [Grok Search](/tools/grok-search), [Kimi Search](/tools/kimi-search), [Minimax Search](/tools/minimax-search), [Ollama Search](/tools/ollama-search), [Sdk Subpaths](/plugins/sdk-subpaths), [Sdk Overview](/plugins/sdk-overview), [Manifest](/plugins/manifest)</div>
|
||||
@@ -3401,7 +3401,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Setup and Diagnostics</span>
|
||||
<span>9 capabilities</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>7%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "7%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-alpha"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-alpha">Alpha</span><span>68%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "68%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Web](/tools/web), [Web Fetch](/tools/web-fetch), [Faq](/help/faq), [Api Usage Costs](/reference/api-usage-costs), [Brave Search](/tools/brave-search), [Perplexity Search](/tools/perplexity-search), [Tavily](/tools/tavily), [Firecrawl](/tools/firecrawl)</div>
|
||||
@@ -3411,7 +3411,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Network Safety</span>
|
||||
<span>4 capabilities</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>7%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "7%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>0%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "0%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-alpha"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-alpha">Alpha</span><span>68%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "68%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Web](/tools/web), [Web Fetch](/tools/web-fetch), [Firecrawl](/tools/firecrawl), [Searxng Search](/tools/searxng-search)</div>
|
||||
@@ -3421,7 +3421,7 @@ A surface is a product area such as Gateway runtime, Discord, or the macOS app.
|
||||
<span className="maturity-category-title">Tool Availability and Fetch</span>
|
||||
<span>11 capabilities</span>
|
||||
</div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>7%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "7%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-experimental"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-experimental">Experimental</span><span>25%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "25%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div><span className="maturity-score maturity-score-beta"><span className="maturity-score-label"><span className="maturity-level-pill maturity-level-beta">Beta</span><span>79%</span></span><span className="maturity-meter" aria-hidden="true"><span style={{ width: "79%" }} /></span></span></div>
|
||||
<div className="maturity-category-docs">[Config Tools](/gateway/config-tools), [Web Fetch](/tools/web-fetch), [Web](/tools/web), [Faq](/help/faq)</div>
|
||||
|
||||
@@ -737,6 +737,10 @@ outbound host generic and use the messaging adapter surface for provider rules:
|
||||
should be treated as `direct`, `group`, or `channel` before directory lookup.
|
||||
- `messaging.targetResolver.looksLikeId(raw, normalized)` tells core whether an
|
||||
input should skip straight to id-like resolution instead of directory search.
|
||||
- `messaging.targetResolver.reservedLiterals` lists bare words that are
|
||||
channel/session references for that provider. Resolution preserves configured
|
||||
directory entries before rejecting reserved literals, then fails closed on a
|
||||
directory miss.
|
||||
- `messaging.targetResolver.resolveTarget(...)` is the plugin fallback when
|
||||
core needs a final provider-owned resolution after normalization or after a
|
||||
directory miss.
|
||||
|
||||
@@ -115,6 +115,17 @@ before the thread starts.
|
||||
After changing Computer Use config, use `/new` or `/reset` in the affected chat
|
||||
before testing if an existing Codex thread has already started.
|
||||
|
||||
On macOS managed stdio startup, OpenClaw prefers the signed desktop Codex app
|
||||
bundle at `/Applications/Codex.app/Contents/Resources/codex` when it exists.
|
||||
That keeps Computer Use under the app bundle that owns the local desktop-control
|
||||
permissions. If the desktop app is not installed, OpenClaw falls back to the
|
||||
managed Codex binary installed beside the plugin. If an installed desktop app
|
||||
initializes with an unsupported app-server version, OpenClaw closes that child
|
||||
and retries the next managed binary candidate instead of letting a stale
|
||||
desktop app shadow the plugin-local fallback. Explicit `appServer.command`
|
||||
config or `OPENCLAW_CODEX_APP_SERVER_BIN` still overrides this managed
|
||||
selection.
|
||||
|
||||
## Commands
|
||||
|
||||
Use the `/codex computer-use` commands from any chat surface where the `codex`
|
||||
@@ -276,7 +287,13 @@ Codex app-server MCP status, or macOS permissions.
|
||||
**Status or a probe times out on `computer-use.list_apps`.** The plugin and MCP
|
||||
server are present, but the local Computer Use bridge did not answer. Quit or
|
||||
restart Codex Computer Use, relaunch Codex Desktop if needed, then retry in a
|
||||
fresh OpenClaw session.
|
||||
fresh OpenClaw session. If the host previously ran Computer Use through an older
|
||||
managed Codex app-server, refresh the installed plugin from the desktop bundled
|
||||
marketplace:
|
||||
|
||||
```text
|
||||
/codex computer-use install --source /Applications/Codex.app/Contents/Resources/plugins/openai-bundled
|
||||
```
|
||||
|
||||
**A Computer Use tool says `Native hook relay unavailable`.** The Codex-native
|
||||
tool hook could not reach an active OpenClaw relay through the local bridge or
|
||||
|
||||
@@ -155,9 +155,13 @@ shorthand before OpenClaw builds app-server start options, and unresolved
|
||||
structured SecretRefs fail before any token or header is sent. When native Codex
|
||||
plugins are configured, OpenClaw uses the connected app-server's plugin control
|
||||
plane to install or refresh those plugins and then refreshes app inventory so
|
||||
plugin-owned apps are visible to the Codex thread. Only connect OpenClaw to
|
||||
remote app-servers that are trusted to accept OpenClaw-managed plugin installs
|
||||
and app inventory refreshes.
|
||||
plugin-owned apps are visible to the Codex thread. `app/list` is still the
|
||||
authoritative inventory and metadata source, but OpenClaw policy decides whether
|
||||
`thread/start` sends `config.apps[appId].enabled = true` for a listed accessible
|
||||
app even if Codex currently marks it disabled. Unknown or missing app ids remain
|
||||
fail-closed; this path only activates marketplace plugins via `plugin/install`
|
||||
and refreshes inventory. Only connect OpenClaw to remote app-servers that are
|
||||
trusted to accept OpenClaw-managed plugin installs and app inventory refreshes.
|
||||
|
||||
## Approval and sandbox modes
|
||||
|
||||
|
||||
@@ -465,7 +465,13 @@ do not receive Gateway env API-key fallback; use an explicit auth profile or the
|
||||
remote app-server's own account.
|
||||
When native Codex plugins are configured, OpenClaw installs or refreshes those
|
||||
plugins through the connected app-server before exposing plugin-owned apps to
|
||||
the Codex thread.
|
||||
the Codex thread. `app/list` remains the source of truth for app ids,
|
||||
accessibility, and metadata, but OpenClaw owns the per-thread enablement
|
||||
decision: if policy allows a listed accessible app, OpenClaw sends
|
||||
`thread/start.config.apps[appId].enabled = true` even when `app/list` currently
|
||||
reports that app disabled. This path does not invent app installation for
|
||||
unknown ids; OpenClaw only activates marketplace plugins with `plugin/install`
|
||||
and then refreshes inventory.
|
||||
|
||||
If a subscription profile hits a Codex usage limit, OpenClaw records the reset
|
||||
time when Codex reports one and tries the next ordered auth profile for the same
|
||||
|
||||
@@ -110,6 +110,13 @@ When you pass a plugin id, OpenClaw reuses the tracked install spec. Stored
|
||||
dist-tags such as `@beta` and exact pinned versions continue to be used on
|
||||
later `update <plugin-id>` runs.
|
||||
|
||||
`openclaw plugins update --all` is the bulk maintenance path. It still respects
|
||||
ordinary tracked install specs, but trusted official OpenClaw plugin records can
|
||||
sync to the current official catalog target instead of staying on a stale exact
|
||||
official package. If `update.channel` is set to `beta`, that bulk official sync
|
||||
uses the beta-channel context. Use a targeted `update <plugin-id>` when you
|
||||
intentionally want to keep an exact or tagged official spec untouched.
|
||||
|
||||
For npm installs, you can pass an explicit package spec to switch the tracked
|
||||
record:
|
||||
|
||||
|
||||
@@ -739,7 +739,7 @@ Write colocated tests in `src/channel.test.ts`:
|
||||
describeMessageTool and action discovery
|
||||
</Card>
|
||||
<Card title="Target resolution" icon="crosshair" href="/plugins/architecture-internals#channel-target-resolution">
|
||||
inferTargetChatType, looksLikeId, resolveTarget
|
||||
inferTargetChatType, looksLikeId, reservedLiterals, resolveTarget
|
||||
</Card>
|
||||
<Card title="Runtime helpers" icon="settings" href="/plugins/sdk-runtime">
|
||||
TTS, STT, media, subagent via api.runtime
|
||||
|
||||
@@ -588,7 +588,7 @@ releases.
|
||||
| `plugin-sdk/reply-history` | Reply-history helpers | `createChannelHistoryWindow`; deprecated map-helper compatibility exports such as `buildPendingHistoryContextFromMap`, `recordPendingHistoryEntry`, and `clearHistoryEntriesIfEnabled` |
|
||||
| `plugin-sdk/reply-reference` | Reply reference planning | `createReplyReferencePlanner` |
|
||||
| `plugin-sdk/reply-chunking` | Reply chunk helpers | Text/markdown chunking helpers |
|
||||
| `plugin-sdk/session-store-runtime` | Session store helpers | Store path + updated-at helpers |
|
||||
| `plugin-sdk/session-store-runtime` | Session store helpers | Store path, updated-at, and reset-freshness helpers |
|
||||
| `plugin-sdk/state-paths` | State path helpers | State and OAuth dir helpers |
|
||||
| `plugin-sdk/routing` | Routing/session-key helpers | `resolveAgentRoute`, `buildAgentSessionKey`, `resolveDefaultAgentBoundAccountId`, session-key normalization helpers |
|
||||
| `plugin-sdk/status-helpers` | Channel status helpers | Channel/account status summary builders, runtime-state defaults, issue metadata helpers |
|
||||
|
||||
@@ -247,7 +247,7 @@ usage endpoint failed or returned no usable usage data.
|
||||
| `plugin-sdk/reply-history` | Shared short-window reply-history helpers. New message-turn code should use `createChannelHistoryWindow`; lower-level map helpers remain deprecated compatibility exports only |
|
||||
| `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`), bounded recent user/assistant transcript text reads by session identity, legacy session store path/session-key helpers, updated-at reads, and transition-only whole-store/file-path compatibility helpers |
|
||||
| `plugin-sdk/session-store-runtime` | Session workflow helpers (`getSessionEntry`, `listSessionEntries`, `patchSessionEntry`, `upsertSessionEntry`), reset freshness resolution for one session entry, bounded recent user/assistant transcript text reads by session identity, legacy session store path/session-key helpers, updated-at reads, and transition-only whole-store/file-path compatibility helpers |
|
||||
| `plugin-sdk/session-transcript-runtime` | Transcript identity, scoped target/read/write helpers, update publishing, write locks, and transcript memory hit keys |
|
||||
| `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 |
|
||||
@@ -307,7 +307,7 @@ usage endpoint failed or returned no usable usage data.
|
||||
| `plugin-sdk/inline-image-data-url-runtime` | Inline image data URL sanitizer and signature sniffing helpers without the broad media runtime surface |
|
||||
| `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/session-store-runtime` | Session-store and reset-freshness 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 |
|
||||
|
||||
@@ -211,6 +211,18 @@ each carrier call should start with fresh context, for example reception,
|
||||
booking, IVR, or Google Meet bridge flows where the same phone number may
|
||||
represent different meetings.
|
||||
|
||||
Voice Call stores generated session keys under the configured agent namespace
|
||||
(`agent:<agentId>:voice:*`) so call memory survives Gateway session-key
|
||||
canonicalization after restarts. Raw explicit integration keys use the same
|
||||
agent namespace. A canonical `agent:<configuredAgentId>:*` key keeps that owner,
|
||||
and its main aliases honor core `session.mainKey` and global scope. Foreign or
|
||||
malformed `agent:*` input is scoped as an opaque key under the configured agent;
|
||||
`global` and `unknown` remain global sentinels. Gateway startup promotes older
|
||||
raw keys in default or `{agentId}`-templated stores where the path proves one
|
||||
owner. In fixed custom stores, ambiguous legacy rows remain untouched because
|
||||
they do not contain enough information to choose an owner; new calls use
|
||||
canonical agent-scoped history.
|
||||
|
||||
## Realtime voice conversations
|
||||
|
||||
`realtime` selects a full-duplex realtime voice provider for live call
|
||||
|
||||
@@ -71,6 +71,11 @@ OpenProse registers `/prose` as a user-invocable skill command:
|
||||
`/prose run <handle/slug>` resolves to `https://p.prose.md/<handle>/<slug>`.
|
||||
Direct URLs are fetched as-is using the `web_fetch` tool.
|
||||
|
||||
Top-level remote runs are explicit. Remote imports inside a `.prose` program are
|
||||
transitive code dependencies: before OpenProse fetches any remote `use` target,
|
||||
it shows the resolved import list and requires the operator to reply exactly
|
||||
`approve remote prose imports` for that run.
|
||||
|
||||
## What it can do
|
||||
|
||||
- Multi-agent research and synthesis with explicit parallelism.
|
||||
@@ -167,9 +172,12 @@ User-level persistent agents live at:
|
||||
|
||||
## Security
|
||||
|
||||
Treat `.prose` files like code. Review them before running. Use OpenClaw tool
|
||||
allowlists and approval gates to control side effects. For deterministic,
|
||||
approval-gated workflows, compare with [Lobster](/tools/lobster).
|
||||
Treat `.prose` files like code. Review them before running, including remote
|
||||
`use` imports. Top-level `/prose run https://...` requests are explicit, but
|
||||
transitive remote imports require per-run approval before they are fetched or
|
||||
executed. Use OpenClaw tool allowlists and approval gates to control side
|
||||
effects. For deterministic, approval-gated workflows, compare with
|
||||
[Lobster](/tools/lobster).
|
||||
|
||||
## Related
|
||||
|
||||
|
||||
@@ -37,7 +37,6 @@ Scope intent:
|
||||
- `agents.defaults.memorySearch.remote.apiKey`
|
||||
- `agents.list[].tts.providers.*.apiKey`
|
||||
- `agents.list[].memorySearch.remote.apiKey`
|
||||
- `agents.list[].tools.exec.env.*`
|
||||
- `talk.providers.*.apiKey`
|
||||
- `talk.realtime.providers.*.apiKey`
|
||||
- `messages.tts.providers.*.apiKey`
|
||||
|
||||
@@ -29,13 +29,6 @@
|
||||
"secretShape": "secret_input",
|
||||
"optIn": true
|
||||
},
|
||||
{
|
||||
"id": "agents.list[].tools.exec.env.*",
|
||||
"configFile": "openclaw.json",
|
||||
"path": "agents.list[].tools.exec.env.*",
|
||||
"secretShape": "secret_input",
|
||||
"optIn": true
|
||||
},
|
||||
{
|
||||
"id": "agents.list[].tts.providers.*.apiKey",
|
||||
"configFile": "openclaw.json",
|
||||
|
||||
@@ -81,6 +81,7 @@ Session persistence has automatic maintenance controls (`session.maintenance`) f
|
||||
- `mode`: `enforce` (default) or `warn`
|
||||
- `pruneAfter`: stale-entry age cutoff (default `30d`)
|
||||
- `maxEntries`: cap entries in `sessions.json` (default `500`)
|
||||
- Short-lived gateway model-run probe retention is fixed at `24h`, but it is pressure-gated: it only removes stale strict probe rows when session-entry maintenance/cap pressure is reached. This applies only to strict explicit probe keys matching `agent:*:explicit:model-run-<uuid>` and runs before global stale-entry cleanup/capping when it runs.
|
||||
- `resetArchiveRetention`: retention for `*.reset.<timestamp>` transcript archives (default: same as `pruneAfter`; `false` disables cleanup)
|
||||
- `maxDiskBytes`: optional sessions-directory budget
|
||||
- `highWaterBytes`: optional target after cleanup (default `80%` of `maxDiskBytes`)
|
||||
@@ -90,7 +91,12 @@ Normal Gateway writes flow through a per-store session writer that serializes in
|
||||
Maintenance keeps durable external conversation pointers such as group sessions
|
||||
and thread-scoped chat sessions, but synthetic runtime entries for cron, hooks,
|
||||
heartbeat, ACP, and sub-agents can still be removed when they exceed the
|
||||
configured age, count, or disk budget.
|
||||
configured age, count, or disk budget. Gateway model-run probe sessions use the
|
||||
separate `24h` model-run retention only when their key exactly matches
|
||||
`agent:*:explicit:model-run-<uuid>`; other explicit sessions are not part of
|
||||
that retention. The model-run cleanup is applied only under session-entry cap
|
||||
pressure. Isolated cron runs keep their own `cron.sessionRetention` control,
|
||||
independent of model-run probe retention.
|
||||
|
||||
OpenClaw no longer creates automatic `sessions.json.bak.*` rotation backups during Gateway writes. The legacy `session.maintenance.rotateBytes` key is ignored and `openclaw doctor --fix` removes it from older configs.
|
||||
|
||||
|
||||
@@ -269,7 +269,7 @@ html.dark .nav-tabs-underline {
|
||||
|
||||
.maturity-summary-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
grid-template-columns: repeat(auto-fit, minmax(min(220px, 100%), 1fr));
|
||||
margin: 14px 0 20px;
|
||||
border-top: 1px solid color-mix(in oklab, rgb(var(--primary)) 18%, transparent);
|
||||
border-bottom: 1px solid color-mix(in oklab, rgb(var(--primary)) 18%, transparent);
|
||||
|
||||
@@ -22,8 +22,7 @@ Working directory for the command.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="env" type="object">
|
||||
Key/value environment overrides. Per-agent configured values are applied after
|
||||
these model-supplied values.
|
||||
Key/value environment overrides merged on top of the inherited environment.
|
||||
</ParamField>
|
||||
|
||||
<ParamField path="yieldMs" type="number" default="10000">
|
||||
@@ -90,7 +89,6 @@ Notes:
|
||||
`$OPENCLAW_STATE_DIR/cache/shell-snapshots/`, then sources that snapshot before each exec command.
|
||||
Secret-looking variables are excluded; sandbox and node exec do not use this snapshot. Set
|
||||
`OPENCLAW_EXEC_SHELL_SNAPSHOT=0` in the Gateway process environment to disable this snapshot path.
|
||||
Per-agent `tools.exec.inheritHostEnv: false` also disables it.
|
||||
- Host execution (`gateway`/`node`) rejects `env.PATH` and loader overrides (`LD_*`/`DYLD_*`) to
|
||||
prevent binary hijacking or injected code.
|
||||
- OpenClaw sets `OPENCLAW_SHELL=exec` in the spawned command environment (including PTY and sandbox execution) so shell/profile rules can detect exec-tool context.
|
||||
@@ -115,8 +113,6 @@ Notes:
|
||||
- `tools.exec.notifyOnExit` (default: true): when true, backgrounded exec sessions enqueue a system event and request a heartbeat on exit.
|
||||
- `tools.exec.approvalRunningNoticeMs` (default: 10000): emit a single "running" notice when an approval-gated exec runs longer than this (0 disables).
|
||||
- `tools.exec.timeoutSec` (default: 1800): default per-command exec timeout in seconds. Per-call `timeout` overrides it; per-call `timeout: 0` disables the exec process timeout.
|
||||
- `agents.list[].tools.exec.env`: credential-oriented environment values injected only into that agent's gateway/sandbox exec children. Values support SecretRefs; node-host exec rejects this map.
|
||||
- `agents.list[].tools.exec.inheritHostEnv` (default: true): set false to omit the Gateway process environment and shell-startup snapshot from Gateway-hosted exec. This is rejected for `host=node`; sandbox exec is already minimal.
|
||||
- `tools.exec.host` (default: `auto`; resolves to `sandbox` when sandbox runtime is active, `gateway` otherwise)
|
||||
- `tools.exec.security` (default: `deny` for sandbox, `full` for gateway + node when unset)
|
||||
- `tools.exec.ask` (default: `off`)
|
||||
@@ -145,9 +141,7 @@ Example:
|
||||
|
||||
### PATH handling
|
||||
|
||||
- `host=gateway`: normally merges your login-shell `PATH` into the exec environment. With
|
||||
`agents.list[].tools.exec.inheritHostEnv: false`, this merge is skipped; use an absolute command or
|
||||
`tools.exec.pathPrepend`. `env.PATH` overrides are
|
||||
- `host=gateway`: merges your login-shell `PATH` into the exec environment. `env.PATH` overrides are
|
||||
rejected for host execution. The daemon itself still runs with a minimal `PATH`:
|
||||
- macOS: `/opt/homebrew/bin`, `/usr/local/bin`, `/usr/bin`, `/bin`
|
||||
- Linux: `/usr/local/bin`, `/usr/bin`, `/bin`
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
* Azure Speech REST helpers. They normalize endpoints, build SSML, list voices,
|
||||
* and synthesize speech with response-size and SSRF guards.
|
||||
*/
|
||||
import { assertOkOrThrowProviderError } from "openclaw/plugin-sdk/provider-http";
|
||||
import {
|
||||
assertOkOrThrowProviderError,
|
||||
readProviderJsonResponse,
|
||||
} from "openclaw/plugin-sdk/provider-http";
|
||||
import { readResponseWithLimit } from "openclaw/plugin-sdk/response-limit-runtime";
|
||||
import type { SpeechVoiceOption } from "openclaw/plugin-sdk/speech-core";
|
||||
import { trimToUndefined } from "openclaw/plugin-sdk/speech-core";
|
||||
@@ -160,7 +163,10 @@ export async function listAzureSpeechVoices(params: {
|
||||
|
||||
try {
|
||||
await assertOkOrThrowProviderError(response, "Azure Speech voices API error");
|
||||
const voices = (await response.json()) as AzureSpeechVoiceEntry[];
|
||||
const voices = await readProviderJsonResponse<AzureSpeechVoiceEntry[]>(
|
||||
response,
|
||||
"azure-speech.voices",
|
||||
);
|
||||
return Array.isArray(voices)
|
||||
? voices
|
||||
.filter((voice) => !isDeprecatedVoice(voice))
|
||||
|
||||
@@ -1,12 +1,70 @@
|
||||
// Byteplus tests cover video generation provider plugin behavior.
|
||||
import {
|
||||
getProviderHttpMocks,
|
||||
installProviderHttpMockCleanup,
|
||||
} from "openclaw/plugin-sdk/provider-http-test-mocks";
|
||||
import { expectExplicitVideoGenerationCapabilities } from "openclaw/plugin-sdk/provider-test-contracts";
|
||||
import { beforeAll, describe, expect, it, vi } from "vitest";
|
||||
import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const { postJsonRequestMock, fetchWithTimeoutMock } = getProviderHttpMocks();
|
||||
// Submit/poll transport is mocked locally so each test can inject the BytePlus task JSON
|
||||
// bodies, while readProviderJsonResponse is kept REAL (via importActual) so the byte-bounded
|
||||
// reader actually streams and cancels oversized bodies under test instead of a stub.
|
||||
const { postJsonRequestMock, fetchWithTimeoutMock, resolveApiKeyForProviderMock } = vi.hoisted(
|
||||
() => ({
|
||||
postJsonRequestMock: vi.fn(),
|
||||
fetchWithTimeoutMock: vi.fn(),
|
||||
resolveApiKeyForProviderMock: vi.fn(async () => ({ apiKey: "provider-key" })),
|
||||
}),
|
||||
);
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/provider-auth-runtime", () => ({
|
||||
resolveApiKeyForProvider: resolveApiKeyForProviderMock,
|
||||
}));
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/provider-http", async (importActual) => {
|
||||
const actual = await importActual<typeof import("openclaw/plugin-sdk/provider-http")>();
|
||||
const resolveTimeoutMs = (timeoutMs: unknown): number =>
|
||||
typeof timeoutMs === "function" ? (timeoutMs() as number) : ((timeoutMs as number) ?? 60_000);
|
||||
return {
|
||||
// REAL byte-bounded JSON reader under test — not stubbed.
|
||||
readProviderJsonResponse: actual.readProviderJsonResponse,
|
||||
postJsonRequest: postJsonRequestMock,
|
||||
fetchProviderOperationResponse: async (params: {
|
||||
url: string;
|
||||
init?: RequestInit;
|
||||
timeoutMs?: unknown;
|
||||
fetchFn: typeof fetch;
|
||||
}) => fetchWithTimeoutMock(params.url, params.init ?? {}, resolveTimeoutMs(params.timeoutMs)),
|
||||
fetchProviderDownloadResponse: async (params: {
|
||||
url: string;
|
||||
init?: RequestInit;
|
||||
timeoutMs?: unknown;
|
||||
fetchFn: typeof fetch;
|
||||
}) => fetchWithTimeoutMock(params.url, params.init ?? {}, resolveTimeoutMs(params.timeoutMs)),
|
||||
assertOkOrThrowHttpError: async () => {},
|
||||
createProviderOperationDeadline: ({
|
||||
label,
|
||||
timeoutMs,
|
||||
}: {
|
||||
label: string;
|
||||
timeoutMs?: number;
|
||||
}) => ({ label, timeoutMs }),
|
||||
createProviderOperationTimeoutResolver:
|
||||
({ defaultTimeoutMs }: { defaultTimeoutMs: number }) =>
|
||||
() =>
|
||||
defaultTimeoutMs,
|
||||
resolveProviderOperationTimeoutMs: ({ defaultTimeoutMs }: { defaultTimeoutMs: number }) =>
|
||||
defaultTimeoutMs,
|
||||
resolveProviderHttpRequestConfig: (params: {
|
||||
baseUrl?: string;
|
||||
defaultBaseUrl: string;
|
||||
allowPrivateNetwork?: boolean;
|
||||
defaultHeaders?: Record<string, string>;
|
||||
}) => ({
|
||||
baseUrl: params.baseUrl ?? params.defaultBaseUrl,
|
||||
allowPrivateNetwork: params.allowPrivateNetwork === true,
|
||||
headers: new Headers(params.defaultHeaders),
|
||||
dispatcherPolicy: undefined,
|
||||
}),
|
||||
waitProviderOperationPollInterval: async () => {},
|
||||
};
|
||||
});
|
||||
|
||||
let buildBytePlusVideoGenerationProvider: typeof import("./video-generation-provider.js").buildBytePlusVideoGenerationProvider;
|
||||
|
||||
@@ -14,20 +72,22 @@ beforeAll(async () => {
|
||||
({ buildBytePlusVideoGenerationProvider } = await import("./video-generation-provider.js"));
|
||||
});
|
||||
|
||||
installProviderHttpMockCleanup();
|
||||
afterEach(() => {
|
||||
postJsonRequestMock.mockReset();
|
||||
fetchWithTimeoutMock.mockReset();
|
||||
resolveApiKeyForProviderMock.mockClear();
|
||||
});
|
||||
|
||||
function mockSuccessfulBytePlusTask(params?: { model?: string }) {
|
||||
postJsonRequestMock.mockResolvedValue({
|
||||
response: {
|
||||
json: async () => ({
|
||||
id: "task_123",
|
||||
}),
|
||||
},
|
||||
response: streamedJsonResponse({
|
||||
id: "task_123",
|
||||
}),
|
||||
release: vi.fn(async () => {}),
|
||||
});
|
||||
fetchWithTimeoutMock
|
||||
.mockResolvedValueOnce({
|
||||
json: async () => ({
|
||||
.mockResolvedValueOnce(
|
||||
streamedJsonResponse({
|
||||
id: "task_123",
|
||||
status: "succeeded",
|
||||
content: {
|
||||
@@ -35,7 +95,7 @@ function mockSuccessfulBytePlusTask(params?: { model?: string }) {
|
||||
},
|
||||
model: params?.model ?? "seedance-1-0-lite-t2v-250428",
|
||||
}),
|
||||
})
|
||||
)
|
||||
.mockResolvedValueOnce({
|
||||
headers: new Headers({ "content-type": "video/webm" }),
|
||||
arrayBuffer: async () => Buffer.from("webm-bytes"),
|
||||
@@ -77,6 +137,53 @@ function streamedVideoResponse(bytes: string): Response {
|
||||
);
|
||||
}
|
||||
|
||||
// BytePlus submit/poll task JSON is now read through the byte-bounded reader, so the
|
||||
// mocked responses must expose a real readable body (not just a json() shortcut).
|
||||
function streamedJsonResponse(payload: unknown): Response {
|
||||
return new Response(
|
||||
new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue(new TextEncoder().encode(JSON.stringify(payload)));
|
||||
controller.close();
|
||||
},
|
||||
}),
|
||||
{ status: 200, headers: { "content-type": "application/json" } },
|
||||
);
|
||||
}
|
||||
|
||||
// Builds a JSON body larger than the shared 16 MiB readProviderJsonResponse cap so the
|
||||
// bounded reader cancels the stream mid-flight; if the cap were removed the reader would
|
||||
// buffer the whole advertised payload before parsing. Tracks how many bytes were pulled
|
||||
// and whether the stream was canceled so callers can assert the body was not fully read.
|
||||
function makeOversizedJsonStream(): {
|
||||
body: ReadableStream<Uint8Array>;
|
||||
maxBytes: number;
|
||||
totalBytes: number;
|
||||
state: { bytesPulled: number; canceled: boolean };
|
||||
} {
|
||||
const maxBytes = 16 * 1024 * 1024; // matches PROVIDER_JSON_RESPONSE_MAX_BYTES.
|
||||
const ONE_MIB = 1024 * 1024;
|
||||
const TOTAL_CHUNKS = 32; // 32 MiB advertised body, double the cap.
|
||||
const chunk = new Uint8Array(ONE_MIB);
|
||||
const state = { bytesPulled: 0, canceled: false };
|
||||
let pulled = 0;
|
||||
const body = new ReadableStream<Uint8Array>({
|
||||
pull(controller) {
|
||||
if (pulled >= TOTAL_CHUNKS) {
|
||||
controller.close();
|
||||
return;
|
||||
}
|
||||
pulled += 1;
|
||||
state.bytesPulled += chunk.length;
|
||||
controller.enqueue(chunk);
|
||||
},
|
||||
cancel() {
|
||||
state.canceled = true;
|
||||
},
|
||||
});
|
||||
return { body, maxBytes, totalBytes: TOTAL_CHUNKS * ONE_MIB, state };
|
||||
}
|
||||
|
||||
describe("byteplus video generation provider", () => {
|
||||
it("declares explicit mode capabilities", () => {
|
||||
expectExplicitVideoGenerationCapabilities(buildBytePlusVideoGenerationProvider());
|
||||
@@ -110,21 +217,19 @@ describe("byteplus video generation provider", () => {
|
||||
|
||||
it("rejects generated video downloads that exceed the configured media cap", async () => {
|
||||
postJsonRequestMock.mockResolvedValue({
|
||||
response: {
|
||||
json: async () => ({ id: "task_too_large" }),
|
||||
},
|
||||
response: streamedJsonResponse({ id: "task_too_large" }),
|
||||
release: vi.fn(async () => {}),
|
||||
});
|
||||
fetchWithTimeoutMock
|
||||
.mockResolvedValueOnce({
|
||||
json: async () => ({
|
||||
.mockResolvedValueOnce(
|
||||
streamedJsonResponse({
|
||||
id: "task_too_large",
|
||||
status: "succeeded",
|
||||
content: {
|
||||
video_url: "https://example.com/too-large.mp4",
|
||||
},
|
||||
}),
|
||||
})
|
||||
)
|
||||
.mockResolvedValueOnce(streamedVideoResponse("too-large"));
|
||||
|
||||
const provider = buildBytePlusVideoGenerationProvider();
|
||||
@@ -222,16 +327,14 @@ describe("byteplus video generation provider", () => {
|
||||
|
||||
it("drops malformed response duration metadata", async () => {
|
||||
postJsonRequestMock.mockResolvedValue({
|
||||
response: {
|
||||
json: async () => ({
|
||||
id: "task_123",
|
||||
}),
|
||||
},
|
||||
response: streamedJsonResponse({
|
||||
id: "task_123",
|
||||
}),
|
||||
release: vi.fn(async () => {}),
|
||||
});
|
||||
fetchWithTimeoutMock
|
||||
.mockResolvedValueOnce({
|
||||
json: async () => ({
|
||||
.mockResolvedValueOnce(
|
||||
streamedJsonResponse({
|
||||
id: "task_123",
|
||||
status: "succeeded",
|
||||
content: {
|
||||
@@ -239,7 +342,7 @@ describe("byteplus video generation provider", () => {
|
||||
},
|
||||
duration: 1.5,
|
||||
}),
|
||||
})
|
||||
)
|
||||
.mockResolvedValueOnce({
|
||||
headers: new Headers({ "content-type": "video/mp4" }),
|
||||
arrayBuffer: async () => Buffer.from("mp4-bytes"),
|
||||
@@ -259,11 +362,15 @@ describe("byteplus video generation provider", () => {
|
||||
it("reports malformed create JSON with a provider-owned error", async () => {
|
||||
const release = vi.fn(async () => {});
|
||||
postJsonRequestMock.mockResolvedValue({
|
||||
response: {
|
||||
json: async () => {
|
||||
throw new SyntaxError("bad json");
|
||||
},
|
||||
},
|
||||
response: new Response(
|
||||
new ReadableStream({
|
||||
start(controller) {
|
||||
controller.enqueue(new TextEncoder().encode("{ not valid json"));
|
||||
controller.close();
|
||||
},
|
||||
}),
|
||||
{ status: 200, headers: { "content-type": "application/json" } },
|
||||
),
|
||||
release,
|
||||
});
|
||||
|
||||
@@ -281,19 +388,17 @@ describe("byteplus video generation provider", () => {
|
||||
|
||||
it("rejects status responses missing a task status", async () => {
|
||||
postJsonRequestMock.mockResolvedValue({
|
||||
response: {
|
||||
json: async () => ({ id: "task_missing_status" }),
|
||||
},
|
||||
response: streamedJsonResponse({ id: "task_missing_status" }),
|
||||
release: vi.fn(async () => {}),
|
||||
});
|
||||
fetchWithTimeoutMock.mockResolvedValueOnce({
|
||||
json: async () => ({
|
||||
fetchWithTimeoutMock.mockResolvedValueOnce(
|
||||
streamedJsonResponse({
|
||||
id: "task_missing_status",
|
||||
content: {
|
||||
video_url: "https://example.com/byteplus.mp4",
|
||||
},
|
||||
}),
|
||||
});
|
||||
);
|
||||
|
||||
const provider = buildBytePlusVideoGenerationProvider();
|
||||
await expect(
|
||||
@@ -308,18 +413,16 @@ describe("byteplus video generation provider", () => {
|
||||
|
||||
it("rejects malformed completed content", async () => {
|
||||
postJsonRequestMock.mockResolvedValue({
|
||||
response: {
|
||||
json: async () => ({ id: "task_malformed_content" }),
|
||||
},
|
||||
response: streamedJsonResponse({ id: "task_malformed_content" }),
|
||||
release: vi.fn(async () => {}),
|
||||
});
|
||||
fetchWithTimeoutMock.mockResolvedValueOnce({
|
||||
json: async () => ({
|
||||
fetchWithTimeoutMock.mockResolvedValueOnce(
|
||||
streamedJsonResponse({
|
||||
id: "task_malformed_content",
|
||||
status: "succeeded",
|
||||
content: ["https://example.com/byteplus.mp4"],
|
||||
}),
|
||||
});
|
||||
);
|
||||
|
||||
const provider = buildBytePlusVideoGenerationProvider();
|
||||
await expect(
|
||||
@@ -331,4 +434,61 @@ describe("byteplus video generation provider", () => {
|
||||
}),
|
||||
).rejects.toThrow("BytePlus video generation completed with malformed content");
|
||||
});
|
||||
|
||||
it("bounds the submit task JSON body and cancels an oversized stream", async () => {
|
||||
const stream = makeOversizedJsonStream();
|
||||
const release = vi.fn(async () => {});
|
||||
postJsonRequestMock.mockResolvedValue({
|
||||
response: new Response(stream.body, {
|
||||
status: 200,
|
||||
headers: { "content-type": "application/json" },
|
||||
}),
|
||||
release,
|
||||
});
|
||||
|
||||
const provider = buildBytePlusVideoGenerationProvider();
|
||||
await expect(
|
||||
provider.generateVideo({
|
||||
provider: "byteplus",
|
||||
model: "seedance-1-0-lite-t2v-250428",
|
||||
prompt: "oversized submit response",
|
||||
cfg: {},
|
||||
}),
|
||||
).rejects.toThrow(
|
||||
`BytePlus video generation failed: JSON response exceeds ${stream.maxBytes} bytes`,
|
||||
);
|
||||
expect(stream.state.canceled).toBe(true);
|
||||
// Only the bounded prefix is pulled, never the full advertised stream.
|
||||
expect(stream.state.bytesPulled).toBeLessThan(stream.totalBytes);
|
||||
// The submit request must still be released even though the body overflowed.
|
||||
expect(release).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("bounds the poll status JSON body and cancels an oversized stream", async () => {
|
||||
postJsonRequestMock.mockResolvedValue({
|
||||
response: streamedJsonResponse({ id: "task_oversized_poll" }),
|
||||
release: vi.fn(async () => {}),
|
||||
});
|
||||
const stream = makeOversizedJsonStream();
|
||||
fetchWithTimeoutMock.mockResolvedValueOnce(
|
||||
new Response(stream.body, {
|
||||
status: 200,
|
||||
headers: { "content-type": "application/json" },
|
||||
}),
|
||||
);
|
||||
|
||||
const provider = buildBytePlusVideoGenerationProvider();
|
||||
await expect(
|
||||
provider.generateVideo({
|
||||
provider: "byteplus",
|
||||
model: "seedance-1-0-lite-t2v-250428",
|
||||
prompt: "oversized poll response",
|
||||
cfg: {},
|
||||
}),
|
||||
).rejects.toThrow(
|
||||
`BytePlus video status request failed: JSON response exceeds ${stream.maxBytes} bytes`,
|
||||
);
|
||||
expect(stream.state.canceled).toBe(true);
|
||||
expect(stream.state.bytesPulled).toBeLessThan(stream.totalBytes);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
fetchProviderDownloadResponse,
|
||||
fetchProviderOperationResponse,
|
||||
postJsonRequest,
|
||||
readProviderJsonResponse,
|
||||
resolveProviderOperationTimeoutMs,
|
||||
resolveProviderHttpRequestConfig,
|
||||
waitProviderOperationPollInterval,
|
||||
@@ -55,16 +56,13 @@ type BytePlusTaskResponse = {
|
||||
|
||||
type BytePlusTaskStatus = "running" | "failed" | "queued" | "succeeded" | "cancelled";
|
||||
|
||||
async function readBytePlusJsonResponse<T>(
|
||||
response: Pick<Response, "json">,
|
||||
label: string,
|
||||
): Promise<T> {
|
||||
let payload: unknown;
|
||||
try {
|
||||
payload = await response.json();
|
||||
} catch (cause) {
|
||||
throw new Error(`${label}: malformed JSON response`, { cause });
|
||||
}
|
||||
async function readBytePlusJsonResponse<T>(response: Response, label: string): Promise<T> {
|
||||
// BytePlus submit/poll task bodies are read through the shared byte-bounded reader
|
||||
// (readResponseWithLimit, via readProviderJsonResponse) so a hostile or buggy endpoint
|
||||
// that streams an unbounded JSON body cannot force the runtime to buffer the whole
|
||||
// payload before parsing. Overflow cancels the stream and throws a bounded error;
|
||||
// malformed JSON keeps the existing `${label}: malformed JSON response` wrapping.
|
||||
const payload = await readProviderJsonResponse<unknown>(response, label);
|
||||
if (!isRecord(payload)) {
|
||||
throw new Error(`${label}: malformed JSON response`);
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
readCodexNotificationItem,
|
||||
readNotificationItemId,
|
||||
shouldDisarmAssistantCompletionIdleWatch,
|
||||
updateActiveCompletionBlockerItemIds,
|
||||
updateActiveTurnItemIds,
|
||||
} from "./attempt-notifications.js";
|
||||
import { CODEX_POST_REASONING_REPLY_IDLE_TIMEOUT_MS } from "./attempt-timeouts.js";
|
||||
@@ -92,6 +93,7 @@ export function applyCodexTurnNotificationState(params: {
|
||||
currentPromptTexts: string[];
|
||||
turnWatches: CodexAttemptTurnWatchController;
|
||||
activeTurnItemIds: Set<string>;
|
||||
activeCompletionBlockerItemIds: Set<string>;
|
||||
activeAppServerTurnRequests: number;
|
||||
pendingOpenClawDynamicToolCompletionIds: Set<string>;
|
||||
turnCrossedToolHandoff: boolean;
|
||||
@@ -121,6 +123,7 @@ export function applyCodexTurnNotificationState(params: {
|
||||
});
|
||||
params.onReportExecutionNotification(notification);
|
||||
updateActiveTurnItemIds(notification, params.activeTurnItemIds);
|
||||
updateActiveCompletionBlockerItemIds(notification, params.activeCompletionBlockerItemIds);
|
||||
if (notification.method === "item/completed" && params.activeTurnItemIds.size === 0) {
|
||||
params.onScheduleTerminalDynamicToolReleaseCheck();
|
||||
}
|
||||
|
||||
@@ -63,6 +63,45 @@ export function updateActiveTurnItemIds(
|
||||
activeItemIds.delete(itemId);
|
||||
}
|
||||
|
||||
export function updateActiveCompletionBlockerItemIds(
|
||||
notification: CodexServerNotification,
|
||||
activeItemIds: Set<string>,
|
||||
): void {
|
||||
if (notification.method !== "item/started" && notification.method !== "item/completed") {
|
||||
return;
|
||||
}
|
||||
const itemId = readNotificationItemId(notification);
|
||||
if (!itemId) {
|
||||
return;
|
||||
}
|
||||
if (notification.method === "item/completed") {
|
||||
activeItemIds.delete(itemId);
|
||||
return;
|
||||
}
|
||||
const item = readCodexNotificationItem(notification.params);
|
||||
if (item && isCompletionBlockingItem(item)) {
|
||||
activeItemIds.add(itemId);
|
||||
}
|
||||
}
|
||||
|
||||
function isCompletionBlockingItem(item: CodexThreadItem): boolean {
|
||||
// Codex emits paired item/started and item/completed notifications for these
|
||||
// execution items. Completion must not time out while any pair is still open.
|
||||
switch (item.type) {
|
||||
case "collabAgentToolCall":
|
||||
case "commandExecution":
|
||||
case "dynamicToolCall":
|
||||
case "fileChange":
|
||||
case "imageGeneration":
|
||||
case "imageView":
|
||||
case "mcpToolCall":
|
||||
case "webSearch":
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function isCompletedAssistantNotification(notification: CodexServerNotification): boolean {
|
||||
if (!isJsonObject(notification.params)) {
|
||||
return false;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Codex tests cover attempt turn watches plugin behavior.
|
||||
import { MAX_TIMER_TIMEOUT_MS } from "openclaw/plugin-sdk/number-runtime";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { updateActiveCompletionBlockerItemIds } from "./attempt-notifications.js";
|
||||
import { createCodexAttemptTurnWatchController } from "./attempt-turn-watches.js";
|
||||
|
||||
describe("Codex app-server attempt turn watches", () => {
|
||||
@@ -23,6 +24,7 @@ describe("Codex app-server attempt turn watches", () => {
|
||||
let terminalQueued = false;
|
||||
let activeRequests = 0;
|
||||
let activeItems = 0;
|
||||
let activeCompletionBlockers = 0;
|
||||
const interrupts: Array<Record<string, unknown>> = [];
|
||||
const timeouts: Array<Record<string, unknown>> = [];
|
||||
const events: Array<{ name: string; fields: Record<string, unknown> }> = [];
|
||||
@@ -36,6 +38,7 @@ describe("Codex app-server attempt turn watches", () => {
|
||||
isTerminalTurnNotificationQueued: () => terminalQueued,
|
||||
getActiveAppServerTurnRequests: () => activeRequests,
|
||||
getActiveTurnItemCount: () => activeItems,
|
||||
getActiveCompletionBlockerItemCount: () => activeCompletionBlockers,
|
||||
turnCompletionIdleTimeoutMs: 10,
|
||||
turnAssistantCompletionIdleTimeoutMs: 10,
|
||||
turnAttemptIdleTimeoutMs: 10,
|
||||
@@ -69,6 +72,9 @@ describe("Codex app-server attempt turn watches", () => {
|
||||
set activeItems(value: number) {
|
||||
activeItems = value;
|
||||
},
|
||||
set activeCompletionBlockers(value: number) {
|
||||
activeCompletionBlockers = value;
|
||||
},
|
||||
interrupts,
|
||||
timeouts,
|
||||
events,
|
||||
@@ -155,6 +161,32 @@ describe("Codex app-server attempt turn watches", () => {
|
||||
expect(harness.abortController.signal.aborted).toBe(false);
|
||||
});
|
||||
|
||||
it("waits for active completion blocker items before firing completion idle timeout", () => {
|
||||
const harness = createController();
|
||||
harness.activeCompletionBlockers = 1;
|
||||
|
||||
harness.controller.touchActivity("request:mcpServer/elicitation/request:response", {
|
||||
arm: true,
|
||||
});
|
||||
vi.advanceTimersByTime(10);
|
||||
|
||||
expect(harness.timeouts).toEqual([]);
|
||||
expect(harness.abortController.signal.aborted).toBe(false);
|
||||
|
||||
harness.activeCompletionBlockers = 0;
|
||||
harness.controller.touchActivity("notification:item/completed");
|
||||
vi.advanceTimersByTime(10);
|
||||
|
||||
expect(harness.timeouts).toMatchObject([
|
||||
{
|
||||
kind: "completion",
|
||||
idleMs: 10,
|
||||
timeoutMs: 10,
|
||||
lastActivityReason: "notification:item/completed",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("releases a completed assistant item after the assistant idle guard expires", () => {
|
||||
const harness = createController();
|
||||
|
||||
@@ -214,3 +246,41 @@ describe("Codex app-server attempt turn watches", () => {
|
||||
expect(harness.abortController.signal.reason).toBe("turn_progress_idle_timeout");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Codex completion blocker item tracking", () => {
|
||||
it.each([
|
||||
"collabAgentToolCall",
|
||||
"commandExecution",
|
||||
"dynamicToolCall",
|
||||
"fileChange",
|
||||
"imageGeneration",
|
||||
"imageView",
|
||||
"mcpToolCall",
|
||||
"webSearch",
|
||||
])("tracks the %s lifecycle", (type) => {
|
||||
const activeItemIds = new Set<string>();
|
||||
updateActiveCompletionBlockerItemIds(
|
||||
{ method: "item/started", params: { item: { id: "item-1", type } } },
|
||||
activeItemIds,
|
||||
);
|
||||
expect(activeItemIds).toEqual(new Set(["item-1"]));
|
||||
|
||||
updateActiveCompletionBlockerItemIds(
|
||||
{ method: "item/completed", params: { item: { id: "item-1", type } } },
|
||||
activeItemIds,
|
||||
);
|
||||
expect(activeItemIds).toEqual(new Set());
|
||||
});
|
||||
|
||||
it.each(["agentMessage", "contextCompaction", "plan", "reasoning", "subAgentActivity"])(
|
||||
"does not track the %s lifecycle",
|
||||
(type) => {
|
||||
const activeItemIds = new Set<string>();
|
||||
updateActiveCompletionBlockerItemIds(
|
||||
{ method: "item/started", params: { item: { id: "item-1", type } } },
|
||||
activeItemIds,
|
||||
);
|
||||
expect(activeItemIds).toEqual(new Set());
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -36,6 +36,7 @@ export function createCodexAttemptTurnWatchController(params: {
|
||||
isTerminalTurnNotificationQueued: () => boolean;
|
||||
getActiveAppServerTurnRequests: () => number;
|
||||
getActiveTurnItemCount: () => number;
|
||||
getActiveCompletionBlockerItemCount: () => number;
|
||||
turnCompletionIdleTimeoutMs: number;
|
||||
turnAssistantCompletionIdleTimeoutMs: number;
|
||||
turnAttemptIdleTimeoutMs: number;
|
||||
@@ -121,7 +122,8 @@ export function createCodexAttemptTurnWatchController(params: {
|
||||
params.isCompleted() ||
|
||||
params.signal.aborted ||
|
||||
!completionIdleWatchArmed ||
|
||||
params.getActiveAppServerTurnRequests() > 0
|
||||
params.getActiveAppServerTurnRequests() > 0 ||
|
||||
params.getActiveCompletionBlockerItemCount() > 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
@@ -183,7 +185,8 @@ export function createCodexAttemptTurnWatchController(params: {
|
||||
params.isTerminalTurnNotificationQueued() ||
|
||||
params.signal.aborted ||
|
||||
!completionIdleWatchArmed ||
|
||||
params.getActiveAppServerTurnRequests() > 0
|
||||
params.getActiveAppServerTurnRequests() > 0 ||
|
||||
params.getActiveCompletionBlockerItemCount() > 0
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
@@ -302,7 +305,8 @@ export function createCodexAttemptTurnWatchController(params: {
|
||||
params.isTerminalTurnNotificationQueued() ||
|
||||
params.signal.aborted ||
|
||||
!completionIdleWatchArmed ||
|
||||
params.getActiveAppServerTurnRequests() > 0
|
||||
params.getActiveAppServerTurnRequests() > 0 ||
|
||||
params.getActiveCompletionBlockerItemCount() > 0
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -639,6 +639,15 @@ function assertSupportedCodexAppServerVersion(response: CodexInitializeResponse)
|
||||
return detectedVersion;
|
||||
}
|
||||
|
||||
export function isUnsupportedCodexAppServerVersionError(error: unknown): boolean {
|
||||
return (
|
||||
error instanceof Error &&
|
||||
error.message.startsWith(
|
||||
`Codex app-server ${MIN_CODEX_APP_SERVER_VERSION} or newer is required`,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function buildCodexAppServerRuntimeIdentity(
|
||||
response: CodexInitializeResponse,
|
||||
serverVersion: string,
|
||||
|
||||
@@ -167,6 +167,7 @@ export type CodexAppServerStartOptions = {
|
||||
transport: CodexAppServerTransportMode;
|
||||
command: string;
|
||||
commandSource?: CodexAppServerCommandSource;
|
||||
managedFallbackCommandPaths?: string[];
|
||||
args: string[];
|
||||
url?: string;
|
||||
authToken?: string;
|
||||
@@ -332,7 +333,9 @@ const codexAppServerNetworkProxySchema = z
|
||||
baseProfile: z.enum(["read-only", "workspace"]).optional(),
|
||||
mode: z.enum(["limited", "full"]).optional(),
|
||||
domains: z.record(z.string(), codexAppServerNetworkProxyDomainPermissionSchema).optional(),
|
||||
unixSockets: z.record(z.string(), codexAppServerNetworkProxyUnixSocketPermissionSchema).optional(),
|
||||
unixSockets: z
|
||||
.record(z.string(), codexAppServerNetworkProxyUnixSocketPermissionSchema)
|
||||
.optional(),
|
||||
proxyUrl: z.string().trim().min(1).optional(),
|
||||
socksUrl: z.string().trim().min(1).optional(),
|
||||
enableSocks5: z.boolean().optional(),
|
||||
@@ -874,6 +877,7 @@ export function codexAppServerStartOptionsKey(
|
||||
transport: options.transport,
|
||||
command: options.command,
|
||||
commandSource: options.commandSource ?? null,
|
||||
managedFallbackCommandPaths: [...(options.managedFallbackCommandPaths ?? [])],
|
||||
args: options.args,
|
||||
url: options.url ?? null,
|
||||
authToken: hashSecretForKey(options.authToken, "authToken"),
|
||||
|
||||
@@ -27,6 +27,8 @@ function managedCommandPath(root: string, platform: NodeJS.Platform): string {
|
||||
return pathApi.join(root, "node_modules", ".bin", platform === "win32" ? "codex.cmd" : "codex");
|
||||
}
|
||||
|
||||
const MACOS_DESKTOP_CODEX_APP_SERVER_COMMAND = "/Applications/Codex.app/Contents/Resources/codex";
|
||||
|
||||
describe("managed Codex app-server binary", () => {
|
||||
it("leaves explicit command overrides unchanged", async () => {
|
||||
const explicitOptions = startOptions("config");
|
||||
@@ -41,10 +43,14 @@ describe("managed Codex app-server binary", () => {
|
||||
expect(pathExists).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("resolves the plugin-local bundled Codex binary", async () => {
|
||||
it("prefers the macOS desktop app bundle when it exists", async () => {
|
||||
const pluginRoot = path.join("/tmp", "openclaw", "extensions", "codex");
|
||||
const paths = resolveManagedCodexAppServerPaths({ platform: "darwin", pluginRoot });
|
||||
const pathExists = vi.fn(async (filePath: string) => filePath === paths.commandPath);
|
||||
const pluginLocalCommand = managedCommandPath(pluginRoot, "darwin");
|
||||
const pathExists = vi.fn(
|
||||
async (filePath: string) =>
|
||||
filePath === MACOS_DESKTOP_CODEX_APP_SERVER_COMMAND || filePath === pluginLocalCommand,
|
||||
);
|
||||
|
||||
await expect(
|
||||
resolveManagedCodexAppServerStartOptions(startOptions("managed"), {
|
||||
@@ -54,10 +60,31 @@ describe("managed Codex app-server binary", () => {
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
...startOptions("managed"),
|
||||
command: paths.commandPath,
|
||||
command: MACOS_DESKTOP_CODEX_APP_SERVER_COMMAND,
|
||||
commandSource: "resolved-managed",
|
||||
managedFallbackCommandPaths: [pluginLocalCommand],
|
||||
});
|
||||
expect(paths.commandPath).toBe(MACOS_DESKTOP_CODEX_APP_SERVER_COMMAND);
|
||||
expect(paths.candidateCommandPaths).toContain(pluginLocalCommand);
|
||||
});
|
||||
|
||||
it("falls back to the plugin-local bundled Codex binary on macOS", async () => {
|
||||
const pluginRoot = path.join("/tmp", "openclaw", "extensions", "codex");
|
||||
const pluginLocalCommand = managedCommandPath(pluginRoot, "darwin");
|
||||
const pathExists = vi.fn(async (filePath: string) => filePath === pluginLocalCommand);
|
||||
|
||||
await expect(
|
||||
resolveManagedCodexAppServerStartOptions(startOptions("managed"), {
|
||||
platform: "darwin",
|
||||
pluginRoot,
|
||||
pathExists,
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
...startOptions("managed"),
|
||||
command: pluginLocalCommand,
|
||||
commandSource: "resolved-managed",
|
||||
});
|
||||
expect(paths.commandPath).toBe(managedCommandPath(pluginRoot, "darwin"));
|
||||
expect(pathExists).toHaveBeenCalledWith(MACOS_DESKTOP_CODEX_APP_SERVER_COMMAND, "darwin");
|
||||
});
|
||||
|
||||
it("resolves Windows Codex command shims", () => {
|
||||
|
||||
@@ -12,6 +12,7 @@ import { MANAGED_CODEX_APP_SERVER_PACKAGE } from "./version.js";
|
||||
|
||||
const CODEX_APP_SERVER_MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
||||
const CODEX_PLUGIN_ROOT = resolveDefaultCodexPluginRoot(CODEX_APP_SERVER_MODULE_DIR);
|
||||
const MACOS_DESKTOP_CODEX_APP_SERVER_COMMAND = "/Applications/Codex.app/Contents/Resources/codex";
|
||||
|
||||
type ManagedCodexAppServerPaths = {
|
||||
commandPath: string;
|
||||
@@ -39,16 +40,19 @@ export async function resolveManagedCodexAppServerStartOptions(
|
||||
pluginRoot: options.pluginRoot,
|
||||
});
|
||||
const pathExists = options.pathExists ?? commandPathExists;
|
||||
const commandPath = await findManagedCodexAppServerCommandPath({
|
||||
const commandPaths = await findManagedCodexAppServerCommandPaths({
|
||||
candidateCommandPaths: paths.candidateCommandPaths,
|
||||
pathExists,
|
||||
platform,
|
||||
});
|
||||
const commandPath = commandPaths[0];
|
||||
const managedFallbackCommandPaths = commandPaths.slice(1);
|
||||
|
||||
return {
|
||||
...startOptions,
|
||||
command: commandPath,
|
||||
commandSource: "resolved-managed",
|
||||
...(managedFallbackCommandPaths.length > 0 ? { managedFallbackCommandPaths } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -77,12 +81,17 @@ function resolveManagedCodexAppServerCommandCandidates(
|
||||
const roots = resolveManagedCodexAppServerCandidateRoots(pluginRoot, platform);
|
||||
return [
|
||||
...new Set([
|
||||
...resolveDesktopCodexAppServerCommandCandidates(platform),
|
||||
...roots.map((root) => pathApi.join(root, "node_modules", ".bin", commandName)),
|
||||
...resolveManagedCodexPackageBinCandidates(roots, platform),
|
||||
]),
|
||||
];
|
||||
}
|
||||
|
||||
function resolveDesktopCodexAppServerCommandCandidates(platform: NodeJS.Platform): string[] {
|
||||
return platform === "darwin" ? [MACOS_DESKTOP_CODEX_APP_SERVER_COMMAND] : [];
|
||||
}
|
||||
|
||||
function resolveDefaultCodexPluginRoot(moduleDir: string): string {
|
||||
const moduleBaseName = path.basename(moduleDir);
|
||||
if (moduleBaseName === "dist" || moduleBaseName === "dist-runtime") {
|
||||
@@ -195,16 +204,20 @@ function pathForPlatform(platform: NodeJS.Platform): typeof path {
|
||||
return platform === "win32" ? path.win32 : path.posix;
|
||||
}
|
||||
|
||||
async function findManagedCodexAppServerCommandPath(params: {
|
||||
async function findManagedCodexAppServerCommandPaths(params: {
|
||||
candidateCommandPaths: readonly string[];
|
||||
pathExists: (filePath: string, platform: NodeJS.Platform) => Promise<boolean>;
|
||||
platform: NodeJS.Platform;
|
||||
}): Promise<string> {
|
||||
}): Promise<string[]> {
|
||||
const commandPaths: string[] = [];
|
||||
for (const commandPath of params.candidateCommandPaths) {
|
||||
if (await params.pathExists(commandPath, params.platform)) {
|
||||
return commandPath;
|
||||
commandPaths.push(commandPath);
|
||||
}
|
||||
}
|
||||
if (commandPaths.length > 0) {
|
||||
return commandPaths;
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
[
|
||||
|
||||
@@ -254,7 +254,7 @@ describe("Codex plugin thread config", () => {
|
||||
const request = vi.fn(async (method: string, params?: unknown) => {
|
||||
if (method === "app/list") {
|
||||
appListParams.push(params as v2.AppsListParams);
|
||||
return { data: [appInfo("google-calendar-app", true)], nextCursor: null };
|
||||
return { data: [appInfo("google-calendar-app", true, false)], nextCursor: null };
|
||||
}
|
||||
if (method === "plugin/list") {
|
||||
return pluginList([pluginSummary("google-calendar", { installed: true, enabled: true })]);
|
||||
@@ -317,6 +317,117 @@ describe("Codex plugin thread config", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("re-enables an OpenClaw-allowed app even when app/list reports it disabled", async () => {
|
||||
const appCache = new CodexAppInventoryCache();
|
||||
await appCache.refreshNow({
|
||||
key: "runtime",
|
||||
nowMs: 0,
|
||||
request: async () => ({
|
||||
data: [appInfo("google-calendar-app", true, false)],
|
||||
nextCursor: null,
|
||||
}),
|
||||
});
|
||||
|
||||
const config = await buildCodexPluginThreadConfig({
|
||||
pluginConfig: {
|
||||
codexPlugins: {
|
||||
enabled: true,
|
||||
plugins: {
|
||||
"google-calendar": {
|
||||
marketplaceName: CODEX_PLUGINS_MARKETPLACE_NAME,
|
||||
pluginName: "google-calendar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
appCache,
|
||||
appCacheKey: "runtime",
|
||||
nowMs: 1,
|
||||
request: async (method) => {
|
||||
if (method === "plugin/list") {
|
||||
return pluginList([pluginSummary("google-calendar", { installed: true, enabled: true })]);
|
||||
}
|
||||
if (method === "plugin/read") {
|
||||
return pluginDetail("google-calendar", [appSummary("google-calendar-app")]);
|
||||
}
|
||||
throw new Error(`unexpected request ${method}`);
|
||||
},
|
||||
});
|
||||
|
||||
expect(config.inventory?.records[0]?.apps).toStrictEqual([
|
||||
{
|
||||
id: "google-calendar-app",
|
||||
name: "google-calendar-app",
|
||||
accessible: true,
|
||||
enabled: false,
|
||||
needsAuth: false,
|
||||
},
|
||||
]);
|
||||
expect(config.configPatch?.apps).toMatchObject({
|
||||
"google-calendar-app": {
|
||||
enabled: true,
|
||||
},
|
||||
});
|
||||
expect(config.diagnostics).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it("refreshes missing app inventory when plugin activation becomes unnecessary", async () => {
|
||||
const appCache = new CodexAppInventoryCache();
|
||||
const appListParams: v2.AppsListParams[] = [];
|
||||
let pluginListCalls = 0;
|
||||
const request = vi.fn(async (method: string, params?: unknown) => {
|
||||
if (method === "plugin/list") {
|
||||
pluginListCalls += 1;
|
||||
const active = pluginListCalls > 1;
|
||||
return pluginList([
|
||||
pluginSummary("google-calendar", { installed: active, enabled: active }),
|
||||
]);
|
||||
}
|
||||
if (method === "plugin/read") {
|
||||
return pluginDetail("google-calendar", [appSummary("google-calendar-app")]);
|
||||
}
|
||||
if (method === "app/list") {
|
||||
appListParams.push(params as v2.AppsListParams);
|
||||
return {
|
||||
data: [appInfo("google-calendar-app", true)],
|
||||
nextCursor: null,
|
||||
} satisfies v2.AppsListResponse;
|
||||
}
|
||||
throw new Error(`unexpected request ${method}`);
|
||||
});
|
||||
|
||||
const config = await buildCodexPluginThreadConfig({
|
||||
pluginConfig: {
|
||||
codexPlugins: {
|
||||
enabled: true,
|
||||
plugins: {
|
||||
"google-calendar": {
|
||||
marketplaceName: CODEX_PLUGINS_MARKETPLACE_NAME,
|
||||
pluginName: "google-calendar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
appCache,
|
||||
appCacheKey: "runtime",
|
||||
request,
|
||||
});
|
||||
|
||||
expect(config.configPatch?.apps).toMatchObject({
|
||||
"google-calendar-app": {
|
||||
enabled: true,
|
||||
},
|
||||
});
|
||||
expect(request.mock.calls.map(([method]) => method)).not.toContain("plugin/install");
|
||||
expect(appListParams).toEqual([
|
||||
{
|
||||
cursor: undefined,
|
||||
limit: 100,
|
||||
forceRefetch: true,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("does not expose plugin apps missing from the app inventory snapshot", async () => {
|
||||
const appCache = new CodexAppInventoryCache();
|
||||
await appCache.refreshNow({
|
||||
@@ -375,11 +486,59 @@ describe("Codex plugin thread config", () => {
|
||||
allowDestructiveActions: true,
|
||||
destructiveApprovalMode: "allow",
|
||||
},
|
||||
message: "google-calendar-app is not accessible or enabled for google-calendar.",
|
||||
message: "google-calendar-app is not accessible for google-calendar.",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("does not expose apps for plugins that OpenClaw policy leaves disabled", async () => {
|
||||
const appCache = new CodexAppInventoryCache();
|
||||
await appCache.refreshNow({
|
||||
key: "runtime",
|
||||
nowMs: 0,
|
||||
request: async () => ({
|
||||
data: [appInfo("google-calendar-app", true)],
|
||||
nextCursor: null,
|
||||
}),
|
||||
});
|
||||
|
||||
const config = await buildCodexPluginThreadConfig({
|
||||
pluginConfig: {
|
||||
codexPlugins: {
|
||||
enabled: true,
|
||||
plugins: {
|
||||
"google-calendar": {
|
||||
enabled: false,
|
||||
marketplaceName: CODEX_PLUGINS_MARKETPLACE_NAME,
|
||||
pluginName: "google-calendar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
appCache,
|
||||
appCacheKey: "runtime",
|
||||
nowMs: 1,
|
||||
request: async (method) => {
|
||||
if (method === "plugin/list") {
|
||||
return pluginList([pluginSummary("google-calendar", { installed: true, enabled: true })]);
|
||||
}
|
||||
throw new Error(`unexpected request ${method}`);
|
||||
},
|
||||
});
|
||||
|
||||
expect(config.configPatch).toEqual({
|
||||
apps: {
|
||||
_default: {
|
||||
enabled: false,
|
||||
destructive_enabled: false,
|
||||
open_world_enabled: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(config.policyContext.apps).toStrictEqual({});
|
||||
expect(config.diagnostics).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it("force-refreshes app inventory when proven plugin apps are not ready", async () => {
|
||||
const appCache = new CodexAppInventoryCache();
|
||||
await appCache.refreshNow({
|
||||
@@ -572,9 +731,7 @@ describe("Codex plugin thread config", () => {
|
||||
let installed = false;
|
||||
const request = vi.fn(async (method: string, params?: unknown) => {
|
||||
if (method === "plugin/list") {
|
||||
return pluginList([
|
||||
pluginSummary("google-calendar", { installed, enabled: installed }),
|
||||
]);
|
||||
return pluginList([pluginSummary("google-calendar", { installed, enabled: installed })]);
|
||||
}
|
||||
if (method === "plugin/read") {
|
||||
return pluginDetail("google-calendar", [appSummary("google-calendar-app")]);
|
||||
@@ -738,6 +895,70 @@ describe("Codex plugin thread config", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("fails closed when app inventory entries are malformed", async () => {
|
||||
const appCache = new CodexAppInventoryCache();
|
||||
await appCache.refreshNow({
|
||||
key: "runtime",
|
||||
nowMs: 0,
|
||||
request: async () =>
|
||||
({
|
||||
data: [{ ...appInfo("google-calendar-app", true), id: "" }] as unknown as v2.AppInfo[],
|
||||
nextCursor: null,
|
||||
}) satisfies v2.AppsListResponse,
|
||||
});
|
||||
|
||||
const config = await buildCodexPluginThreadConfig({
|
||||
pluginConfig: {
|
||||
codexPlugins: {
|
||||
enabled: true,
|
||||
plugins: {
|
||||
"google-calendar": {
|
||||
marketplaceName: CODEX_PLUGINS_MARKETPLACE_NAME,
|
||||
pluginName: "google-calendar",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
appCache,
|
||||
appCacheKey: "runtime",
|
||||
nowMs: 1,
|
||||
request: async (method) => {
|
||||
if (method === "plugin/list") {
|
||||
return pluginList([pluginSummary("google-calendar", { installed: true, enabled: true })]);
|
||||
}
|
||||
if (method === "plugin/read") {
|
||||
return pluginDetail("google-calendar", [appSummary("google-calendar-app")]);
|
||||
}
|
||||
throw new Error(`unexpected request ${method}`);
|
||||
},
|
||||
});
|
||||
|
||||
expect(config.configPatch).toEqual({
|
||||
apps: {
|
||||
_default: {
|
||||
enabled: false,
|
||||
destructive_enabled: false,
|
||||
open_world_enabled: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
expect(config.policyContext.apps).toStrictEqual({});
|
||||
expect(config.diagnostics).toStrictEqual([
|
||||
{
|
||||
code: "app_not_ready",
|
||||
plugin: {
|
||||
configKey: "google-calendar",
|
||||
marketplaceName: CODEX_PLUGINS_MARKETPLACE_NAME,
|
||||
pluginName: "google-calendar",
|
||||
enabled: true,
|
||||
allowDestructiveActions: true,
|
||||
destructiveApprovalMode: "allow",
|
||||
},
|
||||
message: "google-calendar-app is not accessible for google-calendar.",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("uses durable policy and app cache key in the cheap input fingerprint", async () => {
|
||||
const appCache = new CodexAppInventoryCache();
|
||||
const first = buildCodexPluginThreadConfigInputFingerprint({
|
||||
|
||||
@@ -125,6 +125,9 @@ export async function buildCodexPluginThreadConfig(
|
||||
nowMs: params.nowMs,
|
||||
suppressAppInventoryRefresh: true,
|
||||
});
|
||||
const appInventoryRefreshDeferredForActivation =
|
||||
inventory.records.some((record) => record.activationRequired) &&
|
||||
shouldRefreshMissingAppInventory(params, policy, inventory);
|
||||
if (shouldWaitForInitialAppInventory(params, policy, inventory)) {
|
||||
await refreshAppInventoryNow(params, appCache, {
|
||||
forceRefetch: true,
|
||||
@@ -166,10 +169,19 @@ export async function buildCodexPluginThreadConfig(
|
||||
});
|
||||
}
|
||||
}
|
||||
if (activationResults.some((activation) => activation.ok && activation.installAttempted)) {
|
||||
const postInstallRefreshRequired = activationResults.some(
|
||||
(activation) => activation.ok && activation.installAttempted,
|
||||
);
|
||||
// Activation can become unnecessary or fail before it refreshes apps. Rebuild the
|
||||
// deferred missing snapshot so unrelated active plugin apps are not silently erased.
|
||||
const deferredMissingRefreshRequired =
|
||||
appInventoryRefreshDeferredForActivation &&
|
||||
!postInstallRefreshRequired &&
|
||||
shouldRefreshMissingAppInventory(params, policy, inventory);
|
||||
if (postInstallRefreshRequired || deferredMissingRefreshRequired) {
|
||||
await refreshAppInventoryNow(params, appCache, {
|
||||
forceRefetch: true,
|
||||
reason: "post_install",
|
||||
reason: postInstallRefreshRequired ? "post_install" : "deferred_missing",
|
||||
targetAppIds: collectInventoryOwnedAppIds(inventory),
|
||||
});
|
||||
inventory = await readCodexPluginInventory({
|
||||
@@ -219,24 +231,22 @@ export async function buildCodexPluginThreadConfig(
|
||||
const policyApps: Record<string, PluginAppPolicyContextEntry> = {};
|
||||
const pluginAppIds: Record<string, string[]> = {};
|
||||
for (const record of inventory.records) {
|
||||
if (record.activationRequired) {
|
||||
const activation = activationResults.find(
|
||||
(item) => item.identity.configKey === record.policy.configKey,
|
||||
);
|
||||
if (!activation?.ok) {
|
||||
continue;
|
||||
}
|
||||
const activation = activationResults.find(
|
||||
(item) => item.identity.configKey === record.policy.configKey,
|
||||
);
|
||||
if (activation?.ok === false || (record.activationRequired && !activation?.ok)) {
|
||||
continue;
|
||||
}
|
||||
if (record.appOwnership !== "proven") {
|
||||
continue;
|
||||
}
|
||||
pluginAppIds[record.policy.configKey] = [...record.ownedAppIds].toSorted();
|
||||
for (const app of resolveThreadConfigAppsForRecord({ record, inventory })) {
|
||||
if (!app.accessible || !app.enabled) {
|
||||
if (!isPluginAppReadyForThreadStart(app)) {
|
||||
diagnostics.push({
|
||||
code: "app_not_ready",
|
||||
plugin: record.policy,
|
||||
message: `${app.id} is not accessible or enabled for ${record.policy.pluginName}.`,
|
||||
message: `${app.id} is not accessible for ${record.policy.pluginName}.`,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
@@ -362,9 +372,18 @@ function shouldWaitForInitialAppInventory(
|
||||
policy: ResolvedCodexPluginsPolicy,
|
||||
inventory: CodexPluginInventory,
|
||||
): boolean {
|
||||
// Install/enable first so the initial app/list can observe newly activated plugin apps.
|
||||
if (inventory.records.some((record) => record.activationRequired)) {
|
||||
return false;
|
||||
}
|
||||
return shouldRefreshMissingAppInventory(params, policy, inventory);
|
||||
}
|
||||
|
||||
function shouldRefreshMissingAppInventory(
|
||||
params: BuildCodexPluginThreadConfigParams,
|
||||
policy: ResolvedCodexPluginsPolicy,
|
||||
inventory: CodexPluginInventory,
|
||||
): boolean {
|
||||
return Boolean(
|
||||
params.appCacheKey &&
|
||||
policy.pluginPolicies.some((plugin) => plugin.enabled) &&
|
||||
@@ -419,6 +438,13 @@ function resolveThreadConfigAppsForRecord(params: {
|
||||
return params.record.apps;
|
||||
}
|
||||
|
||||
function isPluginAppReadyForThreadStart(app: CodexPluginOwnedApp): boolean {
|
||||
// `app/list` is the source of truth for inventory and access posture, but
|
||||
// OpenClaw owns the per-thread enablement decision. A listed app that is
|
||||
// accessible can be re-enabled for this thread via `config.apps[app.id]`.
|
||||
return app.accessible;
|
||||
}
|
||||
|
||||
function shouldForceRefreshForNotReadyPluginApps(
|
||||
params: BuildCodexPluginThreadConfigParams,
|
||||
policy: ResolvedCodexPluginsPolicy,
|
||||
@@ -434,7 +460,7 @@ function shouldForceRefreshForNotReadyPluginApps(
|
||||
(record) =>
|
||||
record.appOwnership === "proven" &&
|
||||
record.ownedAppIds.length > 0 &&
|
||||
(record.apps.length === 0 || record.apps.some((app) => !app.accessible || !app.enabled)),
|
||||
(record.apps.length === 0 || record.apps.some((app) => !app.accessible)),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -4416,6 +4416,131 @@ describe("runCodexAppServerAttempt", () => {
|
||||
expect(requests.map((entry) => entry.method)).not.toContain("app/list");
|
||||
});
|
||||
|
||||
it("sends a thread/start app enable override when app/list cached the app as disabled", async () => {
|
||||
const sessionFile = path.join(tempDir, "session.jsonl");
|
||||
const workspaceDir = path.join(tempDir, "workspace");
|
||||
const agentDir = path.join(tempDir, "agent");
|
||||
const pluginConfig = {
|
||||
codexPlugins: {
|
||||
enabled: true,
|
||||
plugins: {
|
||||
"google-calendar": {
|
||||
marketplaceName: "openai-curated",
|
||||
pluginName: "google-calendar",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const appServer = resolveCodexAppServerRuntimeOptions({
|
||||
pluginConfig: readCodexPluginConfig(pluginConfig),
|
||||
});
|
||||
defaultCodexAppInventoryCache.clear();
|
||||
await defaultCodexAppInventoryCache.refreshNow({
|
||||
key: buildCodexPluginAppCacheKey({
|
||||
appServer,
|
||||
agentDir,
|
||||
runtimeIdentity: getMockRuntimeIdentity(),
|
||||
}),
|
||||
request: async () => ({
|
||||
data: [
|
||||
{
|
||||
id: "google-calendar-app",
|
||||
name: "Google Calendar",
|
||||
description: null,
|
||||
logoUrl: null,
|
||||
logoUrlDark: null,
|
||||
distributionChannel: null,
|
||||
branding: null,
|
||||
appMetadata: null,
|
||||
labels: null,
|
||||
installUrl: null,
|
||||
isAccessible: true,
|
||||
isEnabled: false,
|
||||
pluginDisplayNames: [],
|
||||
},
|
||||
],
|
||||
nextCursor: null,
|
||||
}),
|
||||
});
|
||||
const { requests, waitForMethod, completeTurn } = createStartedThreadHarness(async (method) => {
|
||||
if (method === "plugin/list") {
|
||||
return {
|
||||
marketplaces: [
|
||||
{
|
||||
name: "openai-curated",
|
||||
path: "/marketplaces/openai-curated",
|
||||
interface: null,
|
||||
plugins: [
|
||||
{
|
||||
id: "google-calendar",
|
||||
name: "google-calendar",
|
||||
source: { type: "remote" },
|
||||
installed: true,
|
||||
enabled: true,
|
||||
installPolicy: "AVAILABLE",
|
||||
authPolicy: "ON_USE",
|
||||
availability: "AVAILABLE",
|
||||
interface: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
marketplaceLoadErrors: [],
|
||||
featuredPluginIds: [],
|
||||
};
|
||||
}
|
||||
if (method === "plugin/read") {
|
||||
return {
|
||||
plugin: {
|
||||
marketplaceName: "openai-curated",
|
||||
marketplacePath: "/marketplaces/openai-curated",
|
||||
summary: {
|
||||
id: "google-calendar",
|
||||
name: "google-calendar",
|
||||
source: { type: "remote" },
|
||||
installed: true,
|
||||
enabled: true,
|
||||
installPolicy: "AVAILABLE",
|
||||
authPolicy: "ON_USE",
|
||||
availability: "AVAILABLE",
|
||||
interface: null,
|
||||
},
|
||||
description: null,
|
||||
skills: [],
|
||||
apps: [
|
||||
{
|
||||
id: "google-calendar-app",
|
||||
name: "Google Calendar",
|
||||
description: null,
|
||||
installUrl: null,
|
||||
needsAuth: false,
|
||||
},
|
||||
],
|
||||
mcpServers: ["google-calendar"],
|
||||
},
|
||||
};
|
||||
}
|
||||
if (method === "app/list") {
|
||||
throw new Error("app/list should use the cached inventory entry");
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
const params = createParams(sessionFile, workspaceDir);
|
||||
params.agentDir = agentDir;
|
||||
|
||||
const run = runCodexAppServerAttempt(params, { pluginConfig });
|
||||
await waitForMethod("turn/start");
|
||||
await completeTurn({ threadId: "thread-1", turnId: "turn-1" });
|
||||
await run;
|
||||
|
||||
const threadStart = requests.find((entry) => entry.method === "thread/start");
|
||||
const threadStartParams = threadStart?.params as
|
||||
| { config?: { apps?: Record<string, { enabled?: boolean }> } }
|
||||
| undefined;
|
||||
expect(threadStartParams?.config?.apps?.["google-calendar-app"]?.enabled).toBe(true);
|
||||
expect(requests.map((entry) => entry.method)).not.toContain("app/list");
|
||||
});
|
||||
|
||||
it("keys plugin app inventory by inherited API key fallback credentials", async () => {
|
||||
const sessionFile = path.join(tempDir, "session.jsonl");
|
||||
const workspaceDir = path.join(tempDir, "workspace");
|
||||
|
||||
@@ -1578,6 +1578,7 @@ export async function runCodexAppServerAttempt(
|
||||
let activeAppServerTurnRequests = 0;
|
||||
const pendingOpenClawDynamicToolCompletionIds = new Set<string>();
|
||||
const activeTurnItemIds = new Set<string>();
|
||||
const activeCompletionBlockerItemIds = new Set<string>();
|
||||
let turnCrossedToolHandoff = false;
|
||||
let pendingTerminalDynamicToolRelease:
|
||||
| {
|
||||
@@ -1627,6 +1628,7 @@ export async function runCodexAppServerAttempt(
|
||||
isTerminalTurnNotificationQueued: () => terminalTurnNotificationQueued,
|
||||
getActiveAppServerTurnRequests: () => activeAppServerTurnRequests,
|
||||
getActiveTurnItemCount: () => activeTurnItemIds.size,
|
||||
getActiveCompletionBlockerItemCount: () => activeCompletionBlockerItemIds.size,
|
||||
turnCompletionIdleTimeoutMs,
|
||||
turnAssistantCompletionIdleTimeoutMs,
|
||||
turnAttemptIdleTimeoutMs,
|
||||
@@ -1899,6 +1901,7 @@ export async function runCodexAppServerAttempt(
|
||||
currentPromptTexts: [codexTurnPromptText],
|
||||
turnWatches,
|
||||
activeTurnItemIds,
|
||||
activeCompletionBlockerItemIds,
|
||||
activeAppServerTurnRequests,
|
||||
pendingOpenClawDynamicToolCompletionIds,
|
||||
turnCrossedToolHandoff,
|
||||
|
||||
@@ -49,9 +49,7 @@ const DISABLED_CODEX_WEB_SEARCH_THREAD_CONFIG_FINGERPRINT = JSON.stringify({
|
||||
web_search: "disabled",
|
||||
});
|
||||
|
||||
function writeCodexAppServerBinding(
|
||||
...args: Parameters<typeof writeRawCodexAppServerBinding>
|
||||
) {
|
||||
function writeCodexAppServerBinding(...args: Parameters<typeof writeRawCodexAppServerBinding>) {
|
||||
const [sessionFile, binding, lookup] = args;
|
||||
return writeRawCodexAppServerBinding(
|
||||
sessionFile,
|
||||
@@ -78,6 +76,7 @@ describe("createCodexAttemptTurnWatchController", () => {
|
||||
isTerminalTurnNotificationQueued: () => false,
|
||||
getActiveAppServerTurnRequests: () => 0,
|
||||
getActiveTurnItemCount: () => 0,
|
||||
getActiveCompletionBlockerItemCount: () => 0,
|
||||
turnCompletionIdleTimeoutMs: 500,
|
||||
turnAssistantCompletionIdleTimeoutMs: 500,
|
||||
turnAttemptIdleTimeoutMs: 200,
|
||||
@@ -807,6 +806,93 @@ describe("runCodexAppServerAttempt turn watches", () => {
|
||||
expect(result.promptError).toBeNull();
|
||||
});
|
||||
|
||||
it("keeps an eliciting MCP tool active past the completion timeout", async () => {
|
||||
const harness = createStartedThreadHarness();
|
||||
const bridgedResponse = {
|
||||
action: "accept",
|
||||
content: null,
|
||||
_meta: null,
|
||||
} as const;
|
||||
vi.spyOn(elicitationBridge, "handleCodexAppServerElicitationRequest").mockResolvedValue(
|
||||
bridgedResponse,
|
||||
);
|
||||
const params = createParams(
|
||||
path.join(tempDir, "session-mcp-elicitation.jsonl"),
|
||||
path.join(tempDir, "workspace-mcp-elicitation"),
|
||||
);
|
||||
params.timeoutMs = 500;
|
||||
|
||||
let settled = false;
|
||||
const run = runCodexAppServerAttempt(params, {
|
||||
turnCompletionIdleTimeoutMs: 15,
|
||||
turnAssistantCompletionIdleTimeoutMs: 1_000,
|
||||
turnTerminalIdleTimeoutMs: 1_000,
|
||||
}).finally(() => {
|
||||
settled = true;
|
||||
});
|
||||
await harness.waitForMethod("turn/start");
|
||||
await harness.notify({
|
||||
method: "item/started",
|
||||
params: {
|
||||
threadId: "thread-1",
|
||||
turnId: "turn-1",
|
||||
item: {
|
||||
id: "mcp-1",
|
||||
type: "mcpToolCall",
|
||||
server: "computer-use",
|
||||
tool: "computer",
|
||||
status: "inProgress",
|
||||
arguments: {},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await expect(
|
||||
harness.handleServerRequest({
|
||||
id: "request-mcp-elicitation",
|
||||
method: "mcpServer/elicitation/request",
|
||||
params: {
|
||||
threadId: "thread-1",
|
||||
turnId: "turn-1",
|
||||
mode: "form",
|
||||
message: "Approve?",
|
||||
requestedSchema: { type: "object", properties: {} },
|
||||
serverName: "computer-use",
|
||||
_meta: null,
|
||||
},
|
||||
}),
|
||||
).resolves.toEqual(bridgedResponse);
|
||||
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, 40);
|
||||
});
|
||||
expect(settled).toBe(false);
|
||||
expect(harness.request.mock.calls.some(([method]) => method === "turn/interrupt")).toBe(false);
|
||||
|
||||
await harness.notify({
|
||||
method: "item/completed",
|
||||
params: {
|
||||
threadId: "thread-1",
|
||||
turnId: "turn-1",
|
||||
item: {
|
||||
id: "mcp-1",
|
||||
type: "mcpToolCall",
|
||||
server: "computer-use",
|
||||
tool: "computer",
|
||||
status: "completed",
|
||||
arguments: {},
|
||||
result: { content: [] },
|
||||
},
|
||||
},
|
||||
});
|
||||
await harness.completeTurn({ threadId: "thread-1", turnId: "turn-1" });
|
||||
|
||||
const result = await run;
|
||||
expect(result.aborted).toBe(false);
|
||||
expect(result.timedOut).toBe(false);
|
||||
expect(result.promptError).toBeNull();
|
||||
});
|
||||
|
||||
it("counts pending user input requests as turn attempt progress", async () => {
|
||||
const harness = createStartedThreadHarness();
|
||||
const params = createParams(
|
||||
|
||||
@@ -187,6 +187,41 @@ describe("shared Codex app-server client", () => {
|
||||
startSpy.mockRestore();
|
||||
});
|
||||
|
||||
it("falls back to the next managed app-server when desktop initialize is unsupported", async () => {
|
||||
const desktop = createClientHarness();
|
||||
const pluginLocal = createClientHarness();
|
||||
const startSpy = vi
|
||||
.spyOn(CodexAppServerClient, "start")
|
||||
.mockReturnValueOnce(desktop.client)
|
||||
.mockReturnValueOnce(pluginLocal.client);
|
||||
mocks.resolveManagedCodexAppServerStartOptions.mockImplementationOnce(async (startOptions) => ({
|
||||
...startOptions,
|
||||
command: "/Applications/Codex.app/Contents/Resources/codex",
|
||||
commandSource: "resolved-managed",
|
||||
managedFallbackCommandPaths: ["/cache/openclaw/codex"],
|
||||
}));
|
||||
|
||||
const listPromise = listCodexAppServerModels({ timeoutMs: 1000 });
|
||||
await sendInitializeResult(desktop, "openclaw/0.124.9 (macOS; test)");
|
||||
await sendInitializeResult(pluginLocal, "openclaw/0.125.0 (macOS; test)");
|
||||
await sendEmptyModelList(pluginLocal);
|
||||
|
||||
await expect(listPromise).resolves.toEqual({ models: [] });
|
||||
expect(desktop.process.stdin.destroyed).toBe(true);
|
||||
expect(pluginLocal.process.stdin.destroyed).toBe(false);
|
||||
expect(startSpy).toHaveBeenCalledTimes(2);
|
||||
expect(startSpy.mock.calls[0]?.[0]).toMatchObject({
|
||||
command: "/Applications/Codex.app/Contents/Resources/codex",
|
||||
commandSource: "resolved-managed",
|
||||
managedFallbackCommandPaths: ["/cache/openclaw/codex"],
|
||||
});
|
||||
expect(startSpy.mock.calls[1]?.[0]).toMatchObject({
|
||||
command: "/cache/openclaw/codex",
|
||||
commandSource: "resolved-managed",
|
||||
});
|
||||
expect(startSpy.mock.calls[1]?.[0]).not.toHaveProperty("managedFallbackCommandPaths");
|
||||
});
|
||||
|
||||
it("closes and clears a shared app-server when initialize times out", async () => {
|
||||
const first = createClientHarness();
|
||||
const second = createClientHarness();
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
resolveCodexAppServerAuthProfileStore,
|
||||
resolveCodexAppServerFallbackApiKeyCacheKey,
|
||||
} from "./auth-bridge.js";
|
||||
import { CodexAppServerClient } from "./client.js";
|
||||
import { CodexAppServerClient, isUnsupportedCodexAppServerVersionError } from "./client.js";
|
||||
import {
|
||||
codexAppServerStartOptionsKey,
|
||||
resolveCodexAppServerRuntimeOptions,
|
||||
@@ -242,27 +242,23 @@ async function acquireSharedCodexAppServerClient(
|
||||
const sharedPromise =
|
||||
entry.promise ??
|
||||
(entry.promise = (async () => {
|
||||
const client = CodexAppServerClient.start(startOptions);
|
||||
const client = await startInitializedCodexAppServerClient({
|
||||
startOptions,
|
||||
agentDir,
|
||||
authProfileId: usesNativeAuth ? null : authProfileId,
|
||||
config: options?.config,
|
||||
onStartedClient: (startedClient) => {
|
||||
entry.client = startedClient;
|
||||
startedClient.setActiveSharedLeaseCountProviderForUnscopedNotifications(
|
||||
() => entry.activeLeases,
|
||||
);
|
||||
options?.onStartedClient?.(startedClient);
|
||||
},
|
||||
});
|
||||
entry.client = client;
|
||||
options?.onStartedClient?.(client);
|
||||
client.setActiveSharedLeaseCountProviderForUnscopedNotifications(() => entry.activeLeases);
|
||||
client.addCloseHandler((closedClient) => clearSharedClientEntryIfCurrent(key, closedClient));
|
||||
try {
|
||||
await client.initialize();
|
||||
await applyCodexAppServerAuthProfile({
|
||||
client,
|
||||
agentDir,
|
||||
authProfileId: usesNativeAuth ? null : authProfileId,
|
||||
startOptions,
|
||||
config: options?.config,
|
||||
});
|
||||
return client;
|
||||
} catch (error) {
|
||||
// Startup failures happen before callers own the shared client, so close
|
||||
// the child here instead of leaving a rejected daemon attached to stdio.
|
||||
client.close();
|
||||
throw error;
|
||||
}
|
||||
return client;
|
||||
})());
|
||||
try {
|
||||
const client = await withTimeout(
|
||||
@@ -291,39 +287,110 @@ export async function createIsolatedCodexAppServerClient(
|
||||
): Promise<CodexAppServerClient> {
|
||||
const { agentDir, usesNativeAuth, authProfileId, authProfileStore, startOptions } =
|
||||
await resolveCodexAppServerClientStartContext(options);
|
||||
const client = CodexAppServerClient.start(startOptions);
|
||||
if (authProfileId) {
|
||||
// Profile-backed Codex auth is ephemeral. Keep the host refresh callback
|
||||
// available whether the profile came from a scoped store or persisted state.
|
||||
client.addRequestHandler(async (request) => {
|
||||
if (request.method !== "account/chatgptAuthTokens/refresh") {
|
||||
return undefined;
|
||||
return await startInitializedCodexAppServerClient({
|
||||
startOptions,
|
||||
agentDir,
|
||||
authProfileId: usesNativeAuth ? null : authProfileId,
|
||||
authProfileStore,
|
||||
config: options?.config,
|
||||
timeoutMs: options?.timeoutMs,
|
||||
onStartedClient: options?.onStartedClient,
|
||||
});
|
||||
}
|
||||
|
||||
async function startInitializedCodexAppServerClient(params: {
|
||||
startOptions: CodexAppServerStartOptions;
|
||||
agentDir: string;
|
||||
authProfileId: string | null | undefined;
|
||||
authProfileStore?: AuthProfileStore;
|
||||
config?: CodexAppServerClientOptions["config"];
|
||||
timeoutMs?: number;
|
||||
onStartedClient?: (client: CodexAppServerClient) => void;
|
||||
}): Promise<CodexAppServerClient> {
|
||||
const startOptionsCandidates = resolveManagedFallbackStartOptions(params.startOptions);
|
||||
for (let index = 0; index < startOptionsCandidates.length; index += 1) {
|
||||
const startOptions = startOptionsCandidates[index];
|
||||
const client = CodexAppServerClient.start(startOptions);
|
||||
params.onStartedClient?.(client);
|
||||
const initialize = client.initialize();
|
||||
try {
|
||||
await withTimeout(initialize, params.timeoutMs ?? 0, "codex app-server initialize timed out");
|
||||
} catch (error) {
|
||||
client.close();
|
||||
void initialize.catch(() => undefined);
|
||||
if (shouldTryManagedFallbackStartOption(error, startOptions, index, startOptionsCandidates)) {
|
||||
continue;
|
||||
}
|
||||
return await refreshCodexAppServerAuthTokens({
|
||||
agentDir,
|
||||
authProfileId,
|
||||
...(authProfileStore ? { authProfileStore } : {}),
|
||||
config: options?.config,
|
||||
throw error;
|
||||
}
|
||||
|
||||
if (params.authProfileId) {
|
||||
// Profile-backed Codex auth is ephemeral. Keep the host refresh callback
|
||||
// available whether the profile came from a scoped store or persisted state.
|
||||
client.addRequestHandler(async (request) => {
|
||||
if (request.method !== "account/chatgptAuthTokens/refresh") {
|
||||
return undefined;
|
||||
}
|
||||
return await refreshCodexAppServerAuthTokens({
|
||||
agentDir: params.agentDir,
|
||||
authProfileId: params.authProfileId!,
|
||||
...(params.authProfileStore ? { authProfileStore: params.authProfileStore } : {}),
|
||||
config: params.config,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
await applyCodexAppServerAuthProfile({
|
||||
client,
|
||||
agentDir: params.agentDir,
|
||||
authProfileId: params.authProfileId,
|
||||
startOptions,
|
||||
config: params.config,
|
||||
...(params.authProfileStore ? { authProfileStore: params.authProfileStore } : {}),
|
||||
});
|
||||
return client;
|
||||
} catch (error) {
|
||||
client.close();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
const initialize = client.initialize();
|
||||
try {
|
||||
await withTimeout(initialize, options?.timeoutMs ?? 0, "codex app-server initialize timed out");
|
||||
await applyCodexAppServerAuthProfile({
|
||||
client,
|
||||
agentDir,
|
||||
authProfileId: usesNativeAuth ? null : authProfileId,
|
||||
startOptions,
|
||||
config: options?.config,
|
||||
...(authProfileStore ? { authProfileStore } : {}),
|
||||
});
|
||||
return client;
|
||||
} catch (error) {
|
||||
client.close();
|
||||
void initialize.catch(() => undefined);
|
||||
throw error;
|
||||
throw new Error("Managed Codex app-server fallback candidates were exhausted.");
|
||||
}
|
||||
|
||||
function resolveManagedFallbackStartOptions(
|
||||
startOptions: CodexAppServerStartOptions,
|
||||
): CodexAppServerStartOptions[] {
|
||||
const commands = [startOptions.command, ...(startOptions.managedFallbackCommandPaths ?? [])];
|
||||
const candidates: CodexAppServerStartOptions[] = [];
|
||||
for (let index = 0; index < commands.length; index += 1) {
|
||||
const command = commands[index];
|
||||
const managedFallbackCommandPaths = commands.slice(index + 1);
|
||||
const candidate = {
|
||||
...startOptions,
|
||||
command,
|
||||
};
|
||||
if (managedFallbackCommandPaths.length === 0) {
|
||||
delete candidate.managedFallbackCommandPaths;
|
||||
} else {
|
||||
candidate.managedFallbackCommandPaths = managedFallbackCommandPaths;
|
||||
}
|
||||
candidates.push(candidate);
|
||||
}
|
||||
return candidates;
|
||||
}
|
||||
|
||||
function shouldTryManagedFallbackStartOption(
|
||||
error: unknown,
|
||||
startOptions: CodexAppServerStartOptions,
|
||||
index: number,
|
||||
startOptionsCandidates: readonly CodexAppServerStartOptions[],
|
||||
): boolean {
|
||||
return (
|
||||
startOptions.commandSource === "resolved-managed" &&
|
||||
index < startOptionsCandidates.length - 1 &&
|
||||
isUnsupportedCodexAppServerVersionError(error)
|
||||
);
|
||||
}
|
||||
|
||||
/** Clears and closes all shared clients for deterministic tests. */
|
||||
|
||||
@@ -11,11 +11,7 @@ import type {
|
||||
PluginHookInboundClaimEvent,
|
||||
} from "openclaw/plugin-sdk/plugin-entry";
|
||||
import type { ReplyPayload } from "openclaw/plugin-sdk/reply-payload";
|
||||
import {
|
||||
loadSessionStore,
|
||||
resolveSessionStoreEntry,
|
||||
resolveStorePath,
|
||||
} from "openclaw/plugin-sdk/session-store-runtime";
|
||||
import { getSessionEntry, resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
|
||||
import { resolveCodexAppServerForModelProvider } from "./app-server/app-server-policy.js";
|
||||
import { resolveCodexAppServerAuthProfileIdForAgent } from "./app-server/auth-bridge.js";
|
||||
import { CODEX_CONTROL_METHODS } from "./app-server/capabilities.js";
|
||||
@@ -881,10 +877,11 @@ function readSessionExecOverrides(params: {
|
||||
return undefined;
|
||||
}
|
||||
const storePath = resolveStorePath(params.config.session?.store, { agentId: params.agentId });
|
||||
const entry = resolveSessionStoreEntry({
|
||||
store: loadSessionStore(storePath, { skipCache: true }),
|
||||
const entry = getSessionEntry({
|
||||
storePath,
|
||||
sessionKey,
|
||||
}).existing;
|
||||
readConsistency: "latest",
|
||||
});
|
||||
if (!entry?.execSecurity && !entry?.execAsk) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -31,15 +31,21 @@ vi.mock("openclaw/plugin-sdk/provider-auth-runtime", () => ({
|
||||
resolveApiKeyForProvider: resolveApiKeyForProviderMock,
|
||||
}));
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/provider-http", () => ({
|
||||
assertOkOrThrowHttpError: assertOkOrThrowHttpErrorMock,
|
||||
createProviderOperationDeadline: createProviderOperationDeadlineMock,
|
||||
postJsonRequest: postJsonRequestMock,
|
||||
postMultipartRequest: postMultipartRequestMock,
|
||||
resolveProviderHttpRequestConfig: resolveProviderHttpRequestConfigMock,
|
||||
resolveProviderOperationTimeoutMs: resolveProviderOperationTimeoutMsMock,
|
||||
sanitizeConfiguredModelProviderRequest: vi.fn((request) => request),
|
||||
}));
|
||||
vi.mock("openclaw/plugin-sdk/provider-http", async () => {
|
||||
const actual = await vi.importActual<typeof import("openclaw/plugin-sdk/provider-http")>(
|
||||
"openclaw/plugin-sdk/provider-http",
|
||||
);
|
||||
return {
|
||||
assertOkOrThrowHttpError: assertOkOrThrowHttpErrorMock,
|
||||
createProviderOperationDeadline: createProviderOperationDeadlineMock,
|
||||
postJsonRequest: postJsonRequestMock,
|
||||
postMultipartRequest: postMultipartRequestMock,
|
||||
readProviderJsonResponse: actual.readProviderJsonResponse,
|
||||
resolveProviderHttpRequestConfig: resolveProviderHttpRequestConfigMock,
|
||||
resolveProviderOperationTimeoutMs: resolveProviderOperationTimeoutMsMock,
|
||||
sanitizeConfiguredModelProviderRequest: vi.fn((request) => request),
|
||||
};
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
vi.doUnmock("openclaw/plugin-sdk/provider-auth-runtime");
|
||||
@@ -63,6 +69,13 @@ function requireFirstMockObjectArg(mock: ReturnType<typeof vi.fn>, label: string
|
||||
return value;
|
||||
}
|
||||
|
||||
function jsonResponse(payload: unknown): Response {
|
||||
return new Response(JSON.stringify(payload), {
|
||||
status: 200,
|
||||
headers: { "Content-Type": "application/json" },
|
||||
});
|
||||
}
|
||||
|
||||
describe("deepinfra image generation provider", () => {
|
||||
afterEach(() => {
|
||||
assertOkOrThrowHttpErrorMock.mockClear();
|
||||
@@ -86,11 +99,9 @@ describe("deepinfra image generation provider", () => {
|
||||
const release = vi.fn(async () => {});
|
||||
const jpegBytes = Buffer.from([0xff, 0xd8, 0xff, 0x00]);
|
||||
postJsonRequestMock.mockResolvedValue({
|
||||
response: {
|
||||
json: async () => ({
|
||||
data: [{ b64_json: jpegBytes.toString("base64"), revised_prompt: "red square" }],
|
||||
}),
|
||||
},
|
||||
response: jsonResponse({
|
||||
data: [{ b64_json: jpegBytes.toString("base64"), revised_prompt: "red square" }],
|
||||
}),
|
||||
release,
|
||||
});
|
||||
|
||||
@@ -168,17 +179,15 @@ describe("deepinfra image generation provider", () => {
|
||||
|
||||
it("sends image edits as multipart OpenAI-compatible requests", async () => {
|
||||
postMultipartRequestMock.mockResolvedValue({
|
||||
response: {
|
||||
json: async () => ({
|
||||
data: [
|
||||
{
|
||||
b64_json: Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]).toString(
|
||||
"base64",
|
||||
),
|
||||
},
|
||||
],
|
||||
}),
|
||||
},
|
||||
response: jsonResponse({
|
||||
data: [
|
||||
{
|
||||
b64_json: Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]).toString(
|
||||
"base64",
|
||||
),
|
||||
},
|
||||
],
|
||||
}),
|
||||
release: vi.fn(async () => {}),
|
||||
});
|
||||
|
||||
|
||||
@@ -172,6 +172,24 @@ describe("hydrateViewer", () => {
|
||||
expect(document.documentElement.dataset.openclawDiffsError).toBeUndefined();
|
||||
warn.mockRestore();
|
||||
});
|
||||
|
||||
it("replaces stale controllers when hydrating the current cards again", async () => {
|
||||
renderCard();
|
||||
const { controllers, hydrateViewer } = await import("./viewer-client.js");
|
||||
controllers.splice(0);
|
||||
|
||||
await hydrateViewer();
|
||||
expect(controllers).toHaveLength(1);
|
||||
const firstController = controllers[0];
|
||||
|
||||
document.body.innerHTML = "";
|
||||
renderCard();
|
||||
await hydrateViewer();
|
||||
|
||||
expect(controllers).toHaveLength(1);
|
||||
expect(controllers[0]).not.toBe(firstController);
|
||||
expect(fileDiffHydrateMock).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("viewerState initialization", () => {
|
||||
|
||||
@@ -287,6 +287,9 @@ function syncAllControllers(): void {
|
||||
}
|
||||
|
||||
export async function hydrateViewer(): Promise<void> {
|
||||
// Rehydration replaces the current DOM card set; do not retain controllers
|
||||
// from a previous render because they can keep stale DOM references alive.
|
||||
controllers.length = 0;
|
||||
const cards = await Promise.all(
|
||||
getCards().map(async ({ host, payload }) => ({
|
||||
host,
|
||||
|
||||
@@ -175,6 +175,7 @@ type DispatchInboundParams = {
|
||||
}) => Promise<void> | void;
|
||||
onReplyStart?: () => Promise<void> | void;
|
||||
sourceReplyDeliveryMode?: "automatic" | "message_tool_only";
|
||||
typingKeepalive?: boolean;
|
||||
disableBlockStreaming?: boolean;
|
||||
suppressDefaultToolProgressMessages?: boolean;
|
||||
queuedDeliveryCorrelations?: Array<{ begin: () => () => void }>;
|
||||
@@ -944,6 +945,7 @@ describe("processDiscordMessage ack reactions", () => {
|
||||
expect(replyTypingFeedback.onReplyStart).toHaveBeenCalledTimes(1);
|
||||
expect(replyTypingFeedback.onIdle).toHaveBeenCalledTimes(1);
|
||||
expect(replyTypingFeedback.onCleanup).toHaveBeenCalledTimes(1);
|
||||
expect(getLastDispatchReplyOptions()?.typingKeepalive).toBe(false);
|
||||
expect(typingMocks.sendTyping).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -984,6 +986,33 @@ describe("processDiscordMessage ack reactions", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps one typing refresh loop for default message-tool replies", async () => {
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
dispatchInboundMessage.mockImplementationOnce(async (params?: DispatchInboundParams) => {
|
||||
await params?.replyOptions?.onReplyStart?.();
|
||||
await vi.advanceTimersByTimeAsync(3_500);
|
||||
return createNoQueuedDispatchResult();
|
||||
});
|
||||
const ctx = await createBaseContext({
|
||||
shouldRequireMention: false,
|
||||
effectiveWasMentioned: false,
|
||||
cfg: {
|
||||
messages: { groupChat: { visibleReplies: "message_tool" } },
|
||||
session: { store: "/tmp/openclaw-discord-process-test-sessions.json" },
|
||||
},
|
||||
route: BASE_CHANNEL_ROUTE,
|
||||
});
|
||||
|
||||
await runProcessDiscordMessage(ctx);
|
||||
|
||||
expect(getLastDispatchReplyOptions()?.typingKeepalive).toBe(false);
|
||||
expect(typingMocks.sendTyping).toHaveBeenCalledTimes(2);
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
}
|
||||
});
|
||||
|
||||
it("debounces intermediate phase reactions and jumps to done for short runs", async () => {
|
||||
dispatchInboundMessage.mockImplementationOnce(async (params?: DispatchInboundParams) => {
|
||||
await params?.replyOptions?.onReasoningStream?.();
|
||||
@@ -1532,6 +1561,7 @@ describe("processDiscordMessage session routing", () => {
|
||||
|
||||
expectRecordFields(requireRecord(getLastDispatchReplyOptions(), "dispatch reply options"), {
|
||||
sourceReplyDeliveryMode: "message_tool_only",
|
||||
typingKeepalive: false,
|
||||
disableBlockStreaming: true,
|
||||
});
|
||||
expect(createDiscordDraftStream).not.toHaveBeenCalled();
|
||||
|
||||
@@ -251,6 +251,14 @@ async function processDiscordMessageInner(
|
||||
},
|
||||
});
|
||||
const sourceRepliesAreToolOnly = sourceReplyDeliveryMode === "message_tool_only";
|
||||
const configuredTypingMode = cfg.session?.typingMode ?? cfg.agents?.defaults?.typingMode;
|
||||
const configuredTypingInterval =
|
||||
cfg.agents?.defaults?.typingIntervalSeconds ?? cfg.session?.typingIntervalSeconds;
|
||||
const shouldDisableCoreTypingKeepalive =
|
||||
Boolean(replyTypingFeedback) ||
|
||||
(sourceRepliesAreToolOnly &&
|
||||
configuredTypingMode === undefined &&
|
||||
configuredTypingInterval === undefined);
|
||||
const ackReaction = resolveAckReaction(cfg, route.agentId, {
|
||||
channel: "discord",
|
||||
accountId,
|
||||
@@ -460,6 +468,7 @@ async function processDiscordMessageInner(
|
||||
channelId: typingChannelId,
|
||||
rest: feedbackRest,
|
||||
log: logVerbose,
|
||||
keepaliveIntervalMs: shouldDisableCoreTypingKeepalive ? undefined : 0,
|
||||
});
|
||||
if (replyTypingFeedback) {
|
||||
// A carried prestart only covers queue wait time; dispatch needs a fresh
|
||||
@@ -955,6 +964,7 @@ async function processDiscordMessageInner(
|
||||
abortSignal,
|
||||
skillFilter: channelConfig?.skills,
|
||||
sourceReplyDeliveryMode,
|
||||
typingKeepalive: shouldDisableCoreTypingKeepalive ? false : undefined,
|
||||
queuedDeliveryCorrelations: isRoomEvent ? [{ begin: beginDeliveryCorrelation }] : undefined,
|
||||
suppressTyping: isRoomEvent ? true : undefined,
|
||||
allowProgressCallbacksWhenSourceDeliverySuppressed:
|
||||
|
||||
@@ -222,6 +222,34 @@ describe("createDiscordMessageHandler queue behavior", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps the configured typing cadence for prestarted feedback", async () => {
|
||||
preflightDiscordMessageMock.mockReset();
|
||||
processDiscordMessageMock.mockReset();
|
||||
preflightDiscordMessageMock.mockImplementation(async () =>
|
||||
createAcceptedDmPreflightContext({
|
||||
cfg: {
|
||||
...createPreflightContext().cfg,
|
||||
agents: { defaults: { typingIntervalSeconds: 7 } },
|
||||
session: { typingIntervalSeconds: 5 },
|
||||
},
|
||||
}),
|
||||
);
|
||||
processDiscordMessageMock.mockResolvedValue(undefined);
|
||||
const replyTypingFeedback = createReplyTypingFeedbackMock("dm-1");
|
||||
const createReplyTypingFeedback = vi.fn(() => replyTypingFeedback);
|
||||
|
||||
const handler = createDiscordMessageHandler({
|
||||
...createDiscordHandlerParams(),
|
||||
testing: { createReplyTypingFeedback },
|
||||
});
|
||||
await handler(createMessageData("m-typing-cadence", "dm-1") as never, {} as never);
|
||||
await flushQueueWork();
|
||||
|
||||
expect(createReplyTypingFeedback).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ keepaliveIntervalMs: 7_000 }),
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps accepted DM dispatch running when accepted typing feedback fails", async () => {
|
||||
preflightDiscordMessageMock.mockReset();
|
||||
processDiscordMessageMock.mockReset();
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
createChannelInboundDebouncer,
|
||||
shouldDebounceTextInbound,
|
||||
} from "openclaw/plugin-sdk/channel-inbound";
|
||||
import { finiteSecondsToTimerSafeMilliseconds } from "openclaw/plugin-sdk/number-runtime";
|
||||
import { danger, logVerbose } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { resolveOpenProviderRuntimeGroupPolicy } from "openclaw/plugin-sdk/runtime-group-policy";
|
||||
import type { Client } from "../internal/discord.js";
|
||||
@@ -102,6 +103,9 @@ function startAcceptedTypingFeedback(params: {
|
||||
accountId: ctx.accountId,
|
||||
channelId: ctx.messageChannelId,
|
||||
log: logVerbose,
|
||||
keepaliveIntervalMs: finiteSecondsToTimerSafeMilliseconds(
|
||||
ctx.cfg.agents?.defaults?.typingIntervalSeconds ?? ctx.cfg.session?.typingIntervalSeconds,
|
||||
),
|
||||
});
|
||||
const cleanup = replyTypingFeedback.onCleanup;
|
||||
replyTypingFeedback.onCleanup = () => {
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
} from "openclaw/plugin-sdk/command-auth-native";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
import type { ResolvedAgentRoute } from "openclaw/plugin-sdk/routing";
|
||||
import { loadSessionStore, resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
|
||||
import { getSessionEntry, resolveStorePath } from "openclaw/plugin-sdk/session-store-runtime";
|
||||
import {
|
||||
normalizeLowercaseStringOrEmpty,
|
||||
normalizeOptionalString,
|
||||
@@ -202,11 +202,10 @@ export async function resolveDiscordNativeChoiceContext(params: {
|
||||
const storePath = resolveStorePath(params.cfg.session?.store, {
|
||||
agentId: route.agentId,
|
||||
});
|
||||
const sessionStore = loadSessionStore(storePath);
|
||||
const sessionEntry = sessionStore[route.sessionKey];
|
||||
const sessionEntry = getSessionEntry({ storePath, sessionKey: route.sessionKey });
|
||||
const override = resolveStoredModelOverride({
|
||||
sessionEntry,
|
||||
sessionStore,
|
||||
loadSessionEntry: (sessionKey) => getSessionEntry({ storePath, sessionKey }),
|
||||
sessionKey: route.sessionKey,
|
||||
defaultProvider: fallback.provider,
|
||||
});
|
||||
@@ -238,11 +237,15 @@ export function resolveDiscordModelPickerCurrentModel(params: {
|
||||
const storePath = resolveStorePath(params.cfg.session?.store, {
|
||||
agentId: params.route.agentId,
|
||||
});
|
||||
const sessionStore = loadSessionStore(storePath, { skipCache: true });
|
||||
const sessionEntry = sessionStore[params.route.sessionKey];
|
||||
const sessionEntry = getSessionEntry({
|
||||
storePath,
|
||||
sessionKey: params.route.sessionKey,
|
||||
readConsistency: "latest",
|
||||
});
|
||||
const override = resolveStoredModelOverride({
|
||||
sessionEntry,
|
||||
sessionStore,
|
||||
loadSessionEntry: (sessionKey) =>
|
||||
getSessionEntry({ storePath, sessionKey, readConsistency: "latest" }),
|
||||
sessionKey: params.route.sessionKey,
|
||||
defaultProvider: params.data.resolvedDefault.provider,
|
||||
});
|
||||
@@ -267,9 +270,12 @@ export function resolveDiscordModelPickerCurrentRuntime(params: {
|
||||
const storePath = resolveStorePath(params.cfg.session?.store, {
|
||||
agentId: params.route.agentId,
|
||||
});
|
||||
const sessionStore = loadSessionStore(storePath, { skipCache: true });
|
||||
const sessionRuntime = normalizeOptionalString(
|
||||
sessionStore[params.route.sessionKey]?.agentRuntimeOverride,
|
||||
getSessionEntry({
|
||||
storePath,
|
||||
sessionKey: params.route.sessionKey,
|
||||
readConsistency: "latest",
|
||||
})?.agentRuntimeOverride,
|
||||
);
|
||||
if (sessionRuntime) {
|
||||
return sessionRuntime;
|
||||
|
||||
@@ -24,6 +24,7 @@ export function createDiscordReplyTypingFeedback(params: {
|
||||
rest?: RequestClient;
|
||||
log: (message: string) => void;
|
||||
maxDurationMs?: number;
|
||||
keepaliveIntervalMs?: number;
|
||||
}): DiscordReplyTypingFeedback {
|
||||
let channelId = params.channelId;
|
||||
const rest =
|
||||
@@ -44,6 +45,7 @@ export function createDiscordReplyTypingFeedback(params: {
|
||||
error: err,
|
||||
});
|
||||
},
|
||||
keepaliveIntervalMs: params.keepaliveIntervalMs,
|
||||
maxDurationMs: params.maxDurationMs ?? DISCORD_REPLY_TYPING_MAX_DURATION_MS,
|
||||
});
|
||||
const updateChannelId = (nextChannelId: string) => {
|
||||
|
||||
@@ -345,7 +345,7 @@ describe("discordOutbound", () => {
|
||||
2,
|
||||
);
|
||||
expect(messageOptions.accountId).toBe("default");
|
||||
expect(messageOptions.replyTo).toBeUndefined();
|
||||
expect(messageOptions.replyTo).toBe("reply-1");
|
||||
|
||||
const mediaCall = mockCall(hoisted.sendMessageDiscordMock, "sendMessageDiscord", 1);
|
||||
expect(mediaCall[0]).toBe("channel:123456");
|
||||
@@ -353,7 +353,7 @@ describe("discordOutbound", () => {
|
||||
const mediaOptions = mockObjectArg(hoisted.sendMessageDiscordMock, "sendMessageDiscord", 1, 2);
|
||||
expect(mediaOptions.accountId).toBe("default");
|
||||
expect(mediaOptions.mediaUrl).toBe("https://example.com/extra.png");
|
||||
expect(mediaOptions.replyTo).toBeUndefined();
|
||||
expect(mediaOptions.replyTo).toBe("reply-1");
|
||||
expect(result).toEqual({
|
||||
channel: "discord",
|
||||
messageId: "msg-1",
|
||||
@@ -361,6 +361,31 @@ describe("discordOutbound", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps captured replyTo on audioAsVoice sends when replyToMode is batched", async () => {
|
||||
await discordOutbound.sendPayload?.({
|
||||
cfg: {},
|
||||
to: "channel:123456",
|
||||
text: "",
|
||||
payload: {
|
||||
text: "voice note",
|
||||
mediaUrls: ["https://example.com/voice.ogg", "https://example.com/extra.png"],
|
||||
audioAsVoice: true,
|
||||
},
|
||||
accountId: "default",
|
||||
replyToId: "reply-1",
|
||||
replyToMode: "batched",
|
||||
});
|
||||
|
||||
expect(
|
||||
mockObjectArg(hoisted.sendVoiceMessageDiscordMock, "sendVoiceMessageDiscord", 0, 2).replyTo,
|
||||
).toBe("reply-1");
|
||||
expect(
|
||||
hoisted.sendMessageDiscordMock.mock.calls.map(
|
||||
(call) => (call[2] as { replyTo?: unknown } | undefined)?.replyTo,
|
||||
),
|
||||
).toEqual(["reply-1", "reply-1"]);
|
||||
});
|
||||
|
||||
it("keeps replyToId on every internal audioAsVoice send when replyToMode is all", async () => {
|
||||
await discordOutbound.sendPayload?.({
|
||||
cfg: {},
|
||||
|
||||
@@ -84,13 +84,15 @@ export async function sendDiscordOutboundPayload(params: {
|
||||
const sendContext = await createDiscordPayloadSendContext(ctx);
|
||||
|
||||
if (payload.audioAsVoice && mediaUrls.length > 0) {
|
||||
// audioAsVoice emits one logical Discord reply across voice/text/media sends.
|
||||
// Capture before helper calls consume implicit single-use reply targets.
|
||||
const voiceReplyTo = sendContext.resolveReplyTo();
|
||||
let lastResult = await sendContext.withRetry(
|
||||
async () =>
|
||||
await sendContext.sendVoice(
|
||||
sendContext.target,
|
||||
mediaUrls[0],
|
||||
resolveDiscordDeliveryOptions(ctx, sendContext),
|
||||
),
|
||||
await sendContext.sendVoice(sendContext.target, mediaUrls[0], {
|
||||
...resolveDiscordDeliveryOptions(ctx, sendContext),
|
||||
replyTo: voiceReplyTo,
|
||||
}),
|
||||
);
|
||||
if (payload.text?.trim()) {
|
||||
lastResult = await sendContext.withRetry(
|
||||
@@ -98,6 +100,7 @@ export async function sendDiscordOutboundPayload(params: {
|
||||
await sendContext.send(sendContext.target, payload.text, {
|
||||
verbose: false,
|
||||
...resolveDiscordFormattedDeliveryOptions(ctx, sendContext),
|
||||
replyTo: voiceReplyTo,
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -107,6 +110,7 @@ export async function sendDiscordOutboundPayload(params: {
|
||||
await sendContext.send(sendContext.target, "", {
|
||||
verbose: false,
|
||||
...resolveDiscordMediaDeliveryOptions(ctx, sendContext, mediaUrl),
|
||||
replyTo: voiceReplyTo,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -55,20 +55,35 @@ describe("PDF document extractor", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("extracts text first and renders fallback images through clawpdf", async () => {
|
||||
pdfDocument.extract.mockResolvedValueOnce({ text: "", images: [] }).mockResolvedValueOnce({
|
||||
text: "",
|
||||
images: [
|
||||
{
|
||||
type: "image",
|
||||
bytes: Uint8Array.from(Buffer.from("png")),
|
||||
mimeType: "image/png",
|
||||
page: 1,
|
||||
width: 10,
|
||||
height: 10,
|
||||
},
|
||||
],
|
||||
});
|
||||
it("extracts text first and renders each fallback page with its own pixel budget", async () => {
|
||||
pdfDocument.extract
|
||||
.mockResolvedValueOnce({ text: "", images: [] })
|
||||
.mockResolvedValueOnce({
|
||||
text: "",
|
||||
images: [
|
||||
{
|
||||
type: "image",
|
||||
bytes: Uint8Array.from(Buffer.from("png1")),
|
||||
mimeType: "image/png",
|
||||
page: 1,
|
||||
width: 5,
|
||||
height: 10,
|
||||
},
|
||||
],
|
||||
})
|
||||
.mockResolvedValueOnce({
|
||||
text: "",
|
||||
images: [
|
||||
{
|
||||
type: "image",
|
||||
bytes: Uint8Array.from(Buffer.from("png2")),
|
||||
mimeType: "image/png",
|
||||
page: 2,
|
||||
width: 5,
|
||||
height: 10,
|
||||
},
|
||||
],
|
||||
});
|
||||
const extractor = createPdfDocumentExtractor();
|
||||
|
||||
const result = await extractor.extract(request());
|
||||
@@ -82,18 +97,24 @@ describe("PDF document extractor", () => {
|
||||
maxPages: 2,
|
||||
maxTextChars: 200_000,
|
||||
});
|
||||
// Each page renders in its own extract() call, with the aggregate pixel cap
|
||||
// allocated across selected pages so later pages are not starved.
|
||||
expect(pdfDocument.extract).toHaveBeenNthCalledWith(2, {
|
||||
mode: "images",
|
||||
maxPages: 2,
|
||||
image: {
|
||||
maxDimension: 10_000,
|
||||
maxPixels: 100,
|
||||
forms: true,
|
||||
},
|
||||
pages: [1],
|
||||
image: { maxDimension: 10_000, maxPixels: 50, forms: true },
|
||||
});
|
||||
expect(pdfDocument.extract).toHaveBeenNthCalledWith(3, {
|
||||
mode: "images",
|
||||
pages: [2],
|
||||
image: { maxDimension: 10_000, maxPixels: 50, forms: true },
|
||||
});
|
||||
expect(result).toEqual({
|
||||
text: "",
|
||||
images: [{ type: "image", data: "cG5n", mimeType: "image/png" }],
|
||||
images: [
|
||||
{ type: "image", data: "cG5nMQ==", mimeType: "image/png" },
|
||||
{ type: "image", data: "cG5nMg==", mimeType: "image/png" },
|
||||
],
|
||||
});
|
||||
expect(pdfDocument.destroy).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
@@ -131,8 +152,9 @@ describe("PDF document extractor", () => {
|
||||
expect(pdfDocument.destroy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("filters selected pages before passing them to clawpdf", async () => {
|
||||
it("filters selected pages and renders them one page per image call", async () => {
|
||||
pdfDocument.extract
|
||||
.mockResolvedValueOnce({ text: "", images: [] })
|
||||
.mockResolvedValueOnce({ text: "", images: [] })
|
||||
.mockResolvedValueOnce({ text: "", images: [] });
|
||||
const extractor = createPdfDocumentExtractor();
|
||||
@@ -141,11 +163,15 @@ describe("PDF document extractor", () => {
|
||||
|
||||
expect(pdfDocument.extract).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
expect.objectContaining({ pages: [2, 1] }),
|
||||
expect.objectContaining({ mode: "text", pages: [2, 1] }),
|
||||
);
|
||||
expect(pdfDocument.extract).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
expect.objectContaining({ pages: [2, 1] }),
|
||||
expect.objectContaining({ mode: "images", pages: [2] }),
|
||||
);
|
||||
expect(pdfDocument.extract).toHaveBeenNthCalledWith(
|
||||
3,
|
||||
expect.objectContaining({ mode: "images", pages: [1] }),
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -83,17 +83,38 @@ async function extractPdfContent(
|
||||
return { text, images: [] };
|
||||
}
|
||||
|
||||
// clawpdf's image render budget (maxPixels) is shared across every page in one
|
||||
// extract() call: the first page consumes it and later pages collapse to 1x1
|
||||
// PNGs that vision models reject. Render each page separately, allocating the
|
||||
// remaining aggregate budget across pages that still need rendering.
|
||||
const imagePages =
|
||||
pages ?? Array.from({ length: Math.min(pdf.pageCount, request.maxPages) }, (_, i) => i + 1);
|
||||
|
||||
try {
|
||||
const imageResult = await pdf.extract({
|
||||
mode: "images",
|
||||
...pageSelection,
|
||||
image: {
|
||||
maxDimension: MAX_RENDER_DIMENSION,
|
||||
maxPixels: request.maxPixels,
|
||||
forms: true,
|
||||
},
|
||||
});
|
||||
return { text, images: imageResult.images.map(toDocumentImage) };
|
||||
const images: DocumentExtractedImage[] = [];
|
||||
let remainingPixels = request.maxPixels;
|
||||
for (let index = 0; index < imagePages.length; index += 1) {
|
||||
if (remainingPixels <= 0) {
|
||||
break;
|
||||
}
|
||||
const pagesRemaining = imagePages.length - index;
|
||||
const maxPixelsPerPage = Math.max(1, Math.ceil(remainingPixels / pagesRemaining));
|
||||
const pageNumber = imagePages[index];
|
||||
const imageResult = await pdf.extract({
|
||||
mode: "images",
|
||||
pages: [pageNumber],
|
||||
image: {
|
||||
maxDimension: MAX_RENDER_DIMENSION,
|
||||
maxPixels: maxPixelsPerPage,
|
||||
forms: true,
|
||||
},
|
||||
});
|
||||
for (const image of imageResult.images) {
|
||||
images.push(toDocumentImage(image));
|
||||
remainingPixels -= image.width * image.height;
|
||||
}
|
||||
}
|
||||
return { text, images };
|
||||
} catch (err) {
|
||||
request.onImageExtractionError?.(err);
|
||||
return { text, images: [] };
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Duckduckgo plugin module implements ddg client behavior.
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
|
||||
import { readProviderTextResponse } from "openclaw/plugin-sdk/provider-http";
|
||||
import {
|
||||
DEFAULT_CACHE_TTL_MINUTES,
|
||||
DEFAULT_SEARCH_COUNT,
|
||||
@@ -113,6 +114,10 @@ function isBotChallenge(html: string): boolean {
|
||||
return /g-recaptcha|are you a human|id="challenge-form"|name="challenge"/i.test(html);
|
||||
}
|
||||
|
||||
async function readDuckDuckGoHtmlResponse(response: Response): Promise<string> {
|
||||
return await readProviderTextResponse(response, "DuckDuckGo search");
|
||||
}
|
||||
|
||||
function parseDuckDuckGoHtml(html: string): DuckDuckGoResult[] {
|
||||
const results: DuckDuckGoResult[] = [];
|
||||
const resultRegex = /<a\b(?=[^>]*\bclass="[^"]*\bresult__a\b[^"]*")([^>]*)>([\s\S]*?)<\/a>/gi;
|
||||
@@ -202,7 +207,7 @@ export async function runDuckDuckGoSearch(params: {
|
||||
);
|
||||
}
|
||||
|
||||
const html = await response.text();
|
||||
const html = await readDuckDuckGoHtmlResponse(response);
|
||||
if (isBotChallenge(html)) {
|
||||
throw new Error("DuckDuckGo returned a bot-detection challenge.");
|
||||
}
|
||||
@@ -238,5 +243,6 @@ export const testing = {
|
||||
decodeHtmlEntities,
|
||||
isBotChallenge,
|
||||
parseDuckDuckGoHtml,
|
||||
readDuckDuckGoHtmlResponse,
|
||||
};
|
||||
export { testing as __testing };
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// Duckduckgo tests cover ddg search provider plugin behavior.
|
||||
import { afterAll, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { createStreamingResponse } from "../../test-support/streaming-error-response.js";
|
||||
import { createDuckDuckGoWebSearchProvider as createDuckDuckGoWebSearchContractProvider } from "../web-search-contract-api.js";
|
||||
import { DEFAULT_DDG_SAFE_SEARCH, resolveDdgRegion, resolveDdgSafeSearch } from "./config.js";
|
||||
|
||||
@@ -104,6 +105,24 @@ describe("duckduckgo web search provider", () => {
|
||||
expect(runDuckDuckGoSearch).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("bounds successful DuckDuckGo HTML bodies without using response.text()", async () => {
|
||||
const streamed = createStreamingResponse({
|
||||
chunkCount: 32,
|
||||
chunkSize: 1024 * 1024,
|
||||
text: "x",
|
||||
headers: { "Content-Type": "text/html" },
|
||||
});
|
||||
const textSpy = vi.spyOn(streamed.response, "text").mockRejectedValue(new Error("unbounded"));
|
||||
|
||||
await expect(ddgClientTesting.readDuckDuckGoHtmlResponse(streamed.response)).rejects.toThrow(
|
||||
"DuckDuckGo search: text response exceeds 16777216 bytes",
|
||||
);
|
||||
|
||||
expect(streamed.getReadCount()).toBeLessThan(32);
|
||||
expect(streamed.wasCanceled()).toBe(true);
|
||||
expect(textSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("reads region from plugin config and normalizes empty values away", () => {
|
||||
expect(
|
||||
resolveDdgRegion({
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
// Elevenlabs provider module implements model/runtime integration.
|
||||
import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime";
|
||||
import { parseStrictFiniteNumber, parseStrictInteger } from "openclaw/plugin-sdk/number-runtime";
|
||||
import { assertOkOrThrowProviderError } from "openclaw/plugin-sdk/provider-http";
|
||||
import {
|
||||
assertOkOrThrowProviderError,
|
||||
readProviderJsonResponse,
|
||||
} from "openclaw/plugin-sdk/provider-http";
|
||||
import { normalizeResolvedSecretInputString } from "openclaw/plugin-sdk/secret-input";
|
||||
import type {
|
||||
SpeechDirectiveTokenParseContext,
|
||||
@@ -367,14 +370,14 @@ async function listElevenLabsVoices(params: {
|
||||
});
|
||||
try {
|
||||
await assertOkOrThrowProviderError(response, "ElevenLabs voices API error");
|
||||
const json = (await response.json()) as {
|
||||
const json = await readProviderJsonResponse<{
|
||||
voices?: Array<{
|
||||
voice_id?: string;
|
||||
name?: string;
|
||||
category?: string;
|
||||
description?: string;
|
||||
}>;
|
||||
};
|
||||
}>(response, "elevenlabs.voices");
|
||||
return Array.isArray(json.voices)
|
||||
? json.voices
|
||||
.map((voice) => ({
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
wrapWebContent,
|
||||
writeCachedSearchPayload,
|
||||
} from "openclaw/plugin-sdk/provider-web-search";
|
||||
import { readResponseWithLimit } from "openclaw/plugin-sdk/response-limit-runtime";
|
||||
import {
|
||||
normalizeOptionalLowercaseString,
|
||||
normalizeOptionalString,
|
||||
@@ -30,6 +31,10 @@ const EXA_SEARCH_TYPES = ["auto", "neural", "fast", "deep", "deep-reasoning", "i
|
||||
const EXA_FRESHNESS_VALUES = ["day", "week", "month", "year"] as const;
|
||||
const EXA_MAX_SEARCH_COUNT = 100;
|
||||
const EXA_ERROR_BODY_LIMIT_BYTES = 8 * 1024;
|
||||
// Exa search responses are untrusted external bodies. Cap the success JSON the
|
||||
// same way other bundled providers do (16 MiB) so a misbehaving or hostile
|
||||
// endpoint cannot stream an unbounded body into memory before we parse it.
|
||||
const EXA_SEARCH_JSON_MAX_BYTES = 16 * 1024 * 1024;
|
||||
|
||||
type ExaConfig = {
|
||||
apiKey?: string;
|
||||
@@ -70,9 +75,17 @@ type ExaSearchResponse = {
|
||||
results?: unknown;
|
||||
};
|
||||
|
||||
async function readExaSearchResults(response: Response): Promise<ExaSearchResult[]> {
|
||||
async function readExaSearchResults(
|
||||
response: Response,
|
||||
opts?: { maxBytes?: number },
|
||||
): Promise<ExaSearchResult[]> {
|
||||
const maxBytes = opts?.maxBytes ?? EXA_SEARCH_JSON_MAX_BYTES;
|
||||
const bytes = await readResponseWithLimit(response, maxBytes, {
|
||||
onOverflow: ({ maxBytes: maxBytesLocal }) =>
|
||||
new Error(`Exa API response exceeds ${maxBytesLocal} bytes`),
|
||||
});
|
||||
try {
|
||||
return normalizeExaResults(await response.json());
|
||||
return normalizeExaResults(JSON.parse(new TextDecoder().decode(bytes)));
|
||||
} catch (cause) {
|
||||
throw new Error("Exa API returned malformed JSON", { cause });
|
||||
}
|
||||
|
||||
@@ -26,6 +26,33 @@ function cancelTrackedResponse(
|
||||
};
|
||||
}
|
||||
|
||||
function streamingJsonResponse(params: { chunkCount: number; chunkSize: number }): {
|
||||
response: Response;
|
||||
getReadCount: () => number;
|
||||
} {
|
||||
// Streaming fixture proves an oversized success body stops being read before
|
||||
// the whole payload is buffered into memory.
|
||||
let reads = 0;
|
||||
const encoder = new TextEncoder();
|
||||
const stream = new ReadableStream<Uint8Array>({
|
||||
pull(controller) {
|
||||
if (reads >= params.chunkCount) {
|
||||
controller.close();
|
||||
return;
|
||||
}
|
||||
reads += 1;
|
||||
controller.enqueue(encoder.encode("a".repeat(params.chunkSize)));
|
||||
},
|
||||
});
|
||||
return {
|
||||
response: new Response(stream, {
|
||||
status: 200,
|
||||
headers: { "content-type": "application/json" },
|
||||
}),
|
||||
getReadCount: () => reads,
|
||||
};
|
||||
}
|
||||
|
||||
describe("exa web search provider", () => {
|
||||
it("exposes the expected metadata and selection wiring", () => {
|
||||
const provider = createExaWebSearchProvider();
|
||||
@@ -265,6 +292,27 @@ describe("exa web search provider", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("parses well-formed Exa search JSON under the byte cap", async () => {
|
||||
const response = new Response(
|
||||
JSON.stringify({ results: [{ url: "https://example.com", title: "Example" }] }),
|
||||
{ status: 200, headers: { "content-type": "application/json" } },
|
||||
);
|
||||
|
||||
await expect(testing.readExaSearchResults(response)).resolves.toEqual([
|
||||
{ url: "https://example.com", title: "Example" },
|
||||
]);
|
||||
});
|
||||
|
||||
it("caps oversized Exa search JSON instead of buffering the whole body", async () => {
|
||||
const streamed = streamingJsonResponse({ chunkCount: 64, chunkSize: 1024 });
|
||||
|
||||
await expect(
|
||||
testing.readExaSearchResults(streamed.response, { maxBytes: 4096 }),
|
||||
).rejects.toThrow(/Exa API response exceeds 4096 bytes/);
|
||||
|
||||
expect(streamed.getReadCount()).toBeLessThan(64);
|
||||
});
|
||||
|
||||
it("bounds Exa API error bodies without using response.text()", async () => {
|
||||
const tracked = cancelTrackedResponse(`${"exa upstream unavailable ".repeat(1024)}tail`, {
|
||||
status: 503,
|
||||
|
||||
@@ -12,6 +12,7 @@ import { isProviderApiKeyConfigured } from "openclaw/plugin-sdk/provider-auth";
|
||||
import {
|
||||
assertOkOrThrowHttpError,
|
||||
assertOkOrThrowProviderError,
|
||||
readProviderJsonResponse,
|
||||
} from "openclaw/plugin-sdk/provider-http";
|
||||
import { readResponseWithLimit } from "openclaw/plugin-sdk/response-limit-runtime";
|
||||
import {
|
||||
@@ -645,7 +646,9 @@ export function buildFalImageGenerationProvider(): ImageGenerationProvider {
|
||||
try {
|
||||
await assertOkOrThrowHttpError(response, "fal image generation failed");
|
||||
|
||||
const payload = parseFalImageGenerationResponse(await response.json());
|
||||
const payload = parseFalImageGenerationResponse(
|
||||
await readProviderJsonResponse(response, "fal.image-generation"),
|
||||
);
|
||||
const images: GeneratedImageAsset[] = [];
|
||||
let imageIndex = 0;
|
||||
for (const entry of payload.images) {
|
||||
|
||||
@@ -43,10 +43,7 @@ export {
|
||||
filterSupplementalContextItems,
|
||||
resolveChannelContextVisibilityMode,
|
||||
} from "openclaw/plugin-sdk/context-visibility-runtime";
|
||||
export {
|
||||
loadSessionStore,
|
||||
resolveSessionStoreEntry,
|
||||
} from "openclaw/plugin-sdk/session-store-runtime";
|
||||
export { getSessionEntry } from "openclaw/plugin-sdk/session-store-runtime";
|
||||
export { readJsonFileWithFallback } from "openclaw/plugin-sdk/json-store";
|
||||
export { normalizeAgentId } from "openclaw/plugin-sdk/routing";
|
||||
export { chunkTextForOutbound } from "openclaw/plugin-sdk/text-chunking";
|
||||
|
||||
@@ -29,21 +29,30 @@ function expectExplicitDefaultAccountSelection(
|
||||
expect(account.appId).toBe(appId);
|
||||
}
|
||||
|
||||
function withEnvVar(key: string, value: string | undefined, run: () => void) {
|
||||
function setTestEnvValue(key: string, value: string | undefined): () => void {
|
||||
const prev = process.env[key];
|
||||
if (value === undefined) {
|
||||
delete process.env[key];
|
||||
Reflect.deleteProperty(process.env, key);
|
||||
} else {
|
||||
process.env[key] = value;
|
||||
Reflect.set(process.env, key, value);
|
||||
}
|
||||
return () => restoreTestEnvValue(key, prev);
|
||||
}
|
||||
|
||||
function restoreTestEnvValue(key: string, value: string | undefined): void {
|
||||
if (value === undefined) {
|
||||
Reflect.deleteProperty(process.env, key);
|
||||
} else {
|
||||
Reflect.set(process.env, key, value);
|
||||
}
|
||||
}
|
||||
|
||||
function withEnvVar(key: string, value: string | undefined, run: () => void): void {
|
||||
const restore = setTestEnvValue(key, value);
|
||||
try {
|
||||
run();
|
||||
} finally {
|
||||
if (prev === undefined) {
|
||||
delete process.env[key];
|
||||
} else {
|
||||
process.env[key] = prev;
|
||||
}
|
||||
restore();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,8 +223,7 @@ describe("resolveFeishuCredentials", () => {
|
||||
|
||||
it("resolves env SecretRef objects when unresolved refs are allowed", () => {
|
||||
const key = "FEISHU_APP_SECRET_TEST";
|
||||
const prev = process.env[key];
|
||||
process.env[key] = " secret_from_env ";
|
||||
const restore = setTestEnvValue(key, " secret_from_env ");
|
||||
|
||||
try {
|
||||
const creds = resolveFeishuCredentials(
|
||||
@@ -234,18 +242,13 @@ describe("resolveFeishuCredentials", () => {
|
||||
domain: "feishu",
|
||||
});
|
||||
} finally {
|
||||
if (prev === undefined) {
|
||||
delete process.env[key];
|
||||
} else {
|
||||
process.env[key] = prev;
|
||||
}
|
||||
restore();
|
||||
}
|
||||
});
|
||||
|
||||
it("resolves env SecretRef with custom provider alias when unresolved refs are allowed", () => {
|
||||
const key = "FEISHU_APP_SECRET_CUSTOM_PROVIDER_TEST";
|
||||
const prev = process.env[key];
|
||||
process.env[key] = " secret_from_env_alias ";
|
||||
const restore = setTestEnvValue(key, " secret_from_env_alias ");
|
||||
|
||||
try {
|
||||
const creds = resolveFeishuCredentials(
|
||||
@@ -258,11 +261,7 @@ describe("resolveFeishuCredentials", () => {
|
||||
|
||||
expect(creds?.appSecret).toBe("secret_from_env_alias");
|
||||
} finally {
|
||||
if (prev === undefined) {
|
||||
delete process.env[key];
|
||||
} else {
|
||||
process.env[key] = prev;
|
||||
}
|
||||
restore();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
// Feishu tests cover app registration plugin behavior.
|
||||
import { MAX_TIMER_TIMEOUT_MS } from "openclaw/plugin-sdk/number-runtime";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { beginAppRegistration, pollAppRegistration } from "./app-registration.js";
|
||||
import { beginAppRegistration, pollAppRegistration, printQrCode } from "./app-registration.js";
|
||||
|
||||
const { fetchWithSsrFGuardMock } = vi.hoisted(() => ({
|
||||
const { fetchWithSsrFGuardMock, renderQrTerminalMock } = vi.hoisted(() => ({
|
||||
fetchWithSsrFGuardMock: vi.fn(),
|
||||
renderQrTerminalMock: vi.fn(async () => "terminal-qr"),
|
||||
}));
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/ssrf-runtime", () => ({
|
||||
fetchWithSsrFGuard: fetchWithSsrFGuardMock,
|
||||
}));
|
||||
|
||||
vi.mock("./qr-terminal.js", () => ({
|
||||
renderQrTerminal: renderQrTerminalMock,
|
||||
}));
|
||||
|
||||
function mockFeishuJson(payload: unknown) {
|
||||
fetchWithSsrFGuardMock.mockResolvedValueOnce({
|
||||
response: new Response(JSON.stringify(payload), { status: 200 }),
|
||||
@@ -23,6 +28,7 @@ describe("Feishu app registration", () => {
|
||||
vi.useRealTimers();
|
||||
vi.restoreAllMocks();
|
||||
fetchWithSsrFGuardMock.mockReset();
|
||||
renderQrTerminalMock.mockClear();
|
||||
});
|
||||
|
||||
it("defaults unsafe begin polling lifetimes from provider responses", async () => {
|
||||
@@ -59,4 +65,16 @@ describe("Feishu app registration", () => {
|
||||
await vi.runOnlyPendingTimersAsync();
|
||||
await expect(poll).resolves.toEqual({ status: "timeout" });
|
||||
});
|
||||
|
||||
it("prints scan-to-create QR codes with compact terminal rendering", async () => {
|
||||
const writeSpy = vi.spyOn(process.stdout, "write").mockImplementation(() => true);
|
||||
|
||||
await printQrCode("https://accounts.feishu.cn/verify?device_code=long-device-code");
|
||||
|
||||
expect(renderQrTerminalMock).toHaveBeenCalledWith(
|
||||
"https://accounts.feishu.cn/verify?device_code=long-device-code",
|
||||
{ small: true },
|
||||
);
|
||||
expect(writeSpy).toHaveBeenCalledWith("terminal-qr\n");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -266,7 +266,7 @@ export async function pollAppRegistration(params: {
|
||||
* otherwise the pattern is corrupted and cannot be scanned.
|
||||
*/
|
||||
export async function printQrCode(url: string): Promise<void> {
|
||||
const output = await renderQrTerminal(url);
|
||||
const output = await renderQrTerminal(url, { small: true });
|
||||
process.stdout.write(output.endsWith("\n") ? output : `${output}\n`);
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user