Compare commits

..

259 Commits

Author SHA1 Message Date
Dallin Romney
5d1c4af83e docs: add CLI LTS checklist pilot 2026-06-05 16:28:48 -07:00
Jason (Json)
36d9241cf7 docs: prefer web_fetch in weather skill (#90250)
* docs: prefer web_fetch in weather skill

* docs: use compact wttr json in weather skill
2026-06-05 14:35:55 -04:00
zenglingbiao
d896a4c7a3 fix(context-engine): forward isHeartbeat to afterTurn (fixes #89302) (#90632)
Merged via squash.

Prepared head SHA: 2f6da84c4b
Co-authored-by: zenglingbiao <290951975+zenglingbiao@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-06-05 11:27:37 -07:00
Gio Della-Libera
b3eba2ff38 fix(gateway): dedupe probe warnings by gateway identity (#85791)
Merged via squash.

Prepared head SHA: 13e3c00f56
Co-authored-by: giodl73-repo <235387111+giodl73-repo@users.noreply.github.com>
Co-authored-by: giodl_microsoft <115749436+giodl_microsoft@users.noreply.github.com>
Reviewed-by: @giodl_microsoft
2026-06-05 10:23:12 -07:00
Ted Li
21aa297434 fix(cron): auto-migrate legacy cron store (#90208)
Merged via squash.

Prepared head SHA: f5aa1b6759
Co-authored-by: MonkeyLeeT <6754057+MonkeyLeeT@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-06-05 10:22:02 -07:00
Shakker
4752e9a67d test: bracket provider worker env 2026-06-05 17:09:55 +01:00
Shakker
ec91dce0b8 test: scope internal transcript state env 2026-06-05 17:08:30 +01:00
Shakker
fbbb88925a test: scope openrouter capability env 2026-06-05 17:06:09 +01:00
Shakker
9235c25d33 test: clean model cache state env 2026-06-05 17:04:19 +01:00
Shakker
6ce71737e5 test: manage workspace state fixture 2026-06-05 17:02:10 +01:00
Shakker
935c80d6e1 test: use managed skill workshop state 2026-06-05 17:01:20 +01:00
Vincent Koc
286772e930 test: shorten vitest no-output heartbeat 2026-06-05 09:00:02 -07:00
Shakker
b19904931e test: contain sessions tool state env 2026-06-05 16:58:37 +01:00
Shakker
415272d17e test: isolate pdf media state env 2026-06-05 16:56:05 +01:00
Shakker
002aa1061b test: narrow media tool state env 2026-06-05 16:54:58 +01:00
Shakker
8a83c13389 test: bound sandbox media state env 2026-06-05 16:53:44 +01:00
Shakker
a16b6c02ce test: pair cron task state env 2026-06-05 16:52:47 +01:00
Peter Steinberger
2514980118 feat(matrix): handle voice preflight and threads (#90415)
* feat(matrix): handle voice preflight and threads

Co-authored-by: Frank Dierolf <frank_dierolf@web.de>
Co-authored-by: marc.wilson <marcwilson@gazasrv15i5.globaladvisors.biz>

* test(matrix): satisfy ci guards

* fix(matrix): preserve thread relations on edits

* chore: annotate deprecated compatibility aliases

* fix(matrix): include poll thread roots in reads

* test(matrix): enable audio preflight qa config

* test(matrix): make voice preflight QA mention deterministic

---------

Co-authored-by: Frank Dierolf <frank_dierolf@web.de>
Co-authored-by: marc.wilson <marcwilson@gazasrv15i5.globaladvisors.biz>
2026-06-05 08:49:35 -07:00
Shakker
c85b0ee3db test: scope subagent sqlite state env 2026-06-05 16:48:32 +01:00
Shakker
1e683ff245 test: scope auth path state env 2026-06-05 16:46:55 +01:00
Shakker
fc0b141445 test: contain launch restart home env 2026-06-05 16:45:08 +01:00
Shakker
a0840cad8f test: scope restart sentinel state env 2026-06-05 16:43:32 +01:00
Shakker
03b35b53e3 test: delegate media redirect state env 2026-06-05 16:43:00 +01:00
Peter Steinberger
797bcd5bdb fix: propagate ClickClack toolsAllow through replies
Propagate ClickClack account-level runtime tool allowlists through inbound reply dispatch so restricted ClickClack accounts keep their tool policy when model/agent replies are generated.

This threads `toolsAllow` through shared dispatch, provider wrappers, embedded agent execution, and ACP hook events. ACP-bound sessions now fail closed for restrictive runtime allowlists because ACPX cannot enforce per-turn tool allowlists on reused persistent sessions.

Verification:
- Live ClickClack E2E on Crabbox AWS `run_6a0472ed7e71`, provider `aws`, id `cbx_dace25addcaa`.
- `node scripts/run-vitest.mjs run src/auto-reply/reply/dispatch-acp.test.ts src/plugin-sdk/acp-runtime.test.ts src/auto-reply/reply/dispatch-from-config.reply-dispatch.test.ts src/auto-reply/dispatch.test.ts src/auto-reply/reply/agent-runner-execution.test.ts src/auto-reply/reply/provider-dispatcher.test.ts extensions/clickclack/src/inbound.test.ts --reporter=verbose`
- Crabbox changed gate `run_d32af37fb265`, provider `aws`, id `cbx_8236876017c9`: `corepack pnpm check:changed`
- Autoreview clean: `.agents/skills/autoreview/scripts/autoreview --mode branch --base origin/main`

Supersedes #89500.

Co-authored-by: Michael Appel <mappel@nvidia.com>
2026-06-05 08:40:35 -07:00
Shakker
5a0f9cb03c test: scope logging config path env 2026-06-05 16:39:50 +01:00
Shakker
e4de53a460 test: snapshot flows state env 2026-06-05 16:38:23 +01:00
Shakker
d1fe0184b9 test: preserve secrets state env snapshot 2026-06-05 16:37:09 +01:00
Vincent Koc
da88940c6c fix(android): skip gradle resource tasks on linux arm 2026-06-05 08:14:42 -07:00
Ayaan Zaidi
520992a1de test(gateway): avoid future session fixture timestamps 2026-06-05 18:19:19 +05:30
Ayaan Zaidi
00d21a4720 test(telegram): align transcript append mock 2026-06-05 18:19:19 +05:30
Ayaan Zaidi
3d68f7e5f7 test(gateway): stabilize live session metadata fixture 2026-06-05 18:19:19 +05:30
Ayaan Zaidi
ceee4c6b01 fix(sessions): mark transcript rewrites in registry 2026-06-05 18:19:19 +05:30
Fermin Quant
e22e857ddd fix(sessions): keep transcript append result discriminant 2026-06-05 18:19:19 +05:30
Fermin Quant
57bed6ae0c fix(sessions): cover terminal transcript markers 2026-06-05 18:19:19 +05:30
Fermin Quant
0c9ac48d2c fix(sessions): reconcile stale terminal main transcripts 2026-06-05 18:19:19 +05:30
Ayaan Zaidi
afa04d6454 fix(gateway): share codex model visibility 2026-06-05 17:14:34 +05:30
Ayaan Zaidi
85343ea546 fix(gateway): fail closed for unknown model auth 2026-06-05 17:14:34 +05:30
Ayaan Zaidi
d6dbcb2f4b fix(android): surface expiring providers in palette 2026-06-05 17:14:34 +05:30
Ayaan Zaidi
61d121f1ca fix(android): show unavailable model rows as attention 2026-06-05 17:14:34 +05:30
Ayaan Zaidi
21512a696f fix(gateway): preserve codex alias model availability 2026-06-05 17:14:34 +05:30
Ayaan Zaidi
ea1ef72394 fix(gateway): keep unresolved profile refs unknown 2026-06-05 17:14:34 +05:30
Ayaan Zaidi
7c885528ba fix(gateway): recognize env profile refs in model availability 2026-06-05 17:14:34 +05:30
Ayaan Zaidi
cec5e36a39 fix(gateway): avoid resolving auth during models list 2026-06-05 17:14:34 +05:30
Ayaan Zaidi
e404ce98f5 fix(gateway): require resolved auth for model availability 2026-06-05 17:14:34 +05:30
Ayaan Zaidi
30160933f0 refactor(android): distill provider availability cleanup 2026-06-05 17:14:34 +05:30
Tosko4
8b66003a0b fix(android): clarify provider attention state 2026-06-05 17:14:34 +05:30
Chunyue Wang
12a569109b fix(agents): detect unsigned thinking-only stall when reasoning payload inflates payloadCount (#89874)
Summary:
- Merged fix(agents): detect unsigned thinking-only stall when reasoning payload inflates payloadCount after ClawSweeper review.

Automerge notes:
- No ClawSweeper repair was needed after automerge opt-in.

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

Prepared head SHA: c613c3884f
Review: https://github.com/openclaw/openclaw/pull/89874#issuecomment-4630564594

Co-authored-by: openperf <16864032@qq.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: takhoffman
Co-authored-by: takhoffman <781889+takhoffman@users.noreply.github.com>
2026-06-05 10:29:18 +00:00
Chunyue Wang
1a3ce7c2a8 fix(qqbot): sanitize outbound text to strip reasoning/thinking content (#90132)
Summary:
- Adds QQBot outbound `sanitizeText` wired to `sanitizeAssistantVisibleText` plus a regression test for stripping `<thinking>` and `<think>` blocks.
- PR surface: Source +2, Tests +19. Total +21 across 2 files.
- Reproducibility: yes. source-reproducible: current main QQBot outbound lacks `sanitizeText`, and shared deli ... nnel text sanitization when that hook exists. I did not run a live Tencent QQBot plus MiniMax reproduction.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(qqbot): add curly braces for eslint(curly) compliance

Validation:
- ClawSweeper review passed for head 17cf140183.
- Required merge gates passed before the squash merge.

Prepared head SHA: 17cf140183
Review: https://github.com/openclaw/openclaw/pull/90132#issuecomment-4618527026

Co-authored-by: openperf <16864032@qq.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: takhoffman
Co-authored-by: takhoffman <781889+takhoffman@users.noreply.github.com>
2026-06-05 06:57:16 +00:00
ooiuuii
560b77a4af test: add Codex session route migration coverage (#90319)
* Add Codex session route migration coverage

* Use synthetic Telegram session id in Codex test
2026-06-04 23:28:08 -07:00
ooiuuii
cfd5f1ad13 Add Codex multi-agent migration coverage (#90317) 2026-06-04 23:27:34 -07:00
Kevin Lin
d7759c6a35 feat(googlechat): add native approval cards
## Summary

- Adds native Google Chat approval cards for exec and plugin approval requests that originate from Google Chat spaces or threads.
- Uses opaque server-side action tokens for Google Chat `cardsV2` button callbacks and updates delivered approval messages after resolution or expiry.
- Preserves the shipped Google Chat typing-message default while keeping approval cards on the channel-local native path.
- Suppresses duplicate manual `/approve ...` follow-up delivery inside `extensions/googlechat/` when the native card path owns the approval prompt.
- Documents Google Chat native approval behavior and the `typingIndicator: "message"` default.

## Linked context

Which issue does this close?

Closes #

Which issues, PRs, or discussions are related?

Related Spec 24.8: Google Chat native approval cards.

Was this requested by a maintainer or owner?

Requested by maintainer in the Codex task thread.

## Real behavior proof (required for external PRs)

- Behavior addressed: Google Chat exec and plugin approvals render as native cards and resolve through Google Chat button clicks. The latest change verifies an exec approval card is not accompanied by a duplicate manual `/approve` instruction bubble.
- Real environment tested: OpenClaw dev profile with a real Google Chat DM to the OpenClaw app, local gateway behind a temporary Cloudflare quick tunnel, and Arc/Computer Use against the signed-in Google Chat session.
- Exact steps or command run after this patch: Rebuilt the gateway runtime, started the dev-profile gateway with the Google Chat webhook routed through the tunnel, sent a fresh exec request from Google Chat, verified only the native approval card appeared, clicked `Allow Once` in Google Chat, and checked the command output reply plus marker file.
- Evidence after fix (screenshot, recording, terminal capture, console output, redacted runtime log, linked artifact, or copied live output): Latest proof used nonce `GCHAT_NODOUBLE_LIVE_20260604070730`, approval id `949bc08c-9e57-47c0-b045-137603782292`, and proof directory `.mem/main/proofs/demo-89502-dev-gchat-exec-approval-no-double-send-channel-race/`. `raw/google-chat-gchat-nodouble-request-card-only-clean.png` shows the fresh user message followed by a single native `Exec Approval Required` card with `Allow Once`/`Deny` and no manual `/approve` follow-up bubble. `raw/google-chat-gchat-nodouble-resolved-clean.png` shows the card edited to `Exec Approval: Allowed once` and the final successful command reply. `raw/gchat-nodouble-live-filtered-log.txt` contains `googlechat approval resolved id=949bc08c-9e57-47c0-b045-137603782292 decision=allow-once`. `raw/marker-file-check.txt` records `/tmp/openclaw-gchat-no-double-GCHAT_NODOUBLE_LIVE_20260604070730` as created.
- Observed result after fix: The approval prompt posted as a native Google Chat card only. No duplicate manual approval-instruction bubble was sent. Clicking `Allow Once` resolved the approval through the gateway and OpenClaw replied with the successful exec output in the same Google Chat DM.
- What was not tested: A persistent production Google Chat app URL; live proof used a temporary Cloudflare tunnel for the local dev callback.
- Proof limitations or environment constraints: Video was not captured for the final resumed manual UI run; still screenshots, gateway/proxy logs, a marker-file artifact, and Showboat verification were captured.
- Before evidence (optional but encouraged): Before the final channel-local suppression path, Google Chat could show both the native approval card and a separate manual `/approve` instruction bubble.

## Tests and validation

Which commands did you run?

- `node scripts/build-all.mjs gatewayWatch`
- `node scripts/run-vitest.mjs extensions/googlechat/src/monitor-webhook.test.ts extensions/googlechat/src/monitor.test.ts extensions/googlechat/src/monitor.reply-delivery.test.ts extensions/googlechat/src/monitor-durable.test.ts extensions/googlechat/src/approval-card-actions.test.ts extensions/googlechat/src/approval-handler.runtime.test.ts extensions/googlechat/src/approval-native.test.ts extensions/googlechat/src/approval-card-click.test.ts extensions/googlechat/src/channel-config.test.ts extensions/googlechat/src/targets.test.ts`
- `git diff --check`
- `pnpm docs:list`
- `uvx showboat --workdir .mem/main/proofs/demo-89502-dev-gchat-exec-approval-no-double-send-channel-race verify .mem/main/proofs/demo-89502-dev-gchat-exec-approval-no-double-send-channel-race/raw/showboat-summary.md`
- Live dev-profile Google Chat proof described above.

What regression coverage was added or updated?

- Added Google Chat native approval capability, runtime delivery, card token, and card-click resolver tests.
- Added in-flight native card send suppression coverage so manual follow-up text is suppressed while native card delivery is pending.
- Added cleanup coverage so manual follow-ups are restored if native card send fails.
- Updated webhook ACK coverage for card-click events and default typing-indicator behavior coverage.

What failed before this fix, if known?

Google Chat could deliver the native approval card and still allow a model/message-tool manual `/approve` follow-up to appear as a second visible bubble.

If no test was added, why not?

Tests were added for the changed runtime and webhook behavior.

## Risk checklist

Did user-visible behavior change? (`Yes/No`)

Yes.

Did config, environment, or migration behavior change? (`Yes/No`)

No migration. The shipped Google Chat `typingIndicator: "message"` default is preserved.

Did security, auth, secrets, network, or tool execution behavior change? (`Yes/No`)

Yes.

What is the highest-risk area?

Approval authorization and callback token handling for native Google Chat card actions.

How is that risk mitigated?

Callbacks carry opaque action tokens only, token bindings check account, space, message, expiry, allowed decision, and in-flight state, and actor authorization reuses the existing Google Chat approver allowlist adapter based on stable `users/<id>` principals.

## Current review state

What is the next action?

Merge after current-head CI for `5923f2af46`.

What is still waiting on author, maintainer, CI, or external proof?

Current-head CI is green for `5923f2af46`; live dev-profile proof is complete.

Which bot or reviewer comments were addressed?

Addressed duplicate approval delivery by keeping the final suppression path inside `extensions/googlechat/`, preserving default typing-message behavior, and proving the current Google Chat surface sends only the native approval card.
2026-06-04 23:05:06 -07:00
Vincent Koc
e0018382eb fix(agents): reject empty completion handoffs 2026-06-04 21:33:42 -07:00
clawsweeper[bot]
69d1d78649 fix(mattermost): anchor slash state on globalThis (#68113) (#90534)
Summary:
- The branch stores Mattermost slash-command account state in a process-wide Symbol.for/globalThis Map and adds module-reload regression coverage.
- PR surface: Source +21, Tests +43. Total +64 across 2 files.
- Reproducibility: yes. at source level: current main's route handler returns 503 when its module-local accoun ... pulate state through a separate loader path. I did not run a live Mattermost POST in this read-only review.

Automerge notes:
- No ClawSweeper repair was needed after automerge opt-in.

Validation:
- ClawSweeper review passed for head 3cf28a1f96.
- Required merge gates passed before the squash merge.

Prepared head SHA: 3cf28a1f96
Review: https://github.com/openclaw/openclaw/pull/90534#issuecomment-4627897262

Co-authored-by: ben.li <ly85206559@163.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: takhoffman
Co-authored-by: takhoffman <781889+takhoffman@users.noreply.github.com>
2026-06-05 04:10:43 +00:00
Peter Steinberger
cb5bb9b936 docs: document e2e helpers 2026-06-05 00:04:03 -04:00
Peter Steinberger
bafe17e60b docs: document vitest routing maps 2026-06-04 23:59:11 -04:00
Peter Steinberger
613a2835cb docs: document scoped script helpers 2026-06-04 23:57:22 -04:00
Peter Steinberger
a59eba3ee1 docs: document test project scripts 2026-06-04 23:55:54 -04:00
Peter Steinberger
9b1a01e4f9 docs: document test wrapper scripts 2026-06-04 23:54:19 -04:00
Peter Steinberger
29746cf7a9 docs: document smoke test scripts 2026-06-04 23:53:10 -04:00
joshavant
17ab517047 fix(ios): use dynamic settings bottom margin 2026-06-04 22:52:52 -05:00
joshavant
697eeb8bab fix(ios): keep diagnostics action reachable 2026-06-04 22:52:52 -05:00
joshavant
853f1c0d9e fix(ios): keep gateway row grouped and tappable 2026-06-04 22:52:52 -05:00
joshavant
1447a4507a fix(ios): keep talk unavailable without config 2026-06-04 22:52:52 -05:00
joshavant
748881e0a8 fix(ios): label chat attachment button 2026-06-04 22:52:52 -05:00
Peter Steinberger
ff83d4d164 docs: document runner scripts 2026-06-04 23:52:06 -04:00
Vincent Koc
13078d24ab chore(release): refresh plugin sdk api baseline 2026-06-04 20:50:17 -07:00
Vincent Koc
48c19590eb fix(test): install playwright deps after host validation failure 2026-06-04 20:50:17 -07:00
Peter Steinberger
72547a1ac6 docs: document release audit scripts 2026-06-04 23:49:34 -04:00
Peter Steinberger
26bc069308 docs: document profiling scripts 2026-06-04 23:48:20 -04:00
Peter Steinberger
57f8d71c50 docs: document release runner scripts 2026-06-04 23:46:55 -04:00
Peter Steinberger
980c91d293 docs: document ci dependency docs scripts 2026-06-04 23:42:32 -04:00
Peter Steinberger
6b0ffa2106 docs: document package boundary scripts 2026-06-04 23:37:42 -04:00
Peter Steinberger
056421f4f8 docs: document root runtime guard scripts 2026-06-04 23:34:16 -04:00
Dallin Romney
fb750e6eed Fix main CI guard drift (#90532) 2026-06-04 20:31:41 -07:00
Peter Steinberger
978fdd7d2a docs: document root guard scripts 2026-06-04 23:30:59 -04:00
Peter Steinberger
74f3baebb7 docs: document root build check scripts 2026-06-04 23:28:04 -04:00
Peter Steinberger
deff9ea180 docs: document cjs bridge headers 2026-06-04 23:26:24 -04:00
Peter Steinberger
9fd5f9ee7c docs: document source bridge files 2026-06-04 23:25:42 -04:00
Vincent Koc
4dd7bc6d88 fix(test): stage live docker home credentials 2026-06-04 20:22:35 -07:00
Onur Solmaz
0dbf17471b feat(memory): support qmd query rerank toggle
Add memory.qmd.rerank as an opt-out for QMD query reranking when searchMode is query.

When set to false, direct QMD query calls pass --no-rerank and the mcporter unified query tool receives rerank:false. Search and vsearch modes keep their existing behavior.

Refs #61834.
2026-06-05 11:18:57 +08:00
Peter Steinberger
f3abe61b78 docs: document script lib test helpers 2026-06-04 23:08:26 -04:00
Peter Steinberger
92cdcae500 docs: document script lib report helpers 2026-06-04 23:07:12 -04:00
Peter Steinberger
3cf1bd22f9 docs: document script lib runtime package helpers 2026-06-04 23:05:22 -04:00
Peter Steinberger
44cd0ec13f docs: document script lib plugin helpers 2026-06-04 23:03:25 -04:00
Peter Steinberger
d77bac8911 docs: document script lib package helpers 2026-06-04 23:01:02 -04:00
Peter Steinberger
1da49dcfd0 docs: document script lib process helpers 2026-06-04 22:59:33 -04:00
Peter Steinberger
ee74fff7ad docs: document script lib inventory helpers 2026-06-04 22:57:30 -04:00
Peter Steinberger
1de46bb425 docs: document script lib extension helpers 2026-06-04 22:56:09 -04:00
Peter Steinberger
e662435067 docs: document script lib guard helpers 2026-06-04 22:54:18 -04:00
Peter Steinberger
62a6fd8139 docs: document script lib scan helpers 2026-06-04 22:52:34 -04:00
Peter Steinberger
88158525a7 docs: document script lib helper contracts 2026-06-04 22:51:08 -04:00
Peter Steinberger
c8bb7330b5 docs: add headers to build check scripts 2026-06-04 22:49:21 -04:00
Peter Steinberger
8732ef2f28 docs: document channel sdk core contracts 2026-06-04 22:46:51 -04:00
Peter Steinberger
8f6e71087b docs: document agent harness sdk contracts 2026-06-04 22:45:30 -04:00
Peter Steinberger
9448f91e6f docs: document memory runtime contracts 2026-06-04 22:44:01 -04:00
Peter Steinberger
5613a0fb6e docs: document discord sdk facade contracts 2026-06-04 22:42:23 -04:00
Peter Steinberger
82710b4f1f docs: document lmstudio runtime contracts 2026-06-04 22:41:26 -04:00
Peter Steinberger
d23558e691 docs: document qa runtime facade contracts 2026-06-04 22:40:12 -04:00
Peter Steinberger
2f00fbf28e docs: document tts runtime contracts 2026-06-04 22:39:02 -04:00
Peter Steinberger
86872e0880 docs: document channel approval ingress contracts 2026-06-04 22:38:03 -04:00
Peter Steinberger
506c2ee181 docs: document qa video gateway sdk contracts 2026-06-04 22:34:53 -04:00
Peter Steinberger
1e6fb5089b docs: document approval reaction reply contracts 2026-06-04 22:32:37 -04:00
Peter Steinberger
14690904f0 docs: document browser session oauth sdk contracts 2026-06-04 22:31:20 -04:00
Peter Steinberger
99bb94589b docs: document sdk facade loader contracts 2026-06-04 22:29:06 -04:00
Peter Steinberger
de4571da4b docs: document sdk dedupe and group contracts 2026-06-04 22:27:50 -04:00
Peter Steinberger
a4087c54b5 docs: document provider facade constants 2026-06-04 22:26:17 -04:00
Peter Steinberger
4756d6a42a docs: document sdk migration and approval contracts 2026-06-04 22:24:26 -04:00
Peter Steinberger
9e22b8560c docs: document sdk facade contracts 2026-06-04 22:22:21 -04:00
Peter Steinberger
c1b49bb1d0 docs: document sdk payload and fetch contracts 2026-06-04 22:20:19 -04:00
Peter Steinberger
d6c0f9ccb8 docs: document sdk utility contracts 2026-06-04 22:18:33 -04:00
Peter Steinberger
5d350e785a docs: document sdk single-export contracts 2026-06-04 22:16:29 -04:00
Peter Steinberger
de68623ffe docs: document sdk runtime helper contracts 2026-06-04 22:14:54 -04:00
Peter Steinberger
848f39e70d docs: document public sdk contract helpers 2026-06-04 22:13:13 -04:00
Peter Steinberger
b311fd607f docs: document generated locale bundles 2026-06-04 22:11:11 -04:00
Patrick Erichsen
8f85f94946 feat: install GitHub-backed ClawHub skills (#90478)
* feat: install GitHub-backed ClawHub skills

* fix: satisfy ClawHub install type checks

* fix: harden github-backed skill installs

* fix: keep heartbeat template non-actionable

* feat: support forcing pending ClawHub installs
2026-06-04 19:10:02 -07:00
Peter Steinberger
5380d11977 docs: document scoped extension sources 2026-06-04 22:07:59 -04:00
Peter Steinberger
23716de446 docs: document discord extension sources 2026-06-04 22:06:01 -04:00
Peter Steinberger
efd1a9ace6 docs: document messaging extension sources 2026-06-04 22:03:15 -04:00
Sally O'Malley
7ac1eeb122 fix service env placeholder collection (#90488)
Signed-off-by: sallyom <somalley@redhat.com>
2026-06-04 22:02:24 -04:00
Peter Steinberger
58912f8fd8 docs: document channel extension sources 2026-06-04 21:59:00 -04:00
Peter Steinberger
6868cde4d4 docs: document large extension sources 2026-06-04 21:40:44 -04:00
Peter Steinberger
3c7c25afd2 fix: accept codex app-server auth aliases 2026-06-04 21:40:36 -04:00
Peter Steinberger
96e5812426 docs: document medium extension sources 2026-06-04 21:33:54 -04:00
Shakker
126ebfc997 test: share probe target env cleanup 2026-06-05 02:20:14 +01:00
Shakker
4b151593e2 test: scope model scan key env 2026-06-05 02:18:51 +01:00
Shakker
56f652b499 test: pin oauth tls brew env 2026-06-05 02:17:15 +01:00
Shakker
5a704d26a1 test: localize control ui home env 2026-06-05 02:15:55 +01:00
Shakker
e7bcbd3e7e test: isolate windows acl system root 2026-06-05 02:14:03 +01:00
Shakker
afcf1ddb9d test: confine web fetch key env 2026-06-05 02:12:53 +01:00
Shakker
85e16da2b4 test: scope preauth budget env 2026-06-05 02:11:44 +01:00
Shakker
38e142657b test: bound proxy resolver env 2026-06-05 02:10:43 +01:00
Shakker
4dd00347fc test: contain web media home env 2026-06-05 02:09:30 +01:00
Shakker
0973eb61c3 test: snapshot pairing gateway env 2026-06-05 02:07:35 +01:00
Shakker
e282cb2af5 test: contain media roots state env 2026-06-05 02:05:18 +01:00
Shakker
6f419b3853 test: narrow media proxy env scope 2026-06-05 02:04:56 +01:00
Peter Steinberger
4fa5092cdc docs: document small extension sources 2026-06-04 21:02:07 -04:00
Shakker
53a3d58d62 test: isolate npm update smoke env 2026-06-05 02:00:29 +01:00
Shakker
cef423d066 test: isolate group report planner env 2026-06-05 01:58:47 +01:00
Shakker
5cf63f295b test: snapshot exec audit home env 2026-06-05 01:57:47 +01:00
Shakker
86d958647f test: scope embedded shutdown grace env 2026-06-05 01:57:25 +01:00
Peter Steinberger
12a56d4d46 docs: document control ui sources 2026-06-04 20:57:09 -04:00
Shakker
39cc11ad28 test: scope tui shutdown grace env 2026-06-05 01:56:16 +01:00
Peter Steinberger
4df95d3c3f docs: document package sources 2026-06-04 20:54:41 -04:00
Peter Steinberger
b8d08f0cfd docs: document repository scripts 2026-06-04 20:52:50 -04:00
Shakker
95d51c5fe8 test: snapshot redact config env 2026-06-05 01:51:48 +01:00
Shakker
5c6a501269 test: snapshot log tail config env 2026-06-05 01:51:48 +01:00
Shakker
dc4c9030fc test: snapshot diagnostic state env 2026-06-05 01:51:48 +01:00
Shakker
8ede9e0e07 test: scope doctor gateway token env 2026-06-05 01:51:48 +01:00
Shakker
9739249043 test: scope channel prompts locale 2026-06-05 01:51:48 +01:00
Shakker
dbb80f3bb7 test: scope search setup locale 2026-06-05 01:51:27 +01:00
Shakker
61d9ac8c5d test: scope channel status locale 2026-06-05 01:51:27 +01:00
Shakker
abc00f4c98 test: snapshot logging config env 2026-06-05 01:51:27 +01:00
Shakker
28737a0b09 test: snapshot console settings env 2026-06-05 01:51:27 +01:00
Shakker
28b63e69e9 test: snapshot logger settings env 2026-06-05 01:51:27 +01:00
Shakker
5392cb7139 test: snapshot logger level env 2026-06-05 01:51:27 +01:00
Shakker
55c414ca81 test: reuse parallels env helper 2026-06-05 01:51:27 +01:00
Shakker
74680e3484 test: reuse release env helper 2026-06-05 01:51:27 +01:00
Shakker
d6e1ca997b test: snapshot openai provider env 2026-06-05 01:51:27 +01:00
Shakker
c4ed850f9b test: snapshot tts prefs env 2026-06-05 01:51:27 +01:00
Shakker
4957e3b02f test: share brew env helpers 2026-06-05 01:51:27 +01:00
Shakker
323c8aa87f test: snapshot npm global config env 2026-06-05 01:51:27 +01:00
Shakker
442a2107b5 test: scope bun install detection env 2026-06-05 01:51:27 +01:00
Shakker
ed52d27d78 test: share env api key snapshot 2026-06-05 01:51:27 +01:00
Shakker
cb17c84410 test: let flow registry helper own state env 2026-06-05 01:51:27 +01:00
Shakker
f57adba400 test: snapshot task executor env 2026-06-05 01:51:27 +01:00
Shakker
9f6ed16a6d test: snapshot task flow maintenance env 2026-06-05 01:51:27 +01:00
Shakker
99a838fac4 test: snapshot task registry store env 2026-06-05 01:51:27 +01:00
Shakker
064182aff8 test: snapshot task flow audit env 2026-06-05 01:51:26 +01:00
Shakker
0f9bb59b73 test: snapshot task owner state env 2026-06-05 01:51:26 +01:00
Shakker
79b6dd049e test: scope inherited agent dir fixture 2026-06-05 01:51:26 +01:00
Peter Steinberger
58c663920d docs: document script tests 2026-06-04 20:49:50 -04:00
Marcus Castro
dd2083c7ec fix(whastapp): bound connection startup waits (#90486)
* fix: add timeout to waitForWaConnection to prevent indefinite hangs

If Baileys fails to emit a 'connection.update' event with either 'open'
or 'close' status (e.g. due to network issues or internal errors), the
waitForWaConnection promise hangs forever, blocking the entire monitor
loop.

Add a configurable timeout (default 60s) that rejects the promise and
cleans up the event listener if no connection state is received in time.
The timeout is backward-compatible as an optional parameter with a
sensible default.

* test: add coverage for waitForWaConnection timeout path

- Test that promise rejects with descriptive error after timeout
- Test that event listener is cleaned up after timeout
- Test that timer is cleared when connection opens before timeout

* fix: default timeoutMs to 0 to preserve QR login behavior

The 60s default broke the QR login flow in login-qr.ts, which calls
waitForWaConnection without a timeout and expects to wait up to 3 minutes
while the user scans. Change the default to 0 (wait forever, matching
original behavior) and pass the 60s timeout explicitly at the monitor
callsite where it's actually needed.

* fix: bound whatsapp connection startup waits

* fix: align web channel wait contract

* fix: retry whatsapp setup timeouts

* fix: satisfy whatsapp status lint

* fix: preserve whatsapp wait compatibility

---------

Co-authored-by: MMMMSSSS8899 <praelovk@gmail.com>
2026-06-04 21:45:43 -03:00
Peter Steinberger
29f5e9d35c docs: document test helpers 2026-06-04 20:42:26 -04:00
Peter Steinberger
25211167e8 docs: document vitest config files 2026-06-04 20:40:11 -04:00
Peter Steinberger
ecb6779a16 docs: document root test files 2026-06-04 20:37:28 -04:00
Peter Steinberger
edb920b857 docs: document remaining src helpers 2026-06-04 20:34:26 -04:00
Peter Steinberger
b2e320dfb1 docs: document support test files 2026-06-04 20:31:55 -04:00
Peter Steinberger
1bdf210b43 docs: document rescue and trajectory tests 2026-06-04 20:29:59 -04:00
Peter Steinberger
d8326f13c3 docs: document proxy and mcp helpers 2026-06-04 20:27:46 -04:00
Peter Steinberger
9b30ff181c docs: document routing helpers 2026-06-04 20:25:55 -04:00
Peter Steinberger
4f79f2419c docs: document video generation helpers 2026-06-04 20:24:49 -04:00
Peter Steinberger
65546f0158 docs: document tui components 2026-06-04 20:23:23 -04:00
Peter Steinberger
6d58ff3562 docs: document session helpers 2026-06-04 20:22:09 -04:00
Peter Steinberger
47bae66415 docs: document session config tests 2026-06-04 20:20:20 -04:00
Peter Steinberger
f5b6a977d7 docs: document tts helpers 2026-06-04 20:19:15 -04:00
Peter Steinberger
85e6940202 docs: document talk helpers 2026-06-04 20:17:42 -04:00
Peter Steinberger
5ba4eeceac docs: document daemon tests 2026-06-04 20:16:21 -04:00
Peter Steinberger
a628a66e4d docs: document process helpers 2026-06-04 20:14:34 -04:00
Peter Steinberger
ef08c83e17 docs: document utility helpers 2026-06-04 20:12:49 -04:00
Peter Steinberger
b6ce59d367 docs: document wizard helpers 2026-06-04 20:11:22 -04:00
Peter Steinberger
c8665c66ba docs: document flow helpers 2026-06-04 20:10:01 -04:00
Peter Steinberger
4c3b4f8ad8 docs: document hook helpers 2026-06-04 20:08:40 -04:00
Peter Steinberger
e6f85453dc docs: document llm helpers 2026-06-04 20:07:13 -04:00
Peter Steinberger
f1bdc91b64 docs: document media helpers 2026-06-04 20:05:16 -04:00
Peter Steinberger
add135d238 docs: document logging helpers 2026-06-04 20:04:06 -04:00
Vincent Koc
563dac5989 test(core): remove stale unused test bindings 2026-06-04 17:03:40 -07:00
Vincent Koc
5bc300a1df test(agents): align pdf default model expectation 2026-06-04 17:03:40 -07:00
Vincent Koc
1d19d7ec46 fix(auto-reply): skip commented heartbeat scaffolding 2026-06-04 17:03:40 -07:00
Peter Steinberger
87d053c0cb docs: document shared helpers 2026-06-04 20:02:33 -04:00
Peter Steinberger
5b53cddc75 docs: document cron test files 2026-06-04 20:01:05 -04:00
Peter Steinberger
6c48a12562 docs: document skill runtime files 2026-06-04 19:58:44 -04:00
Peter Steinberger
43cee29f70 docs: document skill loading files 2026-06-04 19:57:11 -04:00
Peter Steinberger
725ddd11cc docs: document remaining plugin runtime files 2026-06-04 19:54:07 -04:00
Peter Steinberger
d2d14d5793 docs: document plugin contract tests 2026-06-04 19:52:26 -04:00
Peter Steinberger
f25c246f6b docs: document plugin runtime helpers 2026-06-04 19:48:26 -04:00
Peter Steinberger
6486fc1c0d docs: document model command tests 2026-06-04 19:46:17 -04:00
Peter Steinberger
81eee47045 docs: document doctor command tests 2026-06-04 19:44:23 -04:00
Peter Steinberger
4499b24781 docs: document cli program tests 2026-06-04 19:41:55 -04:00
Peter Steinberger
b59b34f9d5 docs: document cli service tests 2026-06-04 19:39:51 -04:00
Shakker
912e70acbd test: scope system run helper env 2026-06-05 00:38:28 +01:00
Shakker
16147e16e3 test: isolate approval path token cases 2026-06-05 00:38:28 +01:00
Shakker
638be00f4b test: scope fake runtime path setup 2026-06-05 00:38:28 +01:00
Shakker
695e09d360 test: scope proof temp dir env 2026-06-05 00:38:28 +01:00
Shakker
69ddcc00e6 test: scope invoke prepare path env 2026-06-05 00:38:28 +01:00
Shakker
a18c60e141 test: scope sandbox audit home env 2026-06-05 00:38:28 +01:00
Shakker
ec048ae693 test: restore cron state dir through helper 2026-06-05 00:38:28 +01:00
Shakker
7675b10223 test: capture usage format env setup 2026-06-05 00:38:28 +01:00
Shakker
25a1b0c240 test: cover allowlist tilde expansion 2026-06-05 00:38:28 +01:00
Shakker
c006ed5e16 test: restore reply harness temp home 2026-06-05 00:38:27 +01:00
Shakker
b4e048e60a test: reset gateway token env per case 2026-06-05 00:38:27 +01:00
Shakker
f365568f1b test: shorten tool metadata home paths 2026-06-05 00:38:27 +01:00
Shakker
e2c23d8a5e test: verify model status agent-dir env 2026-06-05 00:38:27 +01:00
Peter Steinberger
408ba4c8a0 docs: document remaining cli tests 2026-06-04 19:37:38 -04:00
Peter Steinberger
4995907541 docs: document cli support tests 2026-06-04 19:35:08 -04:00
Peter Steinberger
8cb093e7a9 docs: document cli test batch 2026-06-04 19:32:43 -04:00
Peter Steinberger
3e29885c83 docs: document channel subdir tests 2026-06-04 19:30:09 -04:00
Peter Steinberger
867d7898df docs: document channel plugin contracts 2026-06-04 19:28:31 -04:00
Peter Steinberger
fa46138047 docs: document channel plugin tests 2026-06-04 19:24:55 -04:00
Peter Steinberger
c135624c69 docs: document root channel tests 2026-06-04 19:23:04 -04:00
Peter Steinberger
048f307695 docs: document remaining plugin sdk files 2026-06-04 19:21:04 -04:00
Peter Steinberger
feffb6d02f docs: document plugin sdk runtime helpers 2026-06-04 19:16:24 -04:00
Peter Steinberger
a16c6ca94b docs: document plugin sdk public helpers 2026-06-04 19:14:41 -04:00
Vincent Koc
7fb748462e fix(ci): classify live installer docker lanes 2026-06-04 16:13:27 -07:00
Peter Steinberger
50dcaad71a docs: document remaining command tests 2026-06-04 19:11:43 -04:00
Peter Steinberger
7a7ca15776 docs: document command setup batch 2026-06-04 19:07:41 -04:00
Peter Steinberger
bf19d198d9 docs: document command cleanup batch 2026-06-04 19:05:16 -04:00
Peter Steinberger
eaad487c42 docs: document command report batch 2026-06-04 19:03:19 -04:00
Peter Steinberger
12ade5c5e8 docs: document command scan batch 2026-06-04 19:01:12 -04:00
Peter Steinberger
076bf2a361 docs: document command status batch 2026-06-04 18:59:04 -04:00
Peter Steinberger
0156de5c34 docs: document command onboarding batch 2026-06-04 18:57:02 -04:00
Shakker
646eb00112 test: pin acp prompt home redaction 2026-06-04 23:55:53 +01:00
Shakker
06f95f9a65 test: anchor auth sqlite agent dirs 2026-06-04 23:55:53 +01:00
Shakker
9a78886c78 test: localize logger env overrides 2026-06-04 23:55:53 +01:00
Shakker
66212260ef test: seal crestodian rescue stores 2026-06-04 23:55:53 +01:00
Shakker
dda0a98b76 test: bracket agent directory fixtures 2026-06-04 23:55:53 +01:00
Shakker
c71d3e45a1 test: isolate tts status fixture homes 2026-06-04 23:55:53 +01:00
Shakker
986025afe4 test: guard fs-safe tilde fixtures 2026-06-04 23:55:53 +01:00
Peter Steinberger
0d393ba6b4 docs: document command diagnostics batch 2026-06-04 18:54:31 -04:00
Peter Steinberger
0de924b35c docs: document command support batch 2026-06-04 18:52:07 -04:00
Peter Steinberger
4a47a9db98 docs: document command test batch 2026-06-04 18:50:37 -04:00
Peter Steinberger
5fa55d93f7 docs: document command helper tests 2026-06-04 18:48:22 -04:00
Peter Steinberger
64008398d1 docs: document noninteractive onboarding tests 2026-06-04 18:47:21 -04:00
Peter Steinberger
5c362884f3 docs: document channel setup tests 2026-06-04 18:46:23 -04:00
Peter Steinberger
71b09b99f8 docs: document migrate command tests 2026-06-04 18:45:28 -04:00
Peter Steinberger
3f31b62cd4 docs: document channels capability tests 2026-06-04 18:44:22 -04:00
Peter Steinberger
4927388580 docs: document gateway status tests 2026-06-04 18:43:35 -04:00
Peter Steinberger
19da9d8832 docs: document status-all command tests 2026-06-04 18:42:37 -04:00
Peter Steinberger
bea27678b4 docs: document link understanding 2026-06-04 18:41:10 -04:00
Peter Steinberger
ba28f7b018 docs: document model catalog planners 2026-06-04 18:40:10 -04:00
8778 changed files with 24177 additions and 5592 deletions

View File

@@ -2085,6 +2085,7 @@ class NodeRuntime(
id = id,
name = obj["name"].asStringOrNull()?.trim()?.takeIf { it.isNotEmpty() } ?: id,
provider = provider,
available = obj.optionalBoolean("available"),
supportsVision = "image" in inputTypes,
supportsAudio = "audio" in inputTypes,
supportsDocuments = "document" in inputTypes,
@@ -2701,6 +2702,7 @@ data class GatewayModelSummary(
val id: String,
val name: String,
val provider: String,
val available: Boolean?,
val supportsVision: Boolean,
val supportsAudio: Boolean,
val supportsDocuments: Boolean,
@@ -2883,6 +2885,15 @@ private fun JsonObject?.double(key: String): Double? = (this?.get(key) as? JsonP
private fun JsonObject?.boolean(key: String): Boolean = (this?.get(key) as? JsonPrimitive)?.content?.trim() == "true"
private fun JsonObject?.optionalBoolean(key: String): Boolean? =
(this?.get(key) as? JsonPrimitive)?.content?.trim()?.lowercase()?.let { value ->
when (value) {
"true" -> true
"false" -> false
else -> null
}
}
internal fun cronJobLastRunStatus(state: JsonObject?): String? =
state
.cronStatus("lastStatus")

View File

@@ -297,14 +297,15 @@ private fun CommandSectionLabel(title: String) {
}
}
/** Builds provider quick-action metadata from current gateway/catalog state. */
private fun providerCommandSubtitle(
internal fun providerCommandSubtitle(
isConnected: Boolean,
providers: List<GatewayModelProviderSummary>,
models: List<GatewayModelSummary>,
): String {
if (!isConnected) return "Connect Gateway to load models"
val readyProviderCount = providers.count { modelProviderReady(it.status) }
val expiringProviderCount = expiringModelProviderCount(providers)
if (expiringProviderCount > 0) return "$expiringProviderCount providers expiring"
val readyProviderCount = readyModelProviderCount(providers, models)
if (readyProviderCount > 0) return "$readyProviderCount providers ready"
if (models.isNotEmpty()) return "${models.size} models available"
return "Configure model access"

View File

@@ -192,6 +192,9 @@ private data class ProviderSetupRow(
val name: String,
val subtitle: String,
val ready: Boolean,
val available: Boolean,
val statusLabel: String,
val warning: Boolean,
)
private data class ProviderRow(
@@ -199,37 +202,60 @@ private data class ProviderRow(
val name: String,
val status: String,
val ready: Boolean,
val available: Boolean,
val setupRequired: Boolean,
val warning: Boolean,
val modelCount: Int,
)
/** Combines auth-provider readiness rows with catalog-only providers. */
/** Combines auth-provider readiness rows with catalog-only browse providers. */
private fun providerRows(
providers: List<GatewayModelProviderSummary>,
models: List<GatewayModelSummary>,
): List<ProviderRow> {
val modelCounts = models.groupingBy { it.provider }.eachCount()
val availableProviderIds =
models
.filter(::modelAvailabilityUsable)
.map { it.provider.normalizedProviderId() }
.toSet()
val authRows =
providers.map { provider ->
val ready = modelProviderReady(provider.status)
val providerId = provider.id.normalizedProviderId()
val authReady = modelProviderReady(provider.status)
val expiring = modelProviderExpiring(provider.status)
val available = providerId in availableProviderIds
ProviderRow(
id = provider.id,
name = provider.displayName,
status = if (ready) "Ready" else "Needs setup",
ready = ready,
status =
when {
authReady -> "Ready"
expiring -> "Expiring"
available -> "Available"
else -> "Needs setup"
},
ready = authReady,
available = available || authReady || expiring,
setupRequired = !authReady && !available && !expiring,
warning = expiring,
modelCount = modelCounts[provider.id] ?: 0,
)
}
// Static/catalog-only providers may expose models without a matching auth
// provider row; keep them visible as ready providers.
// Catalog-only providers can be browsed but are not a readiness signal.
val missingAuthRows =
modelCounts.keys
.filter { provider -> authRows.none { it.id == provider } }
.map { provider ->
val available = provider.normalizedProviderId() in availableProviderIds
ProviderRow(
id = provider,
name = providerDisplayName(provider),
status = "Ready",
ready = true,
status = if (available) "Available" else "Catalog",
ready = available,
available = available,
setupRequired = false,
warning = false,
modelCount = modelCounts[provider] ?: 0,
)
}
@@ -245,6 +271,9 @@ private fun providerSetupRows(providerRows: List<ProviderRow>): List<ProviderSet
name = providerDisplayName(id),
subtitle = providerSetupSubtitle(id, row),
ready = row?.ready == true,
available = row?.available == true,
statusLabel = providerSetupStatusLabel(row),
warning = row?.warning == true || row?.setupRequired == true || row == null,
)
}
}
@@ -254,12 +283,24 @@ private fun providerSetupSubtitle(
row: ProviderRow?,
): String =
when {
row?.warning == true -> "Credential expires soon"
row?.ready == true -> if (row.modelCount > 0) "${row.modelCount} models available" else "Ready"
row != null -> "Finish setup to use ${row.name}"
row?.available == true -> if (row.modelCount > 0) "${row.modelCount} models available" else "Available"
row?.setupRequired == true -> "Finish setup to use ${row.name}"
row != null && row.modelCount > 0 -> "${row.modelCount} catalog models"
id == "ollama" -> "Use models running on your network"
else -> "Add provider credentials on your Gateway"
}
private fun providerSetupStatusLabel(row: ProviderRow?): String =
when {
row?.ready == true -> "Ready"
row?.warning == true -> "Expiring"
row?.available == true -> "Available"
row?.setupRequired == false -> "Catalog"
else -> "Setup"
}
/** Normalizes gateway provider status strings into a ready/not-ready boolean. */
internal fun modelProviderReady(status: String): Boolean {
val normalized = status.trim().lowercase()
@@ -270,6 +311,30 @@ internal fun modelProviderReady(status: String): Boolean {
normalized == "static"
}
private fun modelProviderExpiring(status: String): Boolean = status.trim().lowercase() == "expiring"
internal fun readyModelProviderCount(
providers: List<GatewayModelProviderSummary>,
models: List<GatewayModelSummary>,
): Int {
val authReadyProviders = providers.filter { modelProviderReady(it.status) }.map { it.id.normalizedProviderId() }
val availableModelProviders = models.filter(::modelAvailabilityUsable).map { it.provider.normalizedProviderId() }
return (authReadyProviders + availableModelProviders).distinct().size
}
// Older gateways did not emit `available`; keep those rows on the legacy
// readiness path while still honoring explicit false from upgraded gateways.
internal fun modelAvailabilityUsable(model: GatewayModelSummary): Boolean = model.available != false
internal fun expiringModelProviderCount(providers: List<GatewayModelProviderSummary>): Int =
providers
.filter { modelProviderExpiring(it.status) }
.map { it.id.normalizedProviderId() }
.distinct()
.size
private fun String.normalizedProviderId(): String = trim().lowercase()
/** Groups models by provider using the same display priority as provider rows. */
private fun sortedModelGroups(models: List<GatewayModelSummary>): List<Pair<String, List<GatewayModelSummary>>> =
models
@@ -299,7 +364,18 @@ private fun ProviderList(
ClawPanel(contentPadding = PaddingValues(horizontal = 0.dp, vertical = 0.dp)) {
Column {
if (rows.isEmpty()) {
ProviderListRow(ProviderRow(id = "loading", name = "Provider catalog", status = if (refreshing) "Loading" else "No providers", ready = false, modelCount = 0))
ProviderListRow(
ProviderRow(
id = "loading",
name = "Provider catalog",
status = if (refreshing) "Loading" else "No providers",
ready = false,
available = false,
setupRequired = false,
warning = false,
modelCount = 0,
),
)
} else {
val visibleRows = rows.take(5)
visibleRows.forEachIndexed { index, row ->
@@ -322,12 +398,12 @@ private fun ProviderOverviewPanel(
onRefresh: () -> Unit,
onSetup: () -> Unit,
) {
val readyCount = providerRows.count { it.ready }
val needsSetupCount = providerRows.count { !it.ready }
val readyCount = providerRows.count { it.available }
val needsSetupCount = providerRows.count { it.setupRequired }
ClawPanel(contentPadding = PaddingValues(horizontal = 12.dp, vertical = 12.dp)) {
Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) {
ProviderMetricTile(label = "Ready", value = readyCount.toString(), modifier = Modifier.weight(1f))
ProviderMetricTile(label = "Available", value = readyCount.toString(), modifier = Modifier.weight(1f))
ProviderMetricTile(label = "Models", value = modelCount.toString(), modifier = Modifier.weight(1f))
ProviderMetricTile(label = "Setup", value = needsSetupCount.toString(), modifier = Modifier.weight(1f))
}
@@ -398,8 +474,14 @@ private fun ProviderSetupListRow(
Text(text = row.subtitle, style = ClawTheme.type.caption.copy(fontSize = 12.5.sp, lineHeight = 16.sp), color = ClawTheme.colors.textMuted, maxLines = 1, overflow = TextOverflow.Ellipsis)
}
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(6.dp)) {
Box(modifier = Modifier.size(5.dp).clip(CircleShape).background(if (row.ready) ClawTheme.colors.success else ClawTheme.colors.warning))
Text(text = if (row.ready) "Ready" else "Setup", style = ClawTheme.type.caption.copy(fontSize = 12.5.sp, lineHeight = 16.sp), color = ClawTheme.colors.textMuted, maxLines = 1)
val statusColor =
when {
row.warning -> ClawTheme.colors.warning
row.ready || row.available -> ClawTheme.colors.success
else -> ClawTheme.colors.textMuted
}
Box(modifier = Modifier.size(5.dp).clip(CircleShape).background(statusColor))
Text(text = row.statusLabel, style = ClawTheme.type.caption.copy(fontSize = 12.5.sp, lineHeight = 16.sp), color = ClawTheme.colors.textMuted, maxLines = 1)
Icon(imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight, contentDescription = "Open ${row.name}", modifier = Modifier.size(17.dp), tint = ClawTheme.colors.text)
}
}
@@ -415,7 +497,13 @@ private fun ProviderListRow(row: ProviderRow) {
Text(text = if (row.modelCount > 0) "${row.modelCount} models" else "Provider setup", style = ClawTheme.type.caption.copy(fontSize = 12.5.sp, lineHeight = 16.sp), color = ClawTheme.colors.textMuted, maxLines = 1)
}
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(5.dp)) {
Box(modifier = Modifier.size(4.5.dp).clip(CircleShape).background(if (row.ready) ClawTheme.colors.success else ClawTheme.colors.warning))
val statusColor =
when {
row.warning || row.setupRequired -> ClawTheme.colors.warning
row.ready || row.available -> ClawTheme.colors.success
else -> ClawTheme.colors.textMuted
}
Box(modifier = Modifier.size(4.5.dp).clip(CircleShape).background(statusColor))
Text(text = row.status, style = ClawTheme.type.caption.copy(fontSize = 12.5.sp, lineHeight = 16.sp), color = ClawTheme.colors.textMuted, maxLines = 1)
}
}
@@ -491,12 +579,13 @@ private fun ModelGroup(
@Composable
private fun ModelRow(model: GatewayModelSummary) {
val available = modelAvailabilityUsable(model)
Row(modifier = Modifier.fillMaxWidth().heightIn(min = 48.dp).padding(horizontal = 10.dp, vertical = 5.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(6.dp)) {
Text(text = model.name, style = ClawTheme.type.mono, color = ClawTheme.colors.text, modifier = Modifier.weight(1f), maxLines = 1, overflow = TextOverflow.Ellipsis)
modelCapabilityLabels(model).take(3).forEach { label ->
ProviderMiniTag(text = label)
}
Box(modifier = Modifier.size(4.5.dp).clip(CircleShape).background(ClawTheme.colors.success))
Box(modifier = Modifier.size(4.5.dp).clip(CircleShape).background(if (available) ClawTheme.colors.success else ClawTheme.colors.warning))
}
}

View File

@@ -342,7 +342,8 @@ private fun OverviewScreen(
val cronStatus by viewModel.cronStatus.collectAsState()
val nodesDevicesSummary by viewModel.nodesDevicesSummary.collectAsState()
val channelsSummary by viewModel.channelsSummary.collectAsState()
val readyProviderCount = providers.count { modelProviderReady(it.status) }
val readyProviderCount = readyModelProviderCount(providers, models)
val expiringProviderCount = expiringModelProviderCount(providers)
val attentionRows =
homeAttentionRows(
isConnected = isConnected,
@@ -350,6 +351,7 @@ private fun OverviewScreen(
channelsSummary = channelsSummary,
nodesDevicesSummary = nodesDevicesSummary,
readyProviderCount = readyProviderCount,
expiringProviderCount = expiringProviderCount,
)
LaunchedEffect(isConnected) {
@@ -460,6 +462,7 @@ private fun OverviewScreen(
when {
!isConnected -> "Offline"
readyProviderCount > 0 -> "$readyProviderCount ready"
expiringProviderCount > 0 -> "$expiringProviderCount expiring"
models.isNotEmpty() -> "${models.size} models"
else -> "Setup"
},
@@ -541,6 +544,7 @@ internal fun homeAttentionRows(
channelsSummary: GatewayChannelsSummary,
nodesDevicesSummary: GatewayNodesDevicesSummary,
readyProviderCount: Int,
expiringProviderCount: Int = 0,
): List<HomeAttentionRow> =
listOfNotNull(
if (!isConnected) {
@@ -563,7 +567,12 @@ internal fun homeAttentionRows(
} else {
null
},
if (isConnected && readyProviderCount == 0) {
if (isConnected && expiringProviderCount > 0) {
HomeAttentionRow("Providers", "Provider auth expires soon", Icons.Outlined.Inventory2, Tab.ProvidersModels)
} else {
null
},
if (isConnected && readyProviderCount == 0 && expiringProviderCount == 0) {
HomeAttentionRow("Providers", "No ready providers", Icons.Outlined.Inventory2, Tab.ProvidersModels)
} else {
null

View File

@@ -1,5 +1,8 @@
package ai.openclaw.app.ui
import ai.openclaw.app.GatewayModelProviderSummary
import ai.openclaw.app.GatewayModelSummary
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
@@ -10,8 +13,129 @@ class ProviderModelStatusTest {
assertTrue(modelProviderReady("static"))
}
@Test
fun expiringProviderStatusIsNotFullyReady() {
assertFalse(modelProviderReady("expiring"))
}
@Test
fun missingProviderStatusIsNotReady() {
assertFalse(modelProviderReady("missing"))
}
@Test
fun readyModelProviderCountUsesAuthBackedProviderStatuses() {
val providers =
listOf(
GatewayModelProviderSummary(id = "openai", displayName = "OpenAI", status = "missing", profileCount = 0),
GatewayModelProviderSummary(id = "anthropic", displayName = "Anthropic", status = "ready", profileCount = 1),
GatewayModelProviderSummary(id = "openai", displayName = "OpenAI", status = "expiring", profileCount = 1),
)
assertEquals(1, readyModelProviderCount(providers, emptyList()))
assertEquals(1, expiringModelProviderCount(providers))
}
@Test
fun readyModelProviderCountUsesAvailableModelsAsServingReadiness() {
val models =
listOf(
model(provider = "anthropic", available = true),
model(provider = "anthropic", available = true),
model(provider = "openrouter", available = false),
)
assertEquals(1, readyModelProviderCount(emptyList(), models))
}
@Test
fun readyModelProviderCountDoesNotTreatCatalogOnlyModelsAsReady() {
val providers =
listOf(
GatewayModelProviderSummary(id = "openrouter", displayName = "OpenRouter", status = "missing", profileCount = 0),
)
val models =
listOf(
model(provider = "openrouter", available = false),
)
assertEquals(0, readyModelProviderCount(providers, models))
}
@Test
fun readyModelProviderCountPreservesLegacyRowsWhenAvailabilityIsMissing() {
val models =
listOf(
model(provider = "openrouter", available = null),
)
assertEquals(1, readyModelProviderCount(emptyList(), models))
}
@Test
fun readyModelProviderCountTreatsExpiringAvailableModelsAsUsableButWarnable() {
val providers =
listOf(
GatewayModelProviderSummary(id = "openai", displayName = "OpenAI", status = "expiring", profileCount = 1),
)
val models =
listOf(
model(provider = "openai", available = true),
)
assertEquals(1, readyModelProviderCount(providers, models))
assertEquals(1, expiringModelProviderCount(providers))
assertFalse(modelProviderReady("expiring"))
}
@Test
fun providerCommandSubtitleSurfacesExpiringBeforeReadyModels() {
val providers =
listOf(
GatewayModelProviderSummary(id = "openai", displayName = "OpenAI", status = "expiring", profileCount = 1),
)
val models =
listOf(
model(provider = "openai", available = true),
)
assertEquals("1 providers expiring", providerCommandSubtitle(isConnected = true, providers = providers, models = models))
}
@Test
fun readyModelProviderCountDoesNotTreatUnavailableModelsAsReadyWhenAuthProviderNeedsSetup() {
val providers =
listOf(
GatewayModelProviderSummary(id = "openai", displayName = "OpenAI", status = "missing", profileCount = 0),
)
val models =
listOf(
model(provider = "openai", available = false),
)
assertEquals(0, readyModelProviderCount(providers, models))
}
@Test
fun modelAvailabilityHonorsExplicitUnavailableRows() {
assertTrue(modelAvailabilityUsable(model(provider = "openai", available = true)))
assertTrue(modelAvailabilityUsable(model(provider = "openai", available = null)))
assertFalse(modelAvailabilityUsable(model(provider = "openai", available = false)))
}
private fun model(
provider: String,
available: Boolean?,
): GatewayModelSummary =
GatewayModelSummary(
id = "$provider/test-model",
name = "test-model",
provider = provider,
available = available,
supportsVision = false,
supportsAudio = false,
supportsDocuments = false,
supportsReasoning = false,
contextTokens = null,
)
}

View File

@@ -92,6 +92,21 @@ class ShellScreenLogicTest {
assertEquals(emptyList<String>(), rows.map { it.title })
}
@Test
fun homeAttentionRowsSurfaceExpiringProviderAuth() {
val rows =
homeAttentionRows(
isConnected = true,
pendingApprovals = 0,
channelsSummary = emptyChannels(),
nodesDevicesSummary = emptyNodesDevices(),
readyProviderCount = 0,
expiringProviderCount = 1,
)
assertEquals(listOf("Provider auth expires soon"), rows.map { it.subtitle })
}
private fun emptyChannels(): GatewayChannelsSummary = GatewayChannelsSummary(channels = emptyList())
private fun emptyNodesDevices(): GatewayNodesDevicesSummary = GatewayNodesDevicesSummary(nodes = emptyList(), pendingDevices = emptyList(), pairedDevices = emptyList())

View File

@@ -59,6 +59,11 @@ struct SettingsProTab: View {
@State var notificationActionText = "Request Access"
@State var diagnosticsLastRunText = "Not run"
@State var diagnosticsIssueCount: Int?
@State var bottomOverlayInset: CGFloat = 0
var bottomScrollMargin: CGFloat {
max(0, self.bottomOverlayInset - SettingsLayout.rowHeight - SettingsLayout.bottomContentPadding)
}
var body: some View {
NavigationStack {
@@ -71,9 +76,13 @@ struct SettingsProTab: View {
self.gatewaySection
self.settingsListSection
}
.padding(.vertical, 18)
.padding(.top, 18)
.padding(.bottom, SettingsLayout.bottomContentPadding)
}
.safeAreaPadding(.bottom, OpenClawProMetric.bottomScrollInset)
.contentMargins(.bottom, self.bottomScrollMargin, for: .scrollContent)
SettingsBottomOverlayInsetReader(inset: self.$bottomOverlayInset)
.frame(width: 0, height: 0)
.allowsHitTesting(false)
}
.navigationBarHidden(true)
.navigationDestination(for: SettingsRoute.self) { route in

View File

@@ -37,6 +37,8 @@ extension SettingsProTab {
NavigationLink(value: SettingsRoute.gateway) {
self.gatewayConnectionRow
.padding(14)
.frame(maxWidth: .infinity, minHeight: SettingsLayout.rowHeight, alignment: .leading)
.contentShape(Rectangle())
}
.buttonStyle(.plain)
Divider()
@@ -201,9 +203,13 @@ extension SettingsProTab {
self.aboutDestination
}
}
.padding(.vertical, 18)
.padding(.top, 18)
.padding(.bottom, SettingsLayout.bottomContentPadding)
}
.safeAreaPadding(.bottom, OpenClawProMetric.bottomScrollInset)
.contentMargins(.bottom, self.bottomScrollMargin, for: .scrollContent)
SettingsBottomOverlayInsetReader(inset: self.$bottomOverlayInset)
.frame(width: 0, height: 0)
.allowsHitTesting(false)
}
.navigationTitle(self.title(for: route))
.navigationBarTitleDisplayMode(.inline)
@@ -239,11 +245,11 @@ extension SettingsProTab {
}
.padding(.horizontal, OpenClawProMetric.pagePadding)
self.manualGatewayCard
self.deviceIdentityCard
self.agentSelectionCard
self.gatewaySetupCard
self.discoveredGatewaysCard
self.manualGatewayCard
self.gatewayAdvancedCard
}
}
@@ -292,18 +298,6 @@ extension SettingsProTab {
value: self.diagnosticsHealthValue,
color: self.gatewayConnected ? OpenClawBrand.ok : OpenClawBrand.warn)
self.diagnosticChecksCard
self.detailListCard {
self.detailRow("Device", value: DeviceInfoHelper.deviceFamily())
Divider()
self.detailRow("Platform", value: DeviceInfoHelper.platformStringForDisplay())
Divider()
self.detailRow("App", value: DeviceInfoHelper.openClawVersionString())
Divider()
self.detailRow("Model", value: DeviceInfoHelper.modelIdentifier())
}
ProCard(radius: SettingsLayout.cardRadius) {
self.gatewayActionButton(
title: "Run Diagnostics",
@@ -316,6 +310,18 @@ extension SettingsProTab {
}
.padding(.horizontal, OpenClawProMetric.pagePadding)
self.diagnosticChecksCard
self.detailListCard {
self.detailRow("Device", value: DeviceInfoHelper.deviceFamily())
Divider()
self.detailRow("Platform", value: DeviceInfoHelper.platformStringForDisplay())
Divider()
self.detailRow("App", value: DeviceInfoHelper.openClawVersionString())
Divider()
self.detailRow("Model", value: DeviceInfoHelper.modelIdentifier())
}
self.diagnosticsAdvancedCard
}
}

View File

@@ -1,5 +1,6 @@
import Darwin
import SwiftUI
import UIKit
enum SettingsRoute: Hashable {
case gateway
@@ -14,6 +15,110 @@ enum SettingsRoute: Hashable {
enum SettingsLayout {
static let cardRadius: CGFloat = 12
static let rowHeight: CGFloat = 58
static let bottomContentPadding: CGFloat = 12
}
struct SettingsBottomOverlayInsetReader: UIViewRepresentable {
@Binding var inset: CGFloat
func makeCoordinator() -> Coordinator {
Coordinator(inset: self.$inset)
}
func makeUIView(context: Context) -> SettingsBottomOverlayInsetProbeView {
let view = SettingsBottomOverlayInsetProbeView()
view.onInsetChange = { value in
context.coordinator.updateInset(value)
}
return view
}
func updateUIView(_ uiView: SettingsBottomOverlayInsetProbeView, context: Context) {
context.coordinator.inset = self.$inset
uiView.onInsetChange = { value in
context.coordinator.updateInset(value)
}
uiView.updateInset()
}
final class Coordinator {
var inset: Binding<CGFloat>
init(inset: Binding<CGFloat>) {
self.inset = inset
}
func updateInset(_ value: CGFloat) {
let rounded = max(0, ceil(value))
guard abs(self.inset.wrappedValue - rounded) > 0.5 else { return }
self.inset.wrappedValue = rounded
}
}
}
final class SettingsBottomOverlayInsetProbeView: UIView {
var onInsetChange: ((CGFloat) -> Void)?
override func didMoveToWindow() {
super.didMoveToWindow()
self.updateInset()
}
override func layoutSubviews() {
super.layoutSubviews()
self.updateInset()
}
func updateInset() {
let value = self.visibleTabBarHeight()
DispatchQueue.main.async { [weak self] in
self?.onInsetChange?(value)
}
}
private func visibleTabBarHeight() -> CGFloat {
let tabBarController = self.nearestViewController()?.tabBarController
?? self.findTabBarController(in: self.window?.rootViewController)
guard let tabBar = tabBarController?.tabBar,
!tabBar.isHidden,
tabBar.alpha > 0.01,
tabBar.window != nil,
self.window != nil
else {
return 0
}
let tabFrame = tabBar.convert(tabBar.bounds, to: nil)
guard tabFrame.height.isFinite else { return 0 }
return max(0, tabFrame.height)
}
private func nearestViewController() -> UIViewController? {
var responder: UIResponder? = self
while let current = responder {
if let viewController = current as? UIViewController {
return viewController
}
responder = current.next
}
return nil
}
private func findTabBarController(in viewController: UIViewController?) -> UITabBarController? {
guard let viewController else { return nil }
if let tabBarController = viewController as? UITabBarController {
return tabBarController
}
if let tabBarController = self.findTabBarController(in: viewController.presentedViewController) {
return tabBarController
}
for child in viewController.children {
if let tabBarController = self.findTabBarController(in: child) {
return tabBarController
}
}
return nil
}
}
enum SettingsDiagnosticIssue: String, Equatable, CaseIterable {

View File

@@ -15,6 +15,7 @@ struct TalkProTab: View {
gatewayConnected: self.gatewayConnected,
isEnabled: self.appModel.talkMode.isEnabled || self.talkEnabled,
statusText: self.appModel.talkMode.statusText,
isConfigLoaded: self.appModel.talkMode.gatewayTalkConfigLoaded,
isListening: self.appModel.talkMode.isListening,
isSpeaking: self.appModel.talkMode.isSpeaking,
isUserSpeechDetected: self.appModel.talkMode.isUserSpeechDetected,
@@ -282,6 +283,9 @@ struct TalkProTab: View {
if self.state
.prefersPermissionCopy { return "Gateway approval is required before this phone can capture voice." }
if !self.gatewayConnected { return "Connect to your gateway to start a voice conversation." }
if !self.appModel.talkMode.gatewayTalkConfigLoaded {
return "Open Voice settings after the gateway loads Talk configuration."
}
let subtitle = (self.appModel.talkMode.gatewayTalkVoiceModeSubtitle ?? "")
.trimmingCharacters(in: .whitespacesAndNewlines)
if !subtitle.isEmpty { return subtitle }
@@ -365,6 +369,7 @@ struct TalkProState: Equatable {
let gatewayConnected: Bool
let isEnabled: Bool
let statusText: String
let isConfigLoaded: Bool
let isListening: Bool
let isSpeaking: Bool
let isUserSpeechDetected: Bool
@@ -390,6 +395,7 @@ struct TalkProState: Equatable {
default:
break
}
if !self.isConfigLoaded { return "Voice config unavailable" }
if self.isSpeaking { return "Speaking" }
if self.isListening { return "Listening" }
if self.normalizedStatus.contains("connecting") { return "Connecting" }
@@ -412,6 +418,7 @@ struct TalkProState: Equatable {
default:
break
}
if !self.isConfigLoaded { return "Config" }
if self.isSpeaking { return "Speaking" }
if self.isListening { return "Listening" }
if self.isEnabled { return "Ready" }
@@ -432,6 +439,7 @@ struct TalkProState: Equatable {
default:
break
}
if !self.isConfigLoaded { return "exclamationmark.triangle.fill" }
if self.isSpeaking { return "speaker.wave.2.fill" }
if self.isListening { return "mic.fill" }
if self.normalizedStatus.contains("thinking") { return "sparkles" }
@@ -447,6 +455,7 @@ struct TalkProState: Equatable {
case .missingScope, .requestingUpgrade, .upgradeRequested, .apiKeyMissing:
return OpenClawBrand.warn
default:
if !self.isConfigLoaded { return OpenClawBrand.warn }
return self.isEnabled ? OpenClawBrand.ok : OpenClawBrand.accentHot
}
}
@@ -518,6 +527,7 @@ struct TalkProState: Equatable {
default:
break
}
if !self.isConfigLoaded { return .still }
if self.isSpeaking { return .speaking }
if self.isListening, self.isUserSpeechDetected { return .inputSpeech }
if self.isListening { return .level(micLevel) }

View File

@@ -0,0 +1,73 @@
import Testing
@testable import OpenClaw
@Suite struct TalkProStateTests {
@Test func disabledTalkWithoutLoadedConfigCanStartAndRetryLoad() {
let state = TalkProState(
gatewayConnected: true,
isEnabled: false,
statusText: "Offline",
isConfigLoaded: false,
isListening: false,
isSpeaking: false,
isUserSpeechDetected: false,
permissionState: .unknown)
#expect(state.title == "Voice config unavailable")
#expect(state.chipText == "Config")
#expect(state.primaryAction == .start)
#expect(state.primaryButtonTitle == "Start Talk")
#expect(state.waveformMode(micLevel: 0.8) == .still)
}
@Test func enabledTalkWithoutLoadedConfigCanBeStopped() {
let state = TalkProState(
gatewayConnected: true,
isEnabled: true,
statusText: "Offline",
isConfigLoaded: false,
isListening: false,
isSpeaking: false,
isUserSpeechDetected: false,
permissionState: .unknown)
#expect(state.title == "Voice config unavailable")
#expect(state.chipText == "Config")
#expect(state.primaryAction == .stop)
#expect(state.primaryButtonTitle == "Stop Talk")
#expect(state.waveformMode(micLevel: 0.8) == .still)
}
@Test func enabledTalkWithLoadedConfigCanBeStopped() {
let state = TalkProState(
gatewayConnected: true,
isEnabled: true,
statusText: "Ready",
isConfigLoaded: true,
isListening: false,
isSpeaking: false,
isUserSpeechDetected: false,
permissionState: .ready)
#expect(state.title == "Ready to talk")
#expect(state.chipText == "Ready")
#expect(state.primaryAction == .stop)
}
@Test func missingScopeTakesPriorityOverUnloadedConfig() {
let state = TalkProState(
gatewayConnected: true,
isEnabled: false,
statusText: "Offline",
isConfigLoaded: false,
isListening: false,
isSpeaking: false,
isUserSpeechDetected: false,
permissionState: .missingScope("operator.talk.secrets"))
#expect(state.title == "Gateway permission required")
#expect(state.chipText == "Needs approval")
#expect(state.primaryAction == .enablePermission)
#expect(state.primaryButtonTitle == "Enable Talk")
}
}

View File

@@ -201,6 +201,7 @@ struct OpenClawChatComposer: View {
Image(systemName: "paperclip")
}
.help("Add Image")
.accessibilityLabel("Attachments")
.buttonStyle(.plain)
.controlSize(.small)
} else {
@@ -210,6 +211,7 @@ struct OpenClawChatComposer: View {
Image(systemName: "paperclip")
}
.help("Add Image")
.accessibilityLabel("Attachments")
.buttonStyle(.bordered)
.controlSize(.small)
}
@@ -219,6 +221,7 @@ struct OpenClawChatComposer: View {
Image(systemName: "paperclip")
}
.help("Add Image")
.accessibilityLabel("Attachments")
.buttonStyle(.plain)
.controlSize(.small)
.onChange(of: self.pickerItems) { _, newItems in
@@ -229,6 +232,7 @@ struct OpenClawChatComposer: View {
Image(systemName: "paperclip")
}
.help("Add Image")
.accessibilityLabel("Attachments")
.buttonStyle(.bordered)
.controlSize(.small)
.onChange(of: self.pickerItems) { _, newItems in

View File

@@ -1,3 +1,4 @@
// Bundled A2UI runtime resource embedded by OpenClawKit.
var __defProp$1 = Object.defineProperty;
var __exportAll = (all, no_symbols) => {
let target = {};

View File

@@ -52,7 +52,6 @@ public struct ConnectParams: Codable, Sendable {
public let client: [String: AnyCodable]
public let caps: [String]?
public let commands: [String]?
public let nodeplugintools: [NodePluginToolDescriptor]?
public let permissions: [String: AnyCodable]?
public let pathenv: String?
public let role: String?
@@ -68,7 +67,6 @@ public struct ConnectParams: Codable, Sendable {
client: [String: AnyCodable],
caps: [String]?,
commands: [String]?,
nodeplugintools: [NodePluginToolDescriptor]?,
permissions: [String: AnyCodable]?,
pathenv: String?,
role: String?,
@@ -83,7 +81,6 @@ public struct ConnectParams: Codable, Sendable {
self.client = client
self.caps = caps
self.commands = commands
self.nodeplugintools = nodeplugintools
self.permissions = permissions
self.pathenv = pathenv
self.role = role
@@ -100,7 +97,6 @@ public struct ConnectParams: Codable, Sendable {
case client
case caps
case commands
case nodeplugintools = "nodePluginTools"
case permissions
case pathenv = "pathEnv"
case role
@@ -1132,54 +1128,6 @@ public struct NodeRenameParams: Codable, Sendable {
public struct NodeListParams: Codable, Sendable {}
public struct NodePluginToolDescriptor: Codable, Sendable {
public let pluginid: String
public let name: String
public let description: String
public let parameters: [String: AnyCodable]?
public let command: String?
public let mcp: [String: AnyCodable]?
public init(
pluginid: String,
name: String,
description: String,
parameters: [String: AnyCodable]?,
command: String?,
mcp: [String: AnyCodable]?)
{
self.pluginid = pluginid
self.name = name
self.description = description
self.parameters = parameters
self.command = command
self.mcp = mcp
}
private enum CodingKeys: String, CodingKey {
case pluginid = "pluginId"
case name
case description
case parameters
case command
case mcp
}
}
public struct NodePluginToolsUpdateParams: Codable, Sendable {
public let tools: [NodePluginToolDescriptor]
public init(
tools: [NodePluginToolDescriptor])
{
self.tools = tools
}
private enum CodingKeys: String, CodingKey {
case tools
}
}
public struct NodePendingAckParams: Codable, Sendable {
public let ids: [String]
@@ -4729,6 +4677,7 @@ public struct ModelChoice: Codable, Sendable {
public let name: String
public let provider: String
public let alias: String?
public let available: Bool?
public let contextwindow: Int?
public let reasoning: Bool?
@@ -4737,6 +4686,7 @@ public struct ModelChoice: Codable, Sendable {
name: String,
provider: String,
alias: String?,
available: Bool? = nil,
contextwindow: Int?,
reasoning: Bool?)
{
@@ -4744,6 +4694,7 @@ public struct ModelChoice: Codable, Sendable {
self.name = name
self.provider = provider
self.alias = alias
self.available = available
self.contextwindow = contextwindow
self.reasoning = reasoning
}
@@ -4753,6 +4704,7 @@ public struct ModelChoice: Codable, Sendable {
case name
case provider
case alias
case available
case contextwindow = "contextWindow"
case reasoning
}

View File

@@ -1,4 +1,4 @@
e3b8988a10c61dbf0a78a70bca9ef1ab43c6a58aeaa5ef9f8699f34b6dae4c9d config-baseline.json
a2f53abfe6bbe8b1ddfa5548f555704d8ff0cdd48bcb5780d66499bec0b7775a config-baseline.core.json
3d0f7723873da553f25dfe6892a586d774fa36e447de487eba4dd3e0a012f877 config-baseline.channel.json
60c0700719fd2fe3f7cec4c35da10227b681d87ed1a3876ef830eb6bd80d43f2 config-baseline.json
2ed21fa4a416ac2cec55eb2b6d1b11859aa04b40bd78c6ed9f3eb45b7240261c config-baseline.core.json
0637c9bdcb9517f56049dd786563366877458d35df575328a6b80a890c8bc915 config-baseline.channel.json
e6a1d6f51f0d9c04bd92d51deebfaca8c7917dd28d7998d225c0074e0a095348 config-baseline.plugin.json

View File

@@ -1,2 +1,2 @@
9ce72d763de6c95566e0167f99f5454b07c7c67940675533cb24c07058619a63 plugin-sdk-api-baseline.json
e4dfccb85b985fe865145e24978255b729cdcbca0e26650a363a11bfcfc2e27b plugin-sdk-api-baseline.jsonl
a3ab01b572937539e563aa320ad80135bc701e20fffc43c0351d799590b7a0e0 plugin-sdk-api-baseline.json
9d49587923f8fc4abb16d981bcab54acbf90a3e74ab05933761049e2da0cffe1 plugin-sdk-api-baseline.jsonl

View File

@@ -162,6 +162,7 @@ Configure your tunnel's ingress rules to only route the webhook path:
4. DM access is pairing by default. Unknown senders receive a pairing code; approve with:
- `openclaw pairing approve googlechat <code>`
5. Group spaces require @-mention by default. Use `botUser` if mention detection needs the app's user name.
6. When an exec or plugin approval request starts from Google Chat and a stable `users/<id>` approver is configured, OpenClaw posts a native Google Chat approval card in the originating space or thread. The card buttons use opaque callback tokens, and the manual `/approve <id> <decision>` prompt is only shown when native approval delivery is unavailable.
## Targets
@@ -214,8 +215,9 @@ Notes:
- Default webhook path is `/googlechat` if `webhookPath` isn't set.
- `dangerouslyAllowNameMatching` re-enables mutable email principal matching for allowlists (break-glass compatibility mode).
- Reactions are available via the `reactions` tool and `channels action` when `actions.reactions` is enabled.
- Native approval cards use Google Chat `cardsV2` button clicks, not reaction events. Approvers come from `dm.allowFrom` or `defaultTo` and must be stable numeric `users/<id>` values.
- Message actions expose `send` for text and `upload-file` for explicit attachment sends. `upload-file` accepts `media` / `filePath` / `path` plus optional `message`, `filename`, and thread targeting.
- `typingIndicator` supports `none`, `message` (default), and `reaction` (reaction requires user OAuth).
- `typingIndicator` supports `message` (default), `none`, and `reaction` (reaction requires user OAuth).
- Attachments are downloaded through the Chat API and stored in the media pipeline (size capped by `mediaMaxMb`).
- Bot-authored Google Chat messages are ignored by default. If you intentionally set `allowBots: true`, accepted bot-authored messages use shared [bot loop protection](/channels/bot-loop-protection). Configure `channels.defaults.botLoopProtection`, then override with `channels.googlechat.botLoopProtection` or `channels.googlechat.groups.<space>.botLoopProtection` when one space needs a different budget.

View File

@@ -232,6 +232,20 @@ Notes:
- Tool-progress preview updates are enabled by default when Matrix preview streaming is active. Set `streaming.preview.toolProgress: false` to keep preview edits for answer text but leave tool progress on the normal delivery path.
- Preview edits cost extra Matrix API calls. Leave `streaming: "off"` if you want the most conservative rate-limit profile.
## Voice messages
Inbound Matrix voice notes are transcribed before the room mention gate. This lets a voice note that says the bot name trigger the agent in a `requireMention: true` room, and it gives the agent the transcript instead of only an audio attachment placeholder.
Matrix uses the shared audio media provider configured under `tools.media.audio`, such as OpenAI `gpt-4o-mini-transcribe`. See [Media tools overview](/tools/media-overview) for provider setup and limits.
Behavior details:
- `m.audio` events and `m.file` events with an `audio/*` MIME type are eligible.
- In encrypted rooms, OpenClaw decrypts the attachment through the existing Matrix media path before transcription.
- The transcript is marked as machine-generated and untrusted in the agent prompt.
- The attachment is marked as already transcribed so downstream media tools do not transcribe the same voice note again.
- Set `tools.media.audio.enabled: false` to disable audio transcription globally.
## Approval metadata
Matrix native approval prompts are normal `m.room.message` events with OpenClaw-specific custom event content under `com.openclaw.approval`. Matrix permits custom event-content keys, so stock clients still render the text body while OpenClaw-aware clients can read the structured approval id, kind, state, available decisions, and exec/plugin details.

View File

@@ -334,7 +334,7 @@ If you pass `--url`, that explicit target is added ahead of both. Human output l
- `Local loopback`
<Note>
If multiple gateways are reachable, it prints all of them. Multiple gateways are supported when you use isolated profiles/ports (e.g., a rescue bot), but most installs still run a single gateway.
If multiple probe targets are reachable, it prints all of them. An SSH tunnel, TLS/proxy URL, and configured remote URL can all point at the same gateway even when their transport ports differ; `multiple_gateways` is reserved for distinct or identity-ambiguous reachable gateways. Multiple gateways are supported when you use isolated profiles (e.g., a rescue bot), but most installs still run a single gateway.
</Note>
```bash
@@ -379,7 +379,7 @@ openclaw gateway probe --json
</Accordion>
<Accordion title="Common warning codes">
- `ssh_tunnel_failed`: SSH tunnel setup failed; the command fell back to direct probes.
- `multiple_gateways`: more than one target was reachable; this is unusual unless you intentionally run isolated profiles, such as a rescue bot.
- `multiple_gateways`: distinct gateway identities were reachable, or OpenClaw could not prove reachable targets are the same gateway. An SSH tunnel, proxy URL, or configured remote URL to the same gateway does not trigger this warning.
- `auth_secretref_unresolved`: a configured auth SecretRef could not be resolved for a failed target.
- `probe_scope_limited`: WebSocket connect succeeded, but the read probe was limited by missing `operator.read`.

View File

@@ -25,13 +25,6 @@ Common use cases:
Execution is still guarded by **exec approvals** and per-agent allowlists on the
node host, so you can keep command access scoped and explicit.
Gateway-loaded plugins can also register node-host commands. When a registered
command includes `agentTool` metadata, `openclaw node run` advertises that
plugin or MCP-backed tool to the Gateway while the node is connected. The agent
sees it as a normal plugin tool, but execution still goes through `node.invoke`
and the node command allowlist, so disconnecting the node removes the tool from
new agent runs.
## Browser proxy (zero-config)
Node hosts automatically advertise a browser proxy if `browser.enabled` is not

View File

@@ -70,6 +70,10 @@ present.
`vsearch` and `query`). `search` is BM25-only, so OpenClaw skips semantic
vector readiness probes and embedding maintenance in that mode. If a mode
fails, OpenClaw retries with `qmd query`.
- When `searchMode` is `query`, set `memory.qmd.rerank` to `false` to use QMD's
hybrid query path without the reranker. OpenClaw passes `--no-rerank` to the
direct QMD CLI path and `rerank: false` to QMD's MCP query tool. This option
requires QMD 2.1 or newer.
- With QMD releases that advertise multi-collection filters, OpenClaw groups
same-source collections into one QMD search invocation. Older QMD releases
keep the compatible per-collection fallback.

View File

@@ -387,7 +387,7 @@ On normal runs, `HEARTBEAT.md` is only injected when heartbeat guidance is enabl
On the native Codex harness, `HEARTBEAT.md` content is not injected into the turn. If the file exists and has non-whitespace content, the heartbeat collaboration-mode instructions point Codex at the file and tell it to read before proceeding.
If `HEARTBEAT.md` exists but is effectively empty (only blank lines and markdown headers like `# Heading`), OpenClaw skips the heartbeat run to save API calls. That skip is reported as `reason=empty-heartbeat-file`. If the file is missing, the heartbeat still runs and the model decides what to do.
If `HEARTBEAT.md` exists but is effectively empty (only blank lines, Markdown/HTML comments, Markdown headings like `# Heading`, fence markers, or empty checklist stubs), OpenClaw skips the heartbeat run to save API calls. That skip is reported as `reason=empty-heartbeat-file`. If the file is missing, the heartbeat still runs and the model decides what to do.
Keep it tiny (short checklist or reminders) to avoid prompt bloat.

View File

@@ -167,8 +167,10 @@ What to expect:
- `gateway status --deep` can report `Other gateway-like services detected (best effort)`
and print cleanup hints when stale launchd/systemd/schtasks installs are still around.
- `gateway probe` can warn about `multiple reachable gateways` when more than one target
answers.
- `gateway probe` can warn about `multiple reachable gateway identities` when distinct
gateways answer, or when OpenClaw cannot prove reachable targets are the same gateway.
An SSH tunnel, proxy URL, or configured remote URL to the same gateway is one
gateway with multiple transports, even when transport ports differ.
- If that is intentional, isolate ports, config/state, and workspace roots per gateway.
Checklist per instance:

View File

@@ -169,7 +169,7 @@ openclaw --profile rescue browser status
Interpretation:
- `gateway status --deep` helps catch stale launchd/systemd/schtasks services from older installs.
- `gateway probe` warning text such as `multiple reachable gateways detected` is expected only when you intentionally run more than one isolated gateway.
- `gateway probe` warning text such as `multiple reachable gateway identities detected` is expected only when you intentionally run more than one isolated gateway, or when OpenClaw cannot prove reachable probe targets are the same gateway. An SSH tunnel, proxy URL, or configured remote URL to the same gateway is one gateway with multiple transports, even when transport ports differ.
## Related

View File

@@ -270,13 +270,6 @@ Nodes declare capability claims at connect time:
- `permissions`: granular toggles (e.g. `screen.record`, `camera.capture`).
The Gateway treats these as **claims** and enforces server-side allowlists.
Connected nodes can publish optional agent-visible plugin or MCP tool
descriptors with `node.pluginTools.update` after a successful connect, after
reconnect, or after a local plugin/MCP inventory change. Each descriptor must
use a provider-safe tool `name` and name a `command` in the node's current
command allowlist. The Gateway filters descriptors outside the approved command
surface, removes them when the node disconnects, and rejects operator attempts
to mutate another node's catalog.
## Presence
@@ -468,7 +461,6 @@ enumeration of `src/gateway/server-methods/*.ts`.
- `node.invoke` forwards a command to a connected node.
- `node.invoke.result` returns the result for an invoke request.
- `node.event` carries node-originated events back into the gateway.
- `node.pluginTools.update` replaces the connected node's agent-visible plugin/MCP tool descriptors.
- `node.pending.pull` and `node.pending.ack` are the connected-node queue APIs.
- `node.pending.enqueue` and `node.pending.drain` manage durable pending work for offline/disconnected nodes.

View File

@@ -625,7 +625,7 @@ Look for:
Common signatures:
- `SSH tunnel failed to start; falling back to direct probes.` → SSH setup failed, but the command still tried direct configured/loopback targets.
- `multiple reachable gateways detected` → more than one target answered. Usually this means an intentional multi-gateway setup or stale/duplicate listeners.
- `multiple reachable gateway identities detected` → distinct gateways answered, or OpenClaw could not prove reachable targets are the same gateway. An SSH tunnel, proxy URL, or configured remote URL to the same gateway is treated as one gateway with multiple transports, even when transport ports differ.
- `Read-probe diagnostics are limited by gateway scopes (missing operator.read)` → connect worked, but detail RPC is scope-limited; pair device identity or use credentials with `operator.read`.
- `Gateway accepted the WebSocket connection, but follow-up read diagnostics failed` → connect worked, but the full diagnostic RPC set timed out or failed. Treat this as a reachable Gateway with degraded diagnostics; compare `connect.ok` and `connect.rpcOk` in `--json` output.
- `Capability: pairing-pending` or `gateway closed (1008): pairing required` → the gateway answered, but this client still needs pairing/approval before normal operator access.
@@ -691,7 +691,7 @@ Look for:
- `cron: scheduler disabled; jobs will not run automatically` → cron disabled.
- `cron: timer tick failed` → scheduler tick failed; check file/log/runtime errors.
- `heartbeat skipped` with `reason=quiet-hours` → outside active hours window.
- `heartbeat skipped` with `reason=empty-heartbeat-file` → `HEARTBEAT.md` exists but only contains blank lines / markdown headers, so OpenClaw skips the model call.
- `heartbeat skipped` with `reason=empty-heartbeat-file` → `HEARTBEAT.md` exists but only contains blank, comment, header, fence, or empty-checklist scaffolding, so OpenClaw skips the model call.
- `heartbeat skipped` with `reason=no-tasks-due` → `HEARTBEAT.md` contains a `tasks:` block, but none of the tasks are due on this tick.
- `heartbeat: unknown accountId` → invalid account id for heartbeat delivery target.
- `heartbeat skipped` with `reason=dm-blocked` → heartbeat target resolved to a DM-style destination while `agents.defaults.heartbeat.directPolicy` (or per-agent override) is set to `block`.

View File

@@ -67,7 +67,7 @@ and troubleshooting see the main [FAQ](/help/faq).
Common heartbeat skip reasons:
- `quiet-hours`: outside the configured active-hours window
- `empty-heartbeat-file`: `HEARTBEAT.md` exists but only contains blank/header-only scaffolding
- `empty-heartbeat-file`: `HEARTBEAT.md` exists but only contains blank, comment, header, fence, or empty-checklist scaffolding
- `no-tasks-due`: `HEARTBEAT.md` task mode is active but none of the task intervals are due yet
- `alerts-disabled`: all heartbeat visibility is disabled (`showOk`, `showAlerts`, and `useIndicator` are all off)

View File

@@ -1350,8 +1350,9 @@ lives on the [First-run FAQ](/help/faq-first-run).
}
```
If `HEARTBEAT.md` exists but is effectively empty (only blank lines and markdown
headers like `# Heading`), OpenClaw skips the heartbeat run to save API calls.
If `HEARTBEAT.md` exists but is effectively empty (only blank lines,
Markdown/HTML comments, Markdown headings like `# Heading`, fence markers,
or empty checklist stubs), OpenClaw skips the heartbeat run to save API calls.
If the file is missing, the heartbeat still runs and the model decides what to do.
Per-agent overrides use `agents.list[].heartbeat`. Docs: [Heartbeat](/gateway/heartbeat).

View File

@@ -364,7 +364,7 @@ flowchart TD
- `cron: scheduler disabled; jobs will not run automatically` → cron is disabled.
- `heartbeat skipped` with `reason=quiet-hours` → outside configured active hours.
- `heartbeat skipped` with `reason=empty-heartbeat-file` → `HEARTBEAT.md` exists but only contains blank/header-only scaffolding.
- `heartbeat skipped` with `reason=empty-heartbeat-file` → `HEARTBEAT.md` exists but only contains blank, comment, header, fence, or empty-checklist scaffolding.
- `heartbeat skipped` with `reason=no-tasks-due` → `HEARTBEAT.md` task mode is active but none of the task intervals are due yet.
- `heartbeat skipped` with `reason=alerts-disabled` → all heartbeat visibility is disabled (`showOk`, `showAlerts`, and `useIndicator` are all off).
- `requests-in-flight` → main lane busy; heartbeat wake was deferred.

View File

@@ -0,0 +1,254 @@
---
title: "LTS Deterministic Checklist Plan"
summary: "Plan for turning maturity scorecard claims into auditable release-support evidence packets"
read_when:
- Designing deterministic LTS evidence packets from scorecard inputs
- Auditing CLI support claims before an LTS or stable release
- Reusing the checklist workflow for another OpenClaw surface
---
# LTS Deterministic Checklist Plan
This plan turns the current Clanker-generated maturity scorecard into an auditable release-support artifact. The goal is not to replace Clanker judgment. The goal is to force every proposed LTS claim into a repeatable evidence table that maintainers can inspect before an LTS or stable release.
## General Flow
The deterministic checklist answers one question for every proposed LTS feature:
> Can we prove this works today, and will CI or release validation catch it if it breaks?
Use this hierarchy:
```text
Surface
-> Category
-> Feature
-> Evidence: docs, source, tests, CI/Testbox proof, known gaps
```
The maturity scorecard already defines the surfaces and categories. `LTS.md` marks which categories are proposed for the initial LTS slice. The checklist should not decide LTS policy directly. It should expose which promises are strongly backed, partially backed, missing proof, or require owner judgment.
## Deterministic Checklist Artifact
For each surface, produce one Markdown evidence packet:
```text
Surface: <surface name>
Snapshot:
- Scorecard ref:
- LTS ref:
- OpenClaw source ref:
- gitcrawl freshness:
- discrawl freshness:
- CI/Testbox source:
Summary:
- Included LTS categories:
- Strongly covered categories:
- Partial categories:
- Missing-proof categories:
- Owner-decision categories:
Feature Checklist:
| Category | Feature | Docs | Source | Test or proof | Latest CI/Testbox | Verdict | Gap | Next action |
```
Use these verdicts:
- `covered`: docs, source, and integration/e2e/live proof exist, and latest CI/Testbox proof is known.
- `partial`: implementation exists, but proof is unit-only, stale, platform-limited, or not tied to the user path.
- `missing`: no credible runtime-flow proof was found.
- `owner`: evidence exists, but whether it belongs in LTS is a product/support decision.
Do not let unit tests alone mark a feature as `covered`. Unit tests can support the row, but LTS coverage should be based on integration, e2e, live, or real runtime-flow proof.
## Agent Orchestration Model
Use Clankers as evidence collectors and reviewers.
Recommended roles:
- `surface-auditor`: builds the first checklist for one surface.
- `skeptic-reviewer`: attacks overclaims and downgrades weak rows.
- `ci-finder`: finds latest CI/Testbox proof for cited tests.
- `normalizer`: rewrites rows into the shared verdict vocabulary.
For high-risk surfaces, run two independent `surface-auditor` agents and compare disagreement. The useful output is often the conflict list, not the average answer.
## Standard Surface Auditor Prompt
```text
Audit only the <SURFACE> surface for the proposed LTS checklist.
Inputs:
- LTS source: docs/kevinslin/maturity-scorecard/LTS.md
- Scorecard source: docs/kevinslin/maturity-scorecard/maturity-scorecard.md
- Surface report: docs/kevinslin/maturity-scorecard/inventory/<surface-id>/report.md
- Surface score source: docs/kevinslin/maturity-scorecard/inventory/<surface-id>/scores.yaml
- Category notes: docs/kevinslin/maturity-scorecard/inventory/<surface-id>/*.md
Task:
For every category included in LTS.md for this surface:
1. Extract the user-facing features.
2. Cite docs that promise or explain the feature.
3. Cite implementation source that owns the feature.
4. Cite integration, e2e, live, or runtime-flow tests.
5. Find latest CI/Testbox proof for the cited tests when available.
6. Mark verdict as covered, partial, missing, or owner.
7. Explain the gap and the next action.
Rules:
- Do not change LTS policy.
- Do not score by vibes.
- A row without source plus runtime-flow proof is not covered.
- Unit tests alone are supporting evidence only.
- Prefer exact file paths and line references.
- Keep the final output to the checklist table plus a short gaps summary.
```
## Standard Skeptic Prompt
```text
Review this <SURFACE> LTS checklist.
Find:
- rows that overclaim coverage
- rows where unit tests are being counted as coverage
- rows where docs/source/test do not prove the same user-facing feature
- stale or missing CI/Testbox proof
- vague feature names
- categories that should be marked owner instead of covered
Return only actionable corrections:
| Row | Problem | Required correction | Severity |
```
## CLI Pilot
The CLI is the best first pilot because it is bounded, enterprise-relevant, and easier to connect to docs, source, tests, and release proof than provider or channel surfaces.
The proposed initial LTS slice includes 6 of 8 CLI categories:
- CLI Setup
- Onboarding and Auth Setup
- Gateway Service Management
- CLI Observability
- Doctor
- Updates and Upgrades
The deferred categories are:
- Plugin and Channel Setup
- Windows and WSL2
The CLI pilot should prove whether the included categories are actually backed by deterministic evidence, and whether any deferred category is obviously stronger than an included category.
## CLI Inputs To Read
Read these first:
- `docs/kevinslin/maturity-scorecard/LTS.md`
- `docs/kevinslin/maturity-scorecard/maturity-scorecard.md`
- `docs/kevinslin/maturity-scorecard/inventory/cli-install-update-onboard-doctor/report.md`
- `docs/kevinslin/maturity-scorecard/inventory/cli-install-update-onboard-doctor/scores.yaml`
- `docs/kevinslin/maturity-scorecard/inventory/cli-install-update-onboard-doctor/*.md`
Then read the product docs that match the categories:
- `docs/cli/index.md`
- `docs/cli/onboard.md`
- `docs/cli/configure.md`
- `docs/cli/doctor.md`
- `docs/cli/gateway.md`
- `docs/cli/health.md`
- `docs/cli/logs.md`
- `docs/cli/models.md`
- `docs/start/wizard-cli-automation.md`
- `docs/start/wizard-cli-reference.md`
- `docs/reference/wizard.md`
Use docs only as claims. Every claim still needs source and runtime proof.
## CLI Evidence Search Strategy
For each included category, search source and tests by command name and user workflow.
Suggested source searches:
```bash
rg -n "openclaw (onboard|configure|doctor|gateway|health|logs|models|update)" src packages ui extensions scripts test
rg -n "doctor|onboard|configure|gateway service|service install|update channel|auth profile|model set" src packages test
rg -n "program\\.command|subcommand|Command|commander|cac|yargs|parse" src packages
```
Suggested test searches:
```bash
rg -n "doctor|onboard|configure|gateway service|update|auth profile|models" --glob '*.{test,e2e.test}.ts' src packages test
rg -n "openclaw doctor|openclaw onboard|openclaw gateway|openclaw update" test scripts docs
```
Suggested proof searches:
```bash
gh run list -R openclaw/openclaw --limit 30 --json databaseId,headSha,conclusion,status,displayTitle,createdAt,url
gh pr checks <PR-or-branch> -R openclaw/openclaw
```
If local CI proof is not enough, mark the row `partial` and recommend Crabbox/Testbox proof.
## CLI Feature Table Template
Use this table as the CLI pilot output:
```markdown
| Category | Feature | Docs | Source | Test or proof | Latest CI/Testbox | Verdict | Gap | Next action |
| -------------------------- | ------------------------------------------------------------ | ---------------------------------------- | ------ | ------------- | ----------------- | ------- | -------------------------------------------------------- | ------------------------------------ |
| CLI Setup | Package install exposes `openclaw` CLI | `docs/start/getting-started.md` | TBD | TBD | TBD | partial | Need current package install smoke proof | Find release CI or add package smoke |
| Onboarding and Auth Setup | `openclaw onboard` creates usable config/auth path | `docs/cli/onboard.md` | TBD | TBD | TBD | partial | Need non-interactive and interactive proof separated | Audit onboarding tests |
| Gateway Service Management | CLI can install/start/stop/status Gateway service | `docs/cli/gateway.md` | TBD | TBD | TBD | partial | Need Linux service proof if LTS includes Linux host path | Link service tests and Testbox run |
| CLI Observability | `openclaw health` and `openclaw logs` expose operator status | `docs/cli/health.md`, `docs/cli/logs.md` | TBD | TBD | TBD | partial | Need running Gateway proof | Audit RPC/CLI e2e |
| Doctor | `openclaw doctor --fix` repairs supported config/auth drift | `docs/cli/doctor.md` | TBD | TBD | TBD | partial | Need migration fixture coverage | Audit doctor tests |
| Updates and Upgrades | CLI supports supported update channel flow | TBD | TBD | TBD | TBD | partial | Need release/update smoke proof | Find release CI/Testbox run |
```
Replace `TBD` with exact evidence. Do not leave `TBD` in the final artifact.
## CLI Definition Of Done
The CLI pilot is done when:
- Every included CLI LTS category has at least one feature row.
- Every feature row has docs, source, test/proof, verdict, gap, and next action.
- Rows without integration/e2e/live/runtime-flow proof are marked `partial` or `missing`.
- Latest CI/Testbox evidence is linked when available.
- A skeptic review has downgraded overclaims.
- The final summary names the top 3 CLI gaps Kevin needs to know before LTS.
## Likely CLI Outcomes
Expected useful outputs:
- A short list of CLI categories that are safe to keep in LTS.
- A short list of CLI categories that need one targeted integration or package smoke test.
- A short list of CLI claims that should be narrowed before announcement.
- A reusable checklist template for the next surface.
The most valuable result is not a high score. The most valuable result is a clear distinction between:
- "covered by current release gates"
- "implemented but not release-gated"
- "documented but weakly tested"
- "needs owner decision before support promise"
## Suggested First Day Plan
1. Run one `surface-auditor` on CLI.
2. Run one `ci-finder` on the cited CLI tests and release checks.
3. Run one `skeptic-reviewer` on the completed table.
4. Normalize the verdicts.
5. Send Kevin a concise packet:
- CLI checklist
- top 3 gaps
- recommended test/proof additions
- whether the checklist format should be repeated for Gateway runtime next

View File

@@ -0,0 +1,66 @@
---
title: "CLI LTS Deterministic Checklist"
summary: "Evidence packet for proposed CLI categories in the initial OpenClaw LTS slice"
read_when:
- Auditing whether CLI setup, onboarding, gateway service management, observability, doctor, or updates are ready for an LTS support promise
- Preparing release validation gaps for CLI support claims
- Extending the deterministic LTS checklist to another surface
---
## Surface
CLI install, update, onboard, configure, gateway, health, logs, and doctor.
## Snapshot
- Scorecard ref: planned `docs/kevinslin/maturity-scorecard/maturity-scorecard.md`; not present in this checkout.
- LTS ref: planned `docs/kevinslin/maturity-scorecard/LTS.md`; not present in this checkout.
- OpenClaw source ref: this worktree, branch `lts-checklist-cli`.
- gitcrawl freshness: not checked; this packet uses local source plus live GitHub Actions run metadata.
- discrawl freshness: not checked; no Discord/operator archive claim is needed for the CLI source proof.
- CI/Testbox source: latest observed successful runs from `gh run list` on 2026-06-05:
- Full Release Validation: `27022847039`, `tideclaw/alpha/2026-06-05-1410Z`, `2e307827be8a7aa6fb8b70e2d5e639a6d86c7ddc`, https://github.com/openclaw/openclaw/actions/runs/27022847039
- OpenClaw Release Checks: `27023463705`, same ref/SHA, https://github.com/openclaw/openclaw/actions/runs/27023463705
- main CI: `27032770269`, `d896a4c7a3ef033f93bc6bd6d392e299630c52c7`, https://github.com/openclaw/openclaw/actions/runs/27032770269
- Package Acceptance: latest observed success `26917431094`, `main`, `308114e1486dc2a2409ab1d99a1e5f8e05d97b7e`, https://github.com/openclaw/openclaw/actions/runs/26917431094
## Summary
- Included LTS categories: CLI Setup; Onboarding and Auth Setup; Gateway Service Management; CLI Observability; Doctor; Updates and Upgrades.
- Strongly covered categories: none without a release-run manifest tying exact rows to exact lane success.
- Partial categories: all six included categories have docs, implementation, tests, and at least some Docker or release-gate proof.
- Missing-proof categories: CLI Setup lacks an isolated current package-install smoke row in this packet; CLI Observability lacks a named release lane for `openclaw health` plus `openclaw logs` together.
- Owner-decision categories: Gateway Service Management, because the support promise must name which supervisors and operating systems are in LTS scope.
## Feature Checklist
| Category | Feature | Docs | Source | Test or proof | Latest CI/Testbox | Verdict | Gap | Next action |
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| CLI Setup | Package install exposes `openclaw` and the documented command tree | `docs/cli/index.md:9`, `docs/cli/index.md:21`, `docs/cli/index.md:40` | `src/cli/program/build-program.ts:10`, `src/cli/program/build-program.ts:27`, `src/cli/program.ts:4` | Package install is exercised by `scripts/e2e/npm-onboard-channel-agent-docker.sh:134` and checks `command -v openclaw` at `scripts/e2e/npm-onboard-channel-agent-docker.sh:136`; launcher/package behavior has `test/openclaw-launcher.e2e.test.ts` | Package Acceptance `26917431094` is the latest successful package workflow observed; Release Checks `27023463705` is newer but this packet did not fetch its job manifest to prove the install lane ran. | partial | The proof is embedded in larger package/onboard lanes, not a row-level install smoke tied to the latest release run. | Add or identify a release-gated `install-smoke` row that records package spec, `openclaw --version`, command tree smoke, and runner OS. |
| Onboarding and Auth Setup | `openclaw onboard` creates usable local/remote config, auth refs, workspace, and health path | `docs/cli/onboard.md:8`, `docs/cli/onboard.md:121`, `docs/cli/onboard.md:139`, `docs/start/wizard-cli-automation.md:1`, `docs/reference/wizard.md:1` | `src/commands/onboard.ts:27`, `src/commands/onboard.ts:77`, `src/commands/onboard.ts:113`, `src/commands/onboard.ts:118` | Unit/integration: `src/commands/onboard-non-interactive.gateway.test.ts:330`, `src/commands/onboard-non-interactive.gateway.test.ts:764`, `src/commands/onboard-non-interactive.gateway-health-auth.test.ts:40`; Docker flow: `scripts/e2e/onboard-docker.sh:24`, local and remote cases at `scripts/e2e/lib/onboard/scenario.sh:244`, `scripts/e2e/lib/onboard/scenario.sh:272`; package user path at `scripts/e2e/npm-onboard-channel-agent-docker.sh:149` | Package Acceptance `26917431094` covers `npm-onboard-channel-agent` by policy (`docs/ci.md:292`); OpenClaw Release Checks `27023463705` is newer and calls package lanes by policy (`docs/ci.md:306`). | partial | Onboarding has strong runtime proof, but the row still mixes local setup, remote setup, auth SecretRefs, and package channel-agent proof. | Split into local non-interactive, remote non-interactive, interactive wizard, and SecretRef auth rows, then attach latest release job names/artifacts for each. |
| Gateway Service Management | CLI can install, start, stop, restart, and report Gateway service state | `docs/cli/gateway.md:113`, `docs/cli/gateway.md:139`, `docs/cli/gateway.md:164`; legacy alias listed at `docs/cli/index.md:37` | `src/cli/gateway-cli/register.ts:20`, `src/cli/gateway-cli/register.ts:479`, `src/cli/daemon-cli/register-service-commands.ts:57`, `src/cli/daemon-cli/register-service-commands.ts:83`, `src/cli/daemon-cli/register-service-commands.ts:106`, `src/cli/daemon-cli/register-service-commands.ts:115`, `src/cli/daemon-cli/register-service-commands.ts:129` | Unit/integration: `src/commands/gateway-readiness.test.ts:92`, `src/commands/doctor-gateway-services.test.ts:351`, `src/cli/daemon-cli/register-service-commands.test.ts:1`; Docker service-entrypoint proof: `scripts/e2e/doctor-install-switch-docker.sh:1`, `scripts/e2e/lib/doctor-install-switch/scenario.sh:122`, `scripts/e2e/lib/doctor-install-switch/scenario.sh:160`, `scripts/e2e/lib/doctor-install-switch/scenario.sh:167` | Release Checks `27023463705` is the latest successful release-check run observed; docs state release checks include `doctor-switch` in package acceptance (`docs/ci.md:306`). | owner | The Linux systemd-user Docker shim proves important flows, but LTS support scope for launchd, systemd, Scheduled Tasks, WSL2, and unmanaged supervisors is a product decision. | Define which supervisors are LTS-backed, then require one release-gated service lifecycle lane per supported supervisor. |
| CLI Observability | `openclaw health` and `openclaw logs` expose operator status and logs through Gateway RPC with fallbacks | `docs/cli/health.md:8`, `docs/cli/health.md:31`, `docs/cli/logs.md:9`, `docs/cli/logs.md:58` | `src/commands/health.ts:411`, `src/commands/health.ts:634`, `src/commands/health.ts:658`, `src/cli/logs-cli.ts:100`, `src/cli/logs-cli.ts:119`, `src/cli/logs-cli.ts:146`, `src/cli/logs-cli.ts:483`, `src/gateway/server-methods/logs.ts:12`, `src/gateway/server-methods/health.ts:121` | Unit/integration: `src/commands/health.test.ts:127`, `src/commands/health.test.ts:251`, `src/cli/logs-cli.test.ts:138`, `src/cli/logs-cli.test.ts:416`, `src/cli/logs-cli.test.ts:534`; package/onboard lane checks status surfaces at `scripts/e2e/npm-onboard-channel-agent-docker.sh:170` | main CI `27032770269` is current general CI proof; no latest named release lane was found for `openclaw health` plus `openclaw logs`. | partial | Health and logs are implemented and tested, but logs proof is mostly unit-level; package runtime proof checks status surfaces, not logs tail/follow against a running Gateway. | Add a Docker release-path observability lane that starts Gateway, asserts `openclaw health --json`, `openclaw logs --json`, and `openclaw logs --follow` reconnect behavior. |
| Doctor | `openclaw doctor --fix` repairs supported config, auth, plugin, state, and service drift | `docs/cli/doctor.md:20`, `docs/cli/doctor.md:25`, `docs/cli/doctor.md:61`, `docs/cli/doctor.md:79` | `src/commands/doctor.ts:7`, `src/commands/doctor.ts:25`, `src/flows/doctor-health.ts:20`, `src/flows/doctor-health.ts:63`, `src/flows/doctor-health.ts:81` | Unit/integration: `src/commands/doctor-config-preflight.test.ts:30`, `src/commands/doctor-config-preflight.state-migration.test.ts:49`, `src/commands/doctor-auth-flat-profiles.test.ts:415`, `src/commands/doctor-gateway-daemon-flow.test.ts:503`, `src/commands/doctor-gateway-services.test.ts:480`; Docker package repair proof: `scripts/e2e/lib/doctor-install-switch/scenario.sh:152`, `scripts/e2e/lib/doctor-install-switch/scenario.sh:201`, `scripts/e2e/npm-onboard-channel-agent-docker.sh:175` | Package Acceptance `26917431094`; Release Checks `27023463705` is newer and release policy includes `doctor-switch`, `update-corrupt-plugin`, `upgrade-survivor`, `published-upgrade-survivor`, and `update-restart-auth` (`docs/ci.md:306`). | partial | Doctor is broad; no single artifact summarizes which repair families are release-gated versus unit-only. | Generate a doctor repair matrix from check IDs/repair families and map each to unit, Docker, Package Acceptance, or missing proof. |
| Updates and Upgrades | `openclaw update` supports stable/beta/dev channel switching, package updates, post-core convergence, and update status | `docs/cli/update.md:10`, `docs/cli/update.md:34`, `docs/cli/update.md:89`, `docs/cli/update.md:121`, `docs/cli/update.md:168` | `src/cli/update-cli.ts:41`, `src/cli/update-cli.ts:45`, `src/cli/update-cli.ts:50`, `src/cli/update-cli.ts:101`, `src/cli/update-cli.ts:163`; update implementation is under `src/cli/update-cli/*` and `src/infra/update-*` | Unit/integration: `src/cli/update-cli.test.ts:3307`, `src/cli/update-cli.test.ts:3971`, `src/infra/update-runner.test.ts:2272`; Docker runtime proof: `scripts/e2e/update-channel-switch-docker.sh:1`, package-to-git and git-to-package assertions at `scripts/e2e/update-channel-switch-docker.sh:100`, `scripts/e2e/update-channel-switch-docker.sh:116`; upgrade survivor proof under `scripts/e2e/lib/upgrade-survivor/run.sh:1113` and `scripts/e2e/lib/upgrade-survivor/run.sh:1241` | Release Checks `27023463705`; Package Acceptance policy includes `update-channel-switch`, `upgrade-survivor`, `published-upgrade-survivor`, `update-restart-auth`, and plugin update lanes (`docs/ci.md:294`, `docs/ci.md:306`). | partial | This is close to covered for package/update flows, but row-level proof still lacks latest job artifact/job names and exact package spec. | Pull the latest Release Checks child Package Acceptance job summary and record lane-level outcomes for `update-channel-switch`, `upgrade-survivor`, `published-upgrade-survivor`, and `update-restart-auth`. |
## Deferred Category Check
- Plugin and Channel Setup appears at least as strongly release-gated as some included categories: Package Acceptance `smoke` includes `npm-onboard-channel-agent`, and package profile includes `skill-install`, `plugins-offline`, and `plugin-update` (`docs/ci.md:292`). If CLI LTS promises include post-onboard channel setup, this category may deserve inclusion or a narrow split.
- Windows and WSL2 have release-check hooks through cross-OS release checks (`.github/workflows/openclaw-release-checks.yml:24`, `.github/workflows/openclaw-release-checks.yml:66`) and Windows/package notes in `docs/ci.md:306`, but this packet did not audit Windows-specific source/tests. Keep deferred until a dedicated cross-OS evidence packet exists.
## Skeptic Review Corrections
| Row | Problem | Required correction | Severity |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------- | -------- |
| CLI Setup | Package install proof is only inferred from package/onboard lanes. | Keep `partial` until latest release job artifacts show an install smoke or add a standalone install smoke. | Medium |
| Onboarding and Auth Setup | Combines too many behaviors in one row; SecretRef and interactive wizard could regress independently. | Split local non-interactive, remote non-interactive, interactive, and SecretRef rows. | Medium |
| Gateway Service Management | Current strongest Docker proof uses systemd shims; launchd and Scheduled Tasks support are not equivalently proven here. | Mark `owner`; define OS/supervisor LTS scope before upgrade to `covered`. | High |
| CLI Observability | Unit proof is being asked to carry logs tail/follow support. | Add runtime Gateway logs proof or keep `partial`. | High |
| Doctor | Doctor has many repair families; a single row can hide unproven repairs. | Matrix repair families and mark each separately. | Medium |
| Updates and Upgrades | Release policy strongly covers this area, but latest lane-level artifacts were not inspected. | Fetch latest Release Checks child job summary before calling it `covered`. | Medium |
## Top Gaps For LTS
1. **Lane-level release evidence**: the current release workflows cover many CLI user paths, but the LTS artifact needs child job IDs, lane names, package specs, and artifacts for each row.
2. **Observability runtime proof**: add a Gateway-backed Docker lane for `health` and `logs`, especially `logs --follow` reconnect/journal behavior.
3. **Service support boundary**: decide whether LTS means Linux systemd user only, macOS launchd, native Windows Scheduled Tasks, WSL2, or all of them, then require one real runtime proof per supported supervisor.

View File

@@ -120,11 +120,10 @@ Use [`defineToolPlugin`](/plugins/tool-plugins) for simple tool-only plugins
with fixed tool names. Use `api.registerTool(...)` directly for mixed plugins
or fully dynamic tool registration.
| Method | What it registers |
| -------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| `api.registerTool(tool, opts?)` | Agent tool (required or `{ optional: true }`) |
| `api.registerCommand(def)` | Custom command (bypasses the LLM) |
| `api.registerNodeHostCommand(command)` | Command handled by `openclaw node run`; optional `agentTool` metadata can expose it as an agent-visible tool while the node is connected |
| Method | What it registers |
| ------------------------------- | --------------------------------------------- |
| `api.registerTool(tool, opts?)` | Agent tool (required or `{ optional: true }`) |
| `api.registerCommand(def)` | Custom command (bypasses the LLM) |
Plugin commands can set `agentPromptGuidance` when the agent needs a short,
command-owned routing hint. Keep that text about the command itself; do not add
@@ -151,19 +150,6 @@ surfaces: only guidance explicitly scoped to `codex_app_server` is promoted into
that higher-priority lane. Legacy string guidance and unscoped structured
guidance remain available to non-Codex prompt surfaces for compatibility.
Node-host commands run on the connected node host, not inside the Gateway
process. If `agentTool` is present, the node publishes a descriptor after a
successful Gateway connect; the Gateway exposes it to agent runs only while that
node is connected and only if the descriptor's `command` is in the node's
approved command surface. Set `agentTool.defaultPlatforms` to opt a
non-dangerous command into the default node command allowlist; otherwise require
explicit `gateway.nodes.allowCommands` or a node-invoke policy. `agentTool.name`
must be provider-safe: start with a letter, use only letters, digits,
underscores, or hyphens, and stay within 64 characters. MCP-backed node tools
can set `agentTool.mcp` metadata so catalog and tool-search surfaces can show
the remote MCP server/tool identity, but execution still goes through the
advertised node command.
### Infrastructure
| Method | What it registers |

View File

@@ -251,15 +251,9 @@ two-party event loops that do not go through the shared inbound reply runner.
});
```
`nodes.list(...)` includes each connected node's advertised
`nodePluginTools` descriptors when that node exposes plugin or MCP-backed
tools to the agent. Those descriptors are live connection state: the Gateway
drops them when the node disconnects, and a node can replace them with
`node.pluginTools.update` after local plugin/MCP inventory changes.
Inside the Gateway this runtime is in-process. In plugin CLI commands it calls the configured Gateway over RPC, so commands such as `openclaw googlemeet recover-tab` can inspect paired nodes from the terminal. Node commands still go through normal Gateway node pairing, command allowlists, plugin node-invoke policies, and node-local command handling.
Plugins that expose node-hosted agent tools can set `agentTool.defaultPlatforms` for non-dangerous commands that should be allowlisted by default. Omit it when operators must opt in with `gateway.nodes.allowCommands`. Dangerous node-host commands should register a node-invoke policy with `api.registerNodeInvokePolicy(...)`; the policy runs in the Gateway after command allowlist checks and before the command is forwarded to the node, so direct `node.invoke` calls, node-hosted plugin tools, and higher-level plugin tools share the same enforcement path.
Plugins that expose dangerous node-host commands should register a node-invoke policy with `api.registerNodeInvokePolicy(...)`. The policy runs in the Gateway after command allowlist checks and before the command is forwarded to the node, so direct `node.invoke` calls and higher-level plugin tools share the same enforcement path.
</Accordion>
<Accordion title="api.runtime.tasks.managedFlows">

View File

@@ -467,6 +467,7 @@ Set `memory.backend = "qmd"` to enable. All QMD settings live under `memory.qmd`
| ------------------------ | --------- | -------- | ------------------------------------------------------------------------------------- |
| `command` | `string` | `qmd` | QMD executable path; set an absolute path when service `PATH` differs from your shell |
| `searchMode` | `string` | `search` | Search command: `search`, `vsearch`, `query` |
| `rerank` | `boolean` | -- | Set to `false` with `searchMode: "query"` and QMD 2.1+ to skip QMD reranking |
| `includeDefaultMemory` | `boolean` | `true` | Auto-index `MEMORY.md` + `memory/**/*.md` |
| `paths[]` | `array` | -- | Extra paths: `{ name, path, pattern? }` |
| `sessions.enabled` | `boolean` | `false` | Index session transcripts |
@@ -475,6 +476,8 @@ Set `memory.backend = "qmd"` to enable. All QMD settings live under `memory.qmd`
`searchMode: "search"` is lexical/BM25-only. OpenClaw does not run semantic vector readiness probes or QMD embedding maintenance for that mode, including during `memory status --deep`; `vsearch` and `query` continue to require QMD vector readiness and embeddings.
`rerank: false` only changes QMD `query` mode and requires QMD 2.1 or newer. In direct CLI mode OpenClaw passes `--no-rerank`; in mcporter-backed MCP mode it passes `rerank: false` to QMD's unified query tool. Leave it unset to use QMD's default query reranking behavior.
OpenClaw prefers current QMD collection and MCP query shapes, but keeps older QMD releases working by trying compatible collection pattern flags and older MCP tool names when needed. When QMD advertises support for multiple collection filters, same-source collections are searched with one QMD process; older QMD builds keep the per-collection compatibility path. Same-source means durable memory collections are grouped together, while session transcript collections remain a separate group so source diversification still has both inputs.
<Note>

View File

@@ -172,7 +172,7 @@ By default, OpenClaw runs a heartbeat every 30 minutes with the prompt:
`Read HEARTBEAT.md if it exists (workspace context). Follow it strictly. Do not infer or repeat old tasks from prior chats. If nothing needs attention, reply HEARTBEAT_OK.`
Set `agents.defaults.heartbeat.every: "0m"` to disable.
- If `HEARTBEAT.md` exists but is effectively empty (only blank lines and markdown headers like `# Heading`), OpenClaw skips the heartbeat run to save API calls.
- If `HEARTBEAT.md` exists but is effectively empty (only blank lines, Markdown/HTML comments, Markdown headings like `# Heading`, fence markers, or empty checklist stubs), OpenClaw skips the heartbeat run to save API calls.
- If the file is missing, the heartbeat still runs and the model decides what to do.
- If the agent replies with `HEARTBEAT_OK` (optionally with short padding; see `agents.defaults.heartbeat.ackMaxChars`), OpenClaw suppresses outbound delivery for that heartbeat.
- By default, heartbeat delivery to DM-style `user:<id>` targets is allowed. Set `agents.defaults.heartbeat.directPolicy: "block"` to suppress direct-target delivery while keeping heartbeat runs active.

View File

@@ -287,6 +287,9 @@ Generic model:
- Slack plugin approvals can use Slack's native approval client when the request comes from Slack
and Slack plugin approvers resolve; `approvals.plugin` can also route plugin approvals to Slack
sessions or targets even when Slack exec approvals are disabled
- Google Chat native approval cards handle exec and plugin approvals that originate from Google
Chat spaces or threads when stable `users/<id>` approvers resolve from `dm.allowFrom` or
`defaultTo`; they do not use reaction events for decisions
- WhatsApp and Signal reaction approval delivery are gated by `approvals.exec` and
`approvals.plugin`; they do not have `channels.<channel>.execApprovals` blocks
@@ -306,6 +309,8 @@ FAQ: [Why are there two exec approval configs for chat approvals?](/help/faq-fir
- Discord: `channels.discord.execApprovals.*`
- Slack: `channels.slack.execApprovals.*`
- Telegram: `channels.telegram.execApprovals.*`
- Google Chat: configure stable approvers with `channels.googlechat.dm.allowFrom` or
`channels.googlechat.defaultTo`; no `execApprovals` block is required
- WhatsApp: use `approvals.exec` and `approvals.plugin` to route approval prompts to WhatsApp
- Signal: use `approvals.exec` and `approvals.plugin` to route approval prompts to Signal
@@ -325,6 +330,9 @@ Shared behavior:
routing, not Slack exec approvers
- Slack native buttons preserve approval id kind, so `plugin:` ids can resolve plugin approvals
without a second Slack-local fallback layer
- Google Chat native cards preserve the manual `/approve` fallback in message text but card button
callbacks carry only opaque action tokens; approval id and decision are recovered from server-side
pending state
- WhatsApp emoji approvals handle both exec and plugin prompts only when the matching top-level
forwarding family is enabled and routes to WhatsApp; target-only WhatsApp forwarding stays on
the shared forwarding path unless it matches the same native origin target

View File

@@ -1,3 +1,4 @@
// ACPX tests cover index plugin behavior.
import type { OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry";
import { createTestPluginApi } from "openclaw/plugin-sdk/plugin-test-api";
import { beforeEach, describe, expect, it, vi } from "vitest";

View File

@@ -1,3 +1,4 @@
// ACPX tests cover register plugin behavior.
import { afterEach, describe, expect, it, vi } from "vitest";
const { runtimeRegistry } = vi.hoisted(() => ({

View File

@@ -1,3 +1,4 @@
// ACPX tests cover claude agent acp completion plugin behavior.
import { ClaudeAcpAgent } from "@agentclientprotocol/claude-agent-acp";
import { describe, expect, it, vi } from "vitest";

View File

@@ -1,3 +1,4 @@
// ACPX tests cover codex auth bridge plugin behavior.
import { execFile } from "node:child_process";
import fs from "node:fs/promises";
import os from "node:os";

View File

@@ -1,3 +1,4 @@
// ACPX tests cover config plugin behavior.
import fs from "node:fs";
import { createRequire } from "node:module";
import path from "node:path";

View File

@@ -1,3 +1,4 @@
// ACPX tests cover manifest plugin behavior.
import fs from "node:fs";
import { describe, expect, it } from "vitest";

View File

@@ -1,3 +1,4 @@
// ACPX tests cover process lease plugin behavior.
import { mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";

View File

@@ -1,3 +1,4 @@
// ACPX tests cover process reaper plugin behavior.
import path from "node:path";
import { describe, expect, it, vi } from "vitest";
import { OPENCLAW_ACPX_LEASE_ID_ARG, OPENCLAW_GATEWAY_INSTANCE_ID_ARG } from "./process-lease.js";

View File

@@ -1,3 +1,4 @@
// ACPX tests cover mcp command line plugin behavior.
import { describe, expect, it } from "vitest";
type SplitCommandLine = (

View File

@@ -1,3 +1,4 @@
// ACPX tests cover mcp proxy plugin behavior.
import { spawn } from "node:child_process";
import { chmod, mkdtemp, rm, writeFile } from "node:fs/promises";
import os from "node:os";

View File

@@ -1,3 +1,4 @@
// ACPX tests cover runtime plugin behavior.
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";

View File

@@ -1,3 +1,4 @@
// ACPX tests cover service plugin behavior.
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";

View File

@@ -1,3 +1,4 @@
// Active Memory tests cover config plugin behavior.
import fs from "node:fs";
import {
type JsonSchemaObject,

View File

@@ -1,3 +1,4 @@
// Active Memory tests cover doctor contract api plugin behavior.
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";

View File

@@ -1,3 +1,4 @@
// Active Memory tests cover index plugin behavior.
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";

View File

@@ -1,3 +1,4 @@
// Admin Http Rpc tests cover index plugin behavior.
import { describe, expect, it } from "vitest";
import plugin from "./index.js";
import manifest from "./openclaw.plugin.json" with { type: "json" };

View File

@@ -1,3 +1,4 @@
// Admin Http Rpc tests cover handler plugin behavior.
import { Readable } from "node:stream";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { handleAdminHttpRpcRequest } from "./handler.js";

View File

@@ -1,3 +1,4 @@
// Alibaba tests cover video generation provider plugin behavior.
import {
getProviderHttpMocks,
installProviderHttpMockCleanup,

View File

@@ -1,3 +1,4 @@
// Amazon Bedrock Mantle tests cover discovery plugin behavior.
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
const {

View File

@@ -1,3 +1,4 @@
// Amazon Bedrock Mantle tests cover index plugin behavior.
import { registerSingleProviderPlugin } from "openclaw/plugin-sdk/plugin-test-runtime";
import { beforeEach, describe, expect, it, vi } from "vitest";
import bedrockMantlePlugin from "./index.js";

View File

@@ -1,3 +1,4 @@
// Amazon Bedrock Mantle tests cover mantle anthropic plugin behavior.
import type { Model } from "openclaw/plugin-sdk/llm";
import { describe, expect, it, vi } from "vitest";
import {

View File

@@ -1,3 +1,4 @@
// Amazon Bedrock tests cover config compat plugin behavior.
import { describe, expect, it } from "vitest";
import { migrateAmazonBedrockLegacyConfig } from "./config-compat.js";

View File

@@ -1,3 +1,4 @@
// Amazon Bedrock tests cover discovery plugin behavior.
import type { BedrockClient } from "@aws-sdk/client-bedrock";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import {

View File

@@ -1,3 +1,4 @@
// Amazon Bedrock tests cover embedding provider plugin behavior.
import { describe, expect, it, vi } from "vitest";
import { testing, hasAwsCredentials } from "./embedding-provider.js";

View File

@@ -1,3 +1,4 @@
// Amazon Bedrock tests cover index plugin behavior.
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";

View File

@@ -1,3 +1,4 @@
// Amazon Bedrock tests cover lazy import plugin behavior.
import { registerSingleProviderPlugin } from "openclaw/plugin-sdk/plugin-test-runtime";
import { afterEach, describe, expect, it, vi } from "vitest";

View File

@@ -1,3 +1,4 @@
// Amazon Bedrock tests cover memory embedding adapter plugin behavior.
import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from "vitest";
const hasAwsCredentialsMock = vi.hoisted(() => vi.fn());

View File

@@ -1,3 +1,4 @@
// Amazon Bedrock tests cover provider policy api plugin behavior.
import { describe, expect, it } from "vitest";
import { resolveThinkingProfile } from "./provider-policy-api.js";

View File

@@ -1,3 +1,4 @@
// Amazon Bedrock tests cover stream plugin behavior.
import { describe, expect, it } from "vitest";
import { testing } from "./stream.runtime.js";

View File

@@ -1,3 +1,4 @@
// Anthropic Vertex tests cover api plugin behavior.
import { createAssistantMessageEventStream, type Model } from "openclaw/plugin-sdk/llm";
import { beforeAll, describe, expect, it, vi } from "vitest";
import type { AnthropicVertexStreamDeps } from "./stream-runtime.js";

View File

@@ -1,3 +1,4 @@
// Anthropic Vertex tests cover index plugin behavior.
import { registerSingleProviderPlugin } from "openclaw/plugin-sdk/plugin-test-runtime";
import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from "vitest";

View File

@@ -1,3 +1,4 @@
// Anthropic Vertex tests cover provider discovery.import guard plugin behavior.
import { describe, expect, it } from "vitest";
describe("anthropic-vertex provider discovery entry", () => {

View File

@@ -1,3 +1,4 @@
// Anthropic Vertex tests cover provider policy api plugin behavior.
import { describe, expect, it } from "vitest";
import { resolveThinkingProfile } from "./provider-policy-api.js";

View File

@@ -1,3 +1,4 @@
// Anthropic Vertex tests cover region.adc plugin behavior.
import { platform } from "node:os";
import path from "node:path";
import { afterAll, afterEach, describe, expect, it, vi } from "vitest";

View File

@@ -1,3 +1,4 @@
// Anthropic Vertex tests cover region plugin behavior.
import { describe, expect, it } from "vitest";
import { resolveAnthropicVertexRegion, resolveAnthropicVertexRegionFromBaseUrl } from "./api.js";

View File

@@ -1,3 +1,4 @@
// Anthropic Vertex tests cover stream runtime plugin behavior.
import { createAssistantMessageEventStream, type Model } from "openclaw/plugin-sdk/llm";
import { beforeAll, describe, expect, it, vi } from "vitest";
import type { AnthropicVertexStreamDeps } from "./stream-runtime.js";

View File

@@ -1,3 +1,4 @@
// Anthropic tests cover cli migration plugin behavior.
import type {
ProviderAuthContext,
ProviderAuthMethodNonInteractiveContext,

View File

@@ -1,3 +1,4 @@
// Anthropic tests cover cli shared plugin behavior.
import { describe, expect, it } from "vitest";
import { buildAnthropicCliBackend } from "./cli-backend.js";
import {

View File

@@ -1,3 +1,4 @@
// Anthropic tests cover index plugin behavior.
import type {
ProviderResolveDynamicModelContext,
ProviderRuntimeModel,

View File

@@ -1,3 +1,4 @@
// Anthropic tests cover provider policy api plugin behavior.
import type { ModelDefinitionConfig } from "openclaw/plugin-sdk/provider-model-types";
import { describe, expect, it } from "vitest";
import {

View File

@@ -1,3 +1,4 @@
// Anthropic tests cover provider runtime.contract plugin behavior.
import { describeAnthropicProviderRuntimeContract } from "openclaw/plugin-sdk/provider-test-contracts";
describeAnthropicProviderRuntimeContract(() => import("./index.js"));

View File

@@ -1,3 +1,4 @@
// Anthropic tests cover stream wrappers plugin behavior.
import type { StreamFn } from "openclaw/plugin-sdk/agent-core";
import { afterEach, describe, expect, it, vi } from "vitest";
import {

View File

@@ -1,3 +1,4 @@
// Arcee tests cover index plugin behavior.
import {
registerSingleProviderPlugin,
resolveProviderPluginChoice,

View File

@@ -1,3 +1,4 @@
// Azure Speech tests cover azure speech plugin behavior.
import {
registerProviderPlugin,
requireRegisteredProvider,

View File

@@ -1,3 +1,4 @@
// Azure Speech tests cover speech provider plugin behavior.
import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from "vitest";
const { azureSpeechTTSMock, listAzureSpeechVoicesMock } = vi.hoisted(() => ({

View File

@@ -1,3 +1,4 @@
// Azure Speech tests cover tts plugin behavior.
import { installPinnedHostnameTestHooks } from "openclaw/plugin-sdk/test-env";
import { afterEach, describe, expect, it, vi } from "vitest";
import {

View File

@@ -1,3 +1,4 @@
// Bonjour tests cover index plugin behavior.
import { createTestPluginApi } from "openclaw/plugin-sdk/plugin-test-api";
import { afterAll, describe, expect, it, vi } from "vitest";

View File

@@ -1,3 +1,4 @@
// Bonjour tests cover manifest plugin behavior.
import fs from "node:fs";
import { describe, expect, it } from "vitest";

View File

@@ -1,3 +1,4 @@
// Bonjour tests cover advertiser plugin behavior.
import type { ChildProcess } from "node:child_process";
import fs from "node:fs";
import { createRequire } from "node:module";

View File

@@ -1,3 +1,4 @@
// Bonjour tests cover ciao plugin behavior.
import { describe, expect, it } from "vitest";
const { classifyCiaoUnhandledRejection, ignoreCiaoUnhandledRejection } = await import("./ciao.js");

View File

@@ -56,7 +56,11 @@ export function classifyCiaoProcessError(reason: unknown): CiaoProcessErrorClass
return null;
}
/** Alternate export name for unhandled-rejection classification. */
/**
* Backward-compatible alias for unhandled-rejection classification.
*
* @deprecated Use classifyCiaoProcessError.
*/
export const classifyCiaoUnhandledRejection = classifyCiaoProcessError;
/** Return whether a ciao unhandled rejection is known and ignorable. */

View File

@@ -1,3 +1,4 @@
// Bonjour tests cover errors plugin behavior.
import { describe, expect, it } from "vitest";
import { formatBonjourError } from "./errors.js";

View File

@@ -1,3 +1,4 @@
// Brave tests cover brave web search provider.merge plugin behavior.
import { describe, expect, it, vi } from "vitest";
import { createBraveWebSearchProvider } from "./brave-web-search-provider.js";

View File

@@ -1,3 +1,4 @@
// Brave tests cover brave web search provider plugin behavior.
import fs from "node:fs";
import { validateJsonSchemaValue } from "openclaw/plugin-sdk/json-schema-runtime";
import { afterAll, afterEach, describe, expect, it, vi } from "vitest";

View File

@@ -1,3 +1,4 @@
// Browser tests cover index plugin behavior.
import fs from "node:fs";
import path from "node:path";
import { createTestPluginApi } from "openclaw/plugin-sdk/plugin-test-api";

View File

@@ -1,3 +1,4 @@
// Browser tests cover browser tool.schema plugin behavior.
import { describe, expect, it } from "vitest";
import { BrowserToolSchema } from "./browser-tool.schema.js";
import { ACT_MAX_VIEWPORT_DIMENSION } from "./browser/act-policy.js";

View File

@@ -1,3 +1,4 @@
// Browser tests cover browser tool plugin behavior.
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
const browserClientMocks = vi.hoisted(() => ({

View File

@@ -1,3 +1,4 @@
// Browser tests cover bridge server.auth plugin behavior.
import { afterEach, describe, expect, it } from "vitest";
import { startBrowserBridgeServer, stopBrowserBridgeServer } from "./bridge-server.js";
import type { ResolvedBrowserConfig } from "./config.js";

View File

@@ -1,3 +1,4 @@
// Browser tests cover browser proxy mode plugin behavior.
import { describe, expect, it } from "vitest";
import {
hasChromeProxyControlArg,

View File

@@ -1,3 +1,4 @@
// Browser tests cover browser utils plugin behavior.
import { describe, expect, it, vi } from "vitest";
import {
appendCdpPath,

View File

@@ -1,3 +1,4 @@
// Browser tests cover cdp proxy bypass plugin behavior.
import http from "node:http";
import https from "node:https";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";

View File

@@ -1,3 +1,4 @@
// Browser tests cover cdp.helpers.fuzz plugin behavior.
import { describe, expect, it } from "vitest";
import {
appendCdpPath,

View File

@@ -1,3 +1,4 @@
// Browser tests cover cdp.helpers.internal plugin behavior.
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { WebSocketServer } from "ws";
import { rawDataToString } from "../infra/ws.js";

View File

@@ -1,3 +1,4 @@
// Browser tests cover cdp.helpers plugin behavior.
import { MAX_TIMER_TIMEOUT_MS } from "openclaw/plugin-sdk/number-runtime";
import { afterEach, describe, expect, it, vi } from "vitest";
import { resolveCdpReachabilityPolicy } from "./cdp-reachability-policy.js";

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