Compare commits

..

484 Commits

Author SHA1 Message Date
jesse-merhi
71c9d2f1db feat(signal): quote native replies 2026-06-22 19:18:37 +10:00
Vincent Koc
95093303c8 chore(deadcode): remove test-only channel helpers 2026-06-22 16:57:03 +08:00
Vincent Koc
607b2e9663 fix(ci): debounce canonical main runner admission (#95681)
Compacts canonical pull request CI to 18 bounded Node jobs, preserves isolated subprocess execution, and delays canonical main runner admission to smooth GitHub runner-registration bursts.

Verification: focused CI planner/workflow tests passed; fresh autoreview clean. Hosted CI had two pre-existing runtime-config failures on the current main baseline; merged with explicit maintainer override.
2026-06-22 16:55:56 +08:00
Vincent Koc
aed6f0a14e test(doctor): mock external channel env vars 2026-06-22 16:50:58 +08:00
Vincent Koc
e20edd753b fix(canvas): guard A2UI asset copy roots 2026-06-22 16:43:08 +08:00
Vincent Koc
a89e65c167 fix(canvas): remove stale A2UI compatibility assets 2026-06-22 16:38:54 +08:00
Vincent Koc
f0afbd7e32 fix(crabbox): preflight macOS Swift toolchain 2026-06-22 16:34:57 +08:00
Vincent Koc
d9a38130b1 chore(deadcode): remove test-only task mutation wrappers 2026-06-22 16:24:57 +08:00
Vincent Koc
f2eca94391 feat(plugins): externalize additional official plugins (#95683) 2026-06-22 16:12:51 +08:00
Vincent Koc
4e9dc6b5d5 fix(skills): harden ClawHub update policy
Pass runtime config into CLI ClawHub skill updates so install policy sees configured safety rules, and update the bundled ClawHub skill docs to prefer openclaw skills for normal skill management. Keeps update-all limited to tracked ClawHub installs and intentionally leaves bundled-skill deprecation, legacy bootstrap, and Sherpa packaging for separate follow-up. Proof: focused ClawHub/CLI tests passed, autoreview clean, remote check:changed passed on Blacksmith Testbox tbx_01kvq0ywztsvw9vdc8zz1xktea; wrapper install/build/check passed, with full local pnpm test failing in unrelated baseline areas already reproduced on latest origin/main.
2026-06-22 16:03:19 +08:00
Vincent Koc
1711d0123c chore(deadcode): remove task registry test-only queries 2026-06-22 15:56:48 +08:00
Vincent Koc
d3f7f7d1fc chore(deadcode): remove unused test-only helpers 2026-06-22 15:48:43 +08:00
Vincent Koc
7cc21ef59d fix(qa): stabilize smoke-ci scenarios 2026-06-22 15:41:53 +08:00
Vincent Koc
f9ebb8d91b chore(deadcode): remove unused plugin version validator 2026-06-22 15:37:50 +08:00
zhang-guiping
783d5c19dd fix #89466: [Bug]: Control UI chat input text not cleared after sending (#95503)
Merged via squash.

Prepared head SHA: 32e5fd9cc3
Co-authored-by: zhangguiping-xydt <275915537+zhangguiping-xydt@users.noreply.github.com>
Co-authored-by: vincentkoc <25068+vincentkoc@users.noreply.github.com>
Reviewed-by: @vincentkoc
2026-06-22 15:34:01 +08:00
Peter Steinberger
15a0609a6b test(plugin-sdk): restore private surface budget 2026-06-22 03:30:14 -04:00
Vincent Koc
44ae2fd936 chore(deadcode): remove obsolete cron execution wrapper 2026-06-22 15:25:37 +08:00
ZOOWH
8e76feb482 fix(cron): use main-session systemEvent for silent quick-create preset (#95459)
Merged via squash.

Prepared head SHA: 1e966d4993
Co-authored-by: ZOOWH <265460861+ZOOWH@users.noreply.github.com>
Co-authored-by: vincentkoc <25068+vincentkoc@users.noreply.github.com>
Reviewed-by: @vincentkoc
2026-06-22 15:23:07 +08:00
Vincent Koc
f1c6057cd7 chore(deadcode): remove unused main-lane queue wrapper 2026-06-22 15:18:05 +08:00
Vincent Koc
c57fee8239 chore(deadcode): remove stale preview helper APIs 2026-06-22 15:07:07 +08:00
Peter Steinberger
23b4f33195 fix(plugin-sdk): keep CLI quoting helper private 2026-06-22 02:59:57 -04:00
Vincent Koc
078044a2cf test(sdk): refresh plugin surface budget 2026-06-22 14:57:53 +08:00
Vincent Koc
8ad231c241 chore(deadcode): remove obsolete session maintenance wrapper 2026-06-22 14:53:17 +08:00
Vincent Koc
04994f1046 test(scripts): normalize RPC RTT path assertions 2026-06-22 14:38:15 +08:00
Vincent Koc
9b4c5822eb test(raft): satisfy gateway lint 2026-06-22 14:36:12 +08:00
Vincent Koc
b39e905b69 fix(raft): support current bridge protocol 2026-06-22 14:36:12 +08:00
Vincent Koc
df6c71736c fix(raft): satisfy channel type contracts 2026-06-22 14:36:12 +08:00
Vincent Koc
2b50bbf152 feat(raft): add wake bridge channel 2026-06-22 14:36:12 +08:00
Wynne668
1bd85e3cc3 fix(openai-completions): seal native reasoning before the answer under /reasoning on (#95283)
* fix(openai-completions): seal native reasoning before the answer

deepseek-style providers stream reasoning via reasoning_content deltas
then switch to the answer via content deltas with no boundary event.
thinking_end was only emitted by the end-of-stream finishBlock loop, so
it landed after the answer's text_delta and channels merged the answer
into the reasoning block.

Seal the open native thinking block when visible text (or a tool call)
begins so thinking_end precedes the answer; tag-based <think> reasoning
is unaffected (closed by the partitioner). finishBlock is now idempotent
so the end-of-stream loop never re-emits thinking_end.

* fix(openai-completions): preserve co-streamed reasoning

* fix(openai-completions): order co-streamed reasoning

* fix(openai-completions): seal co-streamed reasoning

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-06-22 14:33:44 +08:00
Peter Steinberger
984efdb0b6 fix(discord): finish voice log preview rename 2026-06-22 02:30:08 -04:00
Vincent Koc
abdd81db11 test(qa): include long-context soak scenario 2026-06-22 14:28:04 +08:00
NIO
3cc05d590c fix(docs): show inline read_when hints in docs:list (#95243)
* fix(docs): show inline read_when hints in docs:list

* test(scripts): use shared temp directory helper

* test(scripts): route docs list helper test

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-06-22 14:14:20 +08:00
Vincent Koc
8246e8dace refactor(plugins): share channel command normalization 2026-06-22 14:10:40 +08:00
Vincent Koc
5df513c895 refactor(channels): share account enabled predicate 2026-06-22 14:08:46 +08:00
Vincent Koc
1529958067 refactor(models): reuse normalized provider lookup 2026-06-22 14:06:50 +08:00
Vincent Koc
7d3bc4d944 fix(ci): bundle test shards and right-size runners
Bundles only isolated low-risk Node shards, keeps stateful suites isolated, and right-sizes Blacksmith runners to reduce runner-registration bursts. Exact-head CI run 27932220655 passed the changed CI planning lanes; its two remaining agent test reds match latest main baseline run 27929063460 and are unrelated to this CI-only diff.
2026-06-22 14:05:40 +08:00
Vincent Koc
5fbb5f75ed refactor(discord): share voice log preview formatting 2026-06-22 14:04:38 +08:00
Vincent Koc
4506d8bad6 fix(qa): surface tool-search bridge results 2026-06-22 08:01:44 +02:00
Vincent Koc
7668ef2d35 refactor(memory): share workspace key normalization 2026-06-22 14:01:23 +08:00
Bryan Tegomoh, MD, MPH
8e4213b1c4 fix(gateway): accept port for health and probe (#94687)
* fix(gateway): accept port for health and probe

* fix(gateway): repair health port override

* fix(gateway): repair health port override

* fix(gateway): accept port for health and probe

---------

Co-authored-by: openclaw-clownfish[bot] <280122609+openclaw-clownfish[bot]@users.noreply.github.com>
2026-06-22 14:00:57 +08:00
Vincent Koc
a0ab5c00a8 test(scripts): harden kitchen sink timeout tests 2026-06-22 13:57:15 +08:00
Ayaan Zaidi
dc09324ec2 fix(telegram): narrow live claim recovery 2026-06-22 11:25:58 +05:30
mikasa0818
3be0fe722a fix(telegram): recover stuck live-owned spool claims
Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-22 11:25:58 +05:30
Vincent Koc
6be98022da fix(qa-matrix): surface gateway stderr in failures 2026-06-22 13:55:49 +08:00
Vincent Koc
cc89e155c3 refactor(imessage): share direct chat normalization 2026-06-22 13:55:36 +08:00
Vincent Koc
0111afe9e2 refactor(cli): share shell argument quoting 2026-06-22 13:54:17 +08:00
Vincent Koc
674b4f3372 refactor(plugins): move owner skills into plugins
Move Canvas, Discord, Slack, voice-call, and WhatsApp skill docs from the root bundled skill tree into their owning plugin packages, with manifest skills declarations and docs/test path updates.

Validation:
- node --import tsx scripts/sync-plugin-versions.ts --check
- node scripts/run-vitest.mjs src/skills/loading/env-path-guidance.test.ts
- autoreview clean
- Crabbox Azure pnpm check:changed run_59ba76511d57 / lease cbx_cbc6750dad72
- wrapper prepare passed pnpm install --frozen-lockfile, pnpm build, and pnpm check before the oversized full local pnpm test was stopped

Follow-up: #95132 remains as the stacked PR for the rest of the starter-skill/Sherpa/ClawHub work.
2026-06-22 13:54:08 +08:00
wangmiao0668000666
4db829646a fix(sdk): type-narrow manifest.files in pack staging root helper (#95465) 2026-06-22 13:52:51 +08:00
Vincent Koc
e046dbb52d test(agents): repair fallback runtime CI coverage 2026-06-22 13:49:43 +08:00
lizeyu-xydt
a964132d80 fix(config): add stdio to McpServerSchema transport union (#95102)
* fix(config): add "stdio" to McpServerSchema transport union

Add z.literal("stdio") to the transport union so that
config validate accepts transport: "stdio" on command-bearing
MCP servers, matching the runtime behavior.

Fixes #95082

* fix(config): align McpServerConfig transport type and add schema coverage

- Widened McpServerConfig.transport to include "stdio"
- Added schema test for command-bearing server with explicit stdio transport
- Added schema test rejecting unsupported transport values

* fix(config): constrain transport stdio to command-bearing MCP servers

* fix(config): align stdio refine with runtime trimmed command check
2026-06-22 13:48:23 +08:00
MaHaoHao-ch
0374892fd8 docs(plugins): document subagent_ended hook fields (#95191)
The Subagents section of docs/plugins/hooks.md listed subagent_ended
but did not describe its payload. Plugin authors reaching for
agentId (as on subagent_spawned) silently got undefined because
PluginHookSubagentEndedEvent uses targetSessionKey as its identity
field and has no agentId/childSessionKey.

Document all fields from PluginHookSubagentEndedEvent and explicitly
note the missing identity fields so handlers can correlate events
correctly.

Closes #95186

Co-authored-by: MaHaoHao-ch <MaHaoHao-ch@users.noreply.github.com>
2026-06-22 13:48:17 +08:00
miorbnli
a2b3aab7b0 fix(ssh): reject hostnames with stray leading or trailing colons in parseSshTarget (#93887)
* fix(ssh): reject hostnames with stray leading or trailing colons in parseSshTarget

parseSshTarget would previously return host values like "host:" or ":22"
when the input had a trailing colon with no port, or a leading colon with
no host. These values flow directly into SSH config HostName fields and the
ssh CLI argv, which causes connections to fail.

Add a guard that rejects host parts starting or ending with ":" before
the existing "-" prefix check. Existing valid inputs ("host", "host:22",
"user@host:22") are unaffected.

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

* fix(ssh): validate stray-colon host in explicit-port parse branch

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-06-22 13:48:10 +08:00
Yuval Dinodia
2ece2945ae fix(compaction): count user-message image blocks in cut-point estimator (#95128)
estimateTokens charged 4800 chars per image in the toolResult branch but
counted only text in the user branch, so image blocks in recent user turns
scored zero. findCutPoint never reached keepRecentTokens and left the cut at
the earliest point, so image-heavy sessions compacted to a no-op and looped on
context overflow. Fold the per-image accounting into one shared helper used by
both branches.
2026-06-22 13:48:02 +08:00
Vincent Koc
f05fd56d66 fix(googlechat): remove useless test string concatenation 2026-06-22 13:42:34 +08:00
Ayaan Zaidi
a9a75b2b77 test(telegram): tighten spooled drain race proof 2026-06-22 11:08:03 +05:30
weiqinl
e29381a172 fix #86957: drain worker-spooled Telegram updates immediately
Wake the isolated polling drain immediately after a worker-spooled
update is written to channel_ingress_events, instead of waiting for
the next drain interval.

- Add requestImmediateDrain() calls after worker write and spooled message
- Track pending drain requests while drain is active (fix race condition)
- Add regression test for updates arriving during active drain

Fixes #86957.
2026-06-22 11:08:03 +05:30
clawsweeper[bot]
62456d65eb Avoid copying process.env in ingress queue state DB opens (#95278)
* Avoid copying process.env in ingress queue state DB opens

Co-authored-by: kaka-srp <178872848+kaka-srp@users.noreply.github.com>

* Repair #95278: avoid copying process.env in ingress queue DB opens

---------

Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: kaka-srp <178872848+kaka-srp@users.noreply.github.com>
Co-authored-by: openclaw-clownfish[bot] <280122609+openclaw-clownfish[bot]@users.noreply.github.com>
2026-06-22 13:37:51 +08:00
Javier Ailbirt
696c624008 fix(googlechat): sanitize internal tool-trace lines from outbound text (#90684) (#95084)
* fix(googlechat): sanitize internal tool-trace lines from outbound text (#90684)

Google Chat ran only sanitizeForPlainText() on outbound text, so internal
assistant trace lines leaked to users — most visibly the
`⚠️ 🛠️ <command> (agent) failed` banner emitted for benign non-zero shell
exits (e.g. a grep with no match, exit 1). The turn succeeds but the user
gets an alarming "failed" message.

Apply sanitizeAssistantVisibleText() before sanitizeForPlainText() in the
googlechat outbound sanitizeText hook, mirroring Discord and WhatsApp. The
hook runs pre-chunking in the shared deliver path, covering all text sends.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(googlechat): sanitize internal tool-trace lines from outbound text (#90684)

---------

Co-authored-by: Claude Code <claude@anthropic.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: openclaw-clownfish[bot] <280122609+openclaw-clownfish[bot]@users.noreply.github.com>
2026-06-22 13:37:06 +08:00
Ayaan Zaidi
b3b8b289dd test(telegram): keep live polling leases protected (#93378) (thanks @mmyzwl) 2026-06-22 11:05:06 +05:30
Vincent Koc
692c5e34f0 refactor(agents): share thinking block predicate 2026-06-22 13:30:22 +08:00
Vincent Koc
536c8a840b refactor(llm): share cache retention resolution 2026-06-22 13:29:23 +08:00
Wynne668
99551c499b fix(skills): point gog brew install at homebrew-core gogcli (#95017) (#95019)
Merged via squash.

Prepared head SHA: a4f489c616
Co-authored-by: ZengWen-DT <290981215+ZengWen-DT@users.noreply.github.com>
Co-authored-by: vincentkoc <25068+vincentkoc@users.noreply.github.com>
Reviewed-by: @vincentkoc
2026-06-22 13:02:26 +08:00
Vincent Koc
2c3519c1d7 test(agents): cover fallback probe attempt position 2026-06-22 12:58:10 +08:00
Vincent Koc
e114001cca fix(channels): preserve keyed progress state updates 2026-06-22 12:51:33 +08:00
Vincent Koc
3ff59df960 test(agents): cover fallback attempt position 2026-06-22 12:33:38 +08:00
Vincent Koc
a594d2ce73 fix(channels): remove unusable post-final progress override 2026-06-22 12:06:42 +08:00
Vincent Koc
c92f366c14 fix(ci): repair main validation gates 2026-06-22 11:58:27 +08:00
Alex Knight
2b92706dcf feat(mattermost): register /oc_queue as a native slash command
Add oc_queue to DEFAULT_COMMAND_SPECS so the native slash-command registrar exposes /oc_queue, mapped (originalName: queue) to the core /queue directive via the existing trigger-map path. Additive; rides the same registration/callback machinery as the other oc_* commands.
2026-06-22 13:56:40 +10:00
Vincent Koc
a9be81d510 fix(ci): repair baseline lint and test gates 2026-06-22 11:44:21 +08:00
joshavant
97a015bace fix(ios): drop unused bootstrap test hook 2026-06-21 23:40:56 -04:00
joshavant
93c7ec645a fix(ios): remove stale notification authorization seam 2026-06-21 23:40:56 -04:00
joshavant
920bd04e19 fix(ios): make notification guidance suppression a button 2026-06-21 23:40:56 -04:00
joshavant
5ae53cf9fb fix(ios): refine notification permission copy 2026-06-21 23:40:56 -04:00
joshavant
1168ac2fcd fix(ios): update notification permission copy 2026-06-21 23:40:56 -04:00
joshavant
112a0ddaf8 fix(ios): neutralize exec approval prompt copy 2026-06-21 23:40:56 -04:00
joshavant
c3ab1feb61 test(ios): cover notification permission UX 2026-06-21 23:40:56 -04:00
joshavant
0bd2aa8ee0 feat(ios): consolidate notification permission UX 2026-06-21 23:40:56 -04:00
Vincent Koc
7d4d8a7f3d test(agents): add large prompt cache coverage (#95653)
Adds large OpenAI and Anthropic prompt-cache live coverage plus a QA Lab long-context tool-result scenario.

Co-authored-by: vincentkoc <vincentkoc@users.noreply.github.com>
Reviewed-by: @vincentkoc
2026-06-22 11:39:37 +08:00
Vincent Koc
e9be15ff19 test(scripts): use true package timer overflow inputs 2026-06-22 05:31:53 +02:00
Vincent Koc
05580342f7 fix(agents): remove unused context guard import 2026-06-22 11:18:52 +08:00
Vincent Koc
17dc9902f2 test(scripts): use true overflow timer inputs 2026-06-22 05:11:25 +02:00
ly-wang19
540ec53f99 fix(agents): reject bind specs with extra colon segments (#95572)
Summary:
- The PR changes `parseBindingSpecs` to reject `<channel>:<account>:...` bind specs and adds Matrix parser regression coverage for the malformed and valid forms.
- PR surface: Source +8, Tests +26. Total +34 across 2 files.
- Reproducibility: yes. Source inspection of current main shows `parseBindingSpecs` truncating with `split(":", 2)`, and the PR body provides before/after terminal output from a harness importing the real parser.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(agents): reject bind specs with extra colon segments

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

Prepared head SHA: 3e1af31fc4
Review: https://github.com/openclaw/openclaw/pull/95572#issuecomment-4762320150

Co-authored-by: ly-wang19 <ly-wang19@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: openclaw-clownfish[bot] <280122609+openclaw-clownfish[bot]@users.noreply.github.com>
2026-06-22 03:07:50 +00:00
Vincent Koc
a182811070 fix(ci): smooth PR runner-registration bursts (#95625)
Merged via squash.

Prepared head SHA: 72b2b00bf8
Co-authored-by: vincentkoc <25068+vincentkoc@users.noreply.github.com>
Co-authored-by: vincentkoc <25068+vincentkoc@users.noreply.github.com>
Reviewed-by: @vincentkoc
2026-06-22 10:53:33 +08:00
Vincent Koc
4e2f0157c7 test(scripts): harden kitchen sink timer proof 2026-06-22 04:42:34 +02:00
Vincent Koc
0f64e3c052 fix(scripts): clamp secret proof timers 2026-06-22 04:38:45 +02:00
Vincent Koc
65388233e2 chore(deadcode): remove duplicate session transcript reader 2026-06-22 10:37:42 +08:00
Vincent Koc
044df2516e fix(test): keep Gemini CLI docker lane advisory 2026-06-22 04:32:34 +02:00
Vincent Koc
190ca52882 chore(deadcode): remove stale reply tag helper 2026-06-22 10:31:18 +08:00
Vincent Koc
ad304e790d chore(deadcode): remove unused speech contract registry 2026-06-22 10:19:01 +08:00
Vincent Koc
e913e0739d fix(scripts): clamp openwebui probe timers 2026-06-22 04:11:45 +02:00
Vincent Koc
8e24695a8d chore(deadcode): remove redundant observation aliases 2026-06-22 10:09:48 +08:00
Vincent Koc
b0ecf6e1e7 chore(deadcode): trim private attempt facade 2026-06-22 10:07:58 +08:00
Vincent Koc
dc5c2f6360 fix(core): restore typecheck helpers 2026-06-22 04:06:11 +02:00
Vincent Koc
2405d029d4 test(gateway): classify live provider drift 2026-06-22 04:06:11 +02:00
Vincent Koc
4e96ca0d12 chore(deadcode): prune unused attempt reexports 2026-06-22 10:05:03 +08:00
Vincent Koc
84ccba6b32 chore(deadcode): remove stale embedded-runner reexports 2026-06-22 10:03:04 +08:00
Vincent Koc
d17bb9c3e9 chore(deadcode): collapse session helper barrels 2026-06-22 09:59:31 +08:00
Vincent Koc
2f9107f672 chore(deadcode): remove stale internal reexport shims 2026-06-22 09:57:04 +08:00
Vincent Koc
6328c8637b chore(deadcode): remove stale agent helper reexports 2026-06-22 09:52:40 +08:00
Vincent Koc
3053cbc8a5 Merge branch 'main' of https://github.com/openclaw/openclaw into fix/stable-tool-result-prompt-cache
* 'main' of https://github.com/openclaw/openclaw: (39 commits)
  chore(deadcode): remove stale agent helper reexports
  chore(ui): refresh fa control ui locale
  chore(ui): refresh nl control ui locale
  chore(ui): refresh th control ui locale
  chore(ui): refresh vi control ui locale
  chore(ui): refresh id control ui locale
  chore(ui): refresh pl control ui locale
  chore(ui): refresh uk control ui locale
  chore(ui): refresh tr control ui locale
  chore(ui): refresh it control ui locale
  chore(ui): refresh ar control ui locale
  chore(ui): refresh fr control ui locale
  chore(ui): refresh ko control ui locale
  chore(ui): refresh ja-JP control ui locale
  chore(ui): refresh es control ui locale
  chore(ui): refresh zh-TW control ui locale
  chore(ui): refresh zh-CN control ui locale
  chore(ui): refresh pt-BR control ui locale
  chore(ui): refresh de control ui locale
  fix: preserve fast auto delivery semantics
  ...
2026-06-22 09:48:14 +08:00
Vincent Koc
eb9318e953 chore(deadcode): remove stale agent helper reexports 2026-06-22 09:47:02 +08:00
github-actions[bot]
f80f4a8b95 chore(ui): refresh fa control ui locale 2026-06-22 01:42:45 +00:00
github-actions[bot]
034629404d chore(ui): refresh nl control ui locale 2026-06-22 01:42:28 +00:00
github-actions[bot]
ef67ffd697 chore(ui): refresh th control ui locale 2026-06-22 01:42:04 +00:00
github-actions[bot]
fe524d2a46 chore(ui): refresh vi control ui locale 2026-06-22 01:41:57 +00:00
github-actions[bot]
6c8dcc9d35 chore(ui): refresh id control ui locale 2026-06-22 01:41:53 +00:00
github-actions[bot]
34ab295734 chore(ui): refresh pl control ui locale 2026-06-22 01:41:51 +00:00
github-actions[bot]
a5139a8c5c chore(ui): refresh uk control ui locale 2026-06-22 01:41:20 +00:00
github-actions[bot]
7f99824164 chore(ui): refresh tr control ui locale 2026-06-22 01:41:17 +00:00
github-actions[bot]
cbaeaa8856 chore(ui): refresh it control ui locale 2026-06-22 01:41:15 +00:00
github-actions[bot]
a6cac347b6 chore(ui): refresh ar control ui locale 2026-06-22 01:41:03 +00:00
github-actions[bot]
4d0aec8095 chore(ui): refresh fr control ui locale 2026-06-22 01:40:44 +00:00
github-actions[bot]
fa51a624c0 chore(ui): refresh ko control ui locale 2026-06-22 01:40:40 +00:00
github-actions[bot]
075e328c62 chore(ui): refresh ja-JP control ui locale 2026-06-22 01:40:34 +00:00
github-actions[bot]
0d21d489ab chore(ui): refresh es control ui locale 2026-06-22 01:40:18 +00:00
github-actions[bot]
d8f1000600 chore(ui): refresh zh-TW control ui locale 2026-06-22 01:40:05 +00:00
github-actions[bot]
227b4c81ed chore(ui): refresh zh-CN control ui locale 2026-06-22 01:39:58 +00:00
github-actions[bot]
d28d6c2399 chore(ui): refresh pt-BR control ui locale 2026-06-22 01:39:53 +00:00
github-actions[bot]
d09f728208 chore(ui): refresh de control ui locale 2026-06-22 01:39:40 +00:00
Vincent Koc
9e8ab083dd fix: preserve fast auto delivery semantics 2026-06-22 09:37:11 +08:00
Vincent Koc
6eb72a830e fix: gate fallback fast reset notices 2026-06-22 09:37:11 +08:00
Vincent Koc
6b1eef9959 fix: type fallback state for CLI runs 2026-06-22 09:37:11 +08:00
Vincent Koc
14e448e0e1 fix: carry fast mode fallback state through runners 2026-06-22 09:37:11 +08:00
Vincent Koc
aa3797c8d0 fix: complete fast mode fallback and status wiring 2026-06-22 09:37:10 +08:00
Vincent Koc
93ad397725 fix: preserve normalization and ACP fast mode contracts 2026-06-22 09:37:10 +08:00
Vincent Koc
cf1b6fef44 fix: mark embedded auto fast mode explicitly 2026-06-22 09:37:10 +08:00
Vincent Koc
d990115d19 fix: preserve fast mode across retries 2026-06-22 09:37:10 +08:00
Vincent Koc
8a75c4dd5f fix: preserve repeated channel progress events 2026-06-22 09:37:10 +08:00
Vincent Koc
efd3172662 fix: resolve fallback fast cutoff per model 2026-06-22 09:37:10 +08:00
Vincent Koc
8afc1f770b fix(codex): normalize cleared service tier in bindings 2026-06-22 09:37:10 +08:00
Vincent Koc
77012f9807 fix: clear Codex tier and render auto fast status 2026-06-22 09:37:10 +08:00
Vincent Koc
2732f58215 test: align fast auto forward-port with current fixtures 2026-06-22 09:37:10 +08:00
Vincent Koc
3eeccbe782 fix: remove stale fast auto event import 2026-06-22 09:37:09 +08:00
Vincent Koc
2b75806197 feat: forward-port fast talks auto mode (#85104) 2026-06-22 09:37:09 +08:00
Vincent Koc
adb9abe721 chore(deadcode): collapse stale provider registry views 2026-06-22 09:31:39 +08:00
Vincent Koc
2a6554ac12 fix(scripts): clamp audit request timer 2026-06-22 03:30:19 +02:00
Vincent Koc
4113982fa8 fix(scripts): clamp memory fd repro timers 2026-06-22 03:25:02 +02:00
Vincent Koc
2800ce4e28 chore(deadcode): remove unused plugin contract registry paths 2026-06-22 09:21:24 +08:00
Vincent Koc
45a93b8450 fix(scripts): clamp openwebui probe timer 2026-06-22 03:18:24 +02:00
Vincent Koc
708c1b31e0 fix(agents): preserve transformed tool results 2026-06-22 09:17:50 +08:00
Vincent Koc
3fc1284fe6 test(agents): type complete projection fixtures 2026-06-22 09:17:50 +08:00
Vincent Koc
1a075c375c fix(agents): avoid ambiguous prompt projection reuse 2026-06-22 09:17:50 +08:00
Vincent Koc
95f314e822 fix(agents): stabilize repeated tool result identities 2026-06-22 09:17:50 +08:00
Vincent Koc
efe3cbd695 fix(agents): finish aggregate prompt reduction 2026-06-22 09:17:50 +08:00
Vincent Koc
79fac9fda9 fix(agents): cap new prompt projection output 2026-06-22 09:17:50 +08:00
Vincent Koc
ef6dc8f7e5 fix(agents): count frozen prompt bytes 2026-06-22 09:17:50 +08:00
Vincent Koc
f31306eb4e fix(agents): reproject converted tool results safely 2026-06-22 09:17:50 +08:00
Vincent Koc
db24112617 fix(agents): keep converted tool-result blocks intact 2026-06-22 09:17:50 +08:00
Vincent Koc
4fd19adf25 fix(agents): preserve filtered tool-result blocks 2026-06-22 09:17:50 +08:00
Vincent Koc
62dcc9bc3b fix(agents): freeze prompt projection candidates 2026-06-22 09:17:50 +08:00
Vincent Koc
bda4404f69 fix(agents): reuse memoized prompt projections 2026-06-22 09:17:50 +08:00
Vincent Koc
30925601ae fix(agents): memoize prompt truncation projections 2026-06-22 09:17:49 +08:00
Vincent Koc
a2675756b8 test(agents): stabilize Claude live spawn coverage 2026-06-22 09:16:31 +08:00
Vincent Koc
095a44c8de fix(scripts): clamp run-with-env kill timer 2026-06-22 03:15:26 +02:00
Vincent Koc
29eba5aaef fix(test): clamp qa otel child timer 2026-06-22 03:12:25 +02:00
Vincent Koc
b99812b3b1 fix(scripts): clamp additional boundary timers 2026-06-22 03:08:19 +02:00
Vincent Koc
4f7d1f4977 fix(scripts): clamp boundary artifact timers 2026-06-22 03:05:11 +02:00
Vincent Koc
c310f8cfa4 fix(scripts): clamp package download timers 2026-06-22 03:01:52 +02:00
Vincent Koc
08442c4b38 fix(scripts): clamp boundary check timers 2026-06-22 02:54:30 +02:00
Vincent Koc
fe7b78b05f chore(deadcode): prune stale test hooks 2026-06-22 08:51:49 +08:00
Vincent Koc
dd89898133 fix(scripts): clamp package candidate timers 2026-06-22 02:51:05 +02:00
Vincent Koc
851b65c060 fix(scripts): clamp package docker timers 2026-06-22 02:47:41 +02:00
Vincent Koc
75c6a8fff5 fix(scripts): clamp docker all timers 2026-06-22 02:44:00 +02:00
Vincent Koc
f9fc380e90 fix(scripts): clamp gauntlet command timers 2026-06-22 02:38:04 +02:00
Vincent Koc
8ef73be8e8 fix(scripts): clamp plugin lifecycle timers 2026-06-22 02:33:55 +02:00
Vincent Koc
afadf1f7da chore(deadcode): prune stale codeql paths 2026-06-22 08:33:19 +08:00
Vincent Koc
29185aed68 test(e2e): refresh docker setup fixtures 2026-06-22 02:32:05 +02:00
Vincent Koc
3e5ca880bf fix(scripts): clamp parallels update timeouts 2026-06-22 02:29:26 +02:00
Vincent Koc
a09e1b9aa0 chore(deadcode): prune stale routing config 2026-06-22 08:28:19 +08:00
Vincent Koc
11959ad100 fix(scripts): clamp parallels phase timeouts 2026-06-22 02:24:52 +02:00
snowzlmbot
e37b0f8cd3 fix(telegram): render progress drafts with clean HTML transport
Render Telegram progress draft rows with clean plain previews while preserving Telegram HTML formatting through parse_mode.

The progress HTML path now stays transport-owned, including richMessages=false progress messages, while debug/plain/sanitized text remains readable without raw markup.

Thanks @snowzlmbot!
2026-06-22 05:54:44 +05:30
Vincent Koc
dbb58341b5 chore(deadcode): remove stale vitest coverage excludes 2026-06-22 08:20:03 +08:00
Vincent Koc
790dfb66a8 fix(scripts): clamp parallels host timeouts 2026-06-22 02:18:53 +02:00
Jesse Merhi
4cb94cc2cf fix: keep trusted policies with hook registry (#94545)
* fix: keep trusted policies with hook registry

* fix: compose trusted policies with live hooks

* fix: preserve trusted policy order

* test: update hook runner mock

* fix: keep hook policy registry internal
2026-06-22 10:15:24 +10:00
anagnorisis2peripeteia
298a0cd55f fix(claude-cli): disable unsupported background tools (#95008) (thanks @anagnorisis2peripeteia)
Disable Claude CLI native background Bash and Monitor in OpenClaw-managed print runs, matching the existing fail-closed scheduler-tool behavior. This keeps deferred work on OpenClaw-owned wake paths instead of native Claude callbacks OpenClaw cannot deliver.

Thanks @anagnorisis2peripeteia!
2026-06-22 05:44:48 +05:30
Vincent Koc
c578608b78 fix(scripts): clamp telegram proof timeouts 2026-06-22 02:13:45 +02:00
Vincent Koc
8c0767ffa4 chore(deadcode): remove unused tooling helpers 2026-06-22 08:13:03 +08:00
Omar Shahine
a0714a3d68 fix(imessage): keep split-send coalescing opt-in (#93143)
Merged via squash.

Prepared head SHA: 7fc3eca084
Co-authored-by: omarshahine <10343873+omarshahine@users.noreply.github.com>
Co-authored-by: omarshahine <10343873+omarshahine@users.noreply.github.com>
Reviewed-by: @omarshahine
2026-06-21 17:12:21 -07:00
Vincent Koc
f719813a7e fix(scripts): clamp kitchen sink timeouts 2026-06-22 02:08:47 +02:00
Vincent Koc
11a3903ede chore(deadcode): remove stale changed-test routes 2026-06-22 08:05:30 +08:00
Vincent Koc
1f6ae32cab fix(process): clamp queue task timeouts 2026-06-22 02:01:39 +02:00
Vincent Koc
880425b03c chore(deadcode): trim unused discord vitest runtime shims 2026-06-22 07:59:00 +08:00
Vincent Koc
4c736df975 fix(agents): clamp session owner wait timeouts 2026-06-22 01:56:37 +02:00
Vincent Koc
6c85b90469 fix(replies): clamp block delivery timeouts 2026-06-22 01:49:55 +02:00
Vincent Koc
ef41560059 chore(deadcode): remove unused vitest helper config 2026-06-22 07:48:28 +08:00
Vincent Koc
88b21fc30b fix(media): clamp image description timeouts 2026-06-22 01:40:44 +02:00
Vincent Koc
4b6182ee2a fix(minimax): clamp vlm request timeouts 2026-06-22 01:31:13 +02:00
Vincent Koc
d43bc3760e chore(deadcode): remove unused test bindings 2026-06-22 07:31:03 +08:00
Vincent Koc
8cd0c11227 fix(clawhub): clamp request timeouts 2026-06-22 01:22:15 +02:00
Vincent Koc
66b94ba577 fix(process): clamp execfile timeouts 2026-06-22 01:10:52 +02:00
Vincent Koc
77b6ca9a9b fix(sdk): tighten surface report budgets 2026-06-22 01:04:53 +02:00
Vincent Koc
1425bb3a03 fix(process): clamp command timeouts 2026-06-22 01:00:41 +02:00
Peter Steinberger
11484f8a14 fix(ollama): support GLM-5.2 cloud discovery 2026-06-21 19:00:15 -04:00
mikasa
ec7a548062 fix #95378: https://github.com/openclaw/openclaw/issues/95378 (#95390)
* fix(telegram): use session transcript for direct context

* fix(telegram): account for proof and SDK checks

* fix(telegram): address review findings

* fix(telegram): tighten session transcript context

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-06-22 04:29:44 +05:30
Vincent Koc
8ecdb97b63 fix(channels): bound capabilities probes 2026-06-22 00:53:26 +02:00
Vincent Koc
a39e548ede fix(mcp): cap channel bridge request limits 2026-06-22 00:32:32 +02:00
Vincent Koc
328a44695f chore(deadcode): remove unused agent-core prompt formatter 2026-06-22 06:23:07 +08:00
Vincent Koc
c21dcfc7c2 fix(tui): clamp local shutdown grace timeout 2026-06-22 00:22:08 +02:00
Vincent Koc
2bdcc8314d fix(sdk): preserve zero run timeout watchdog 2026-06-22 00:13:51 +02:00
Vincent Koc
464adfe5e5 chore(deadcode): remove unused agent-core harness APIs 2026-06-22 06:08:32 +08:00
Vincent Koc
b66b4504f8 fix(gateway): cap plugin session message reads 2026-06-22 00:06:04 +02:00
Vincent Koc
f6d432e545 fix(gateway): expire default node pending work 2026-06-21 23:52:44 +02:00
Vincent Koc
fd13192adc fix(qa-lab): version UI assets from repo root 2026-06-21 23:43:48 +02:00
Vincent Koc
1c63da09d8 fix(qa-lab): cap agent wait timeout payloads 2026-06-21 23:34:38 +02:00
Vincent Koc
735505442c chore(deadcode): dedupe live assistant text extraction 2026-06-22 05:32:29 +08:00
Vincent Koc
108d6d7eca fix(gateway): reject malformed restart request params 2026-06-21 23:28:53 +02:00
Vincent Koc
984c8f6ea0 chore(deadcode): remove stale channel presence helper 2026-06-22 05:21:39 +08:00
Shakker
cd9060e06a test: restore host cleanup state env 2026-06-21 22:20:58 +01:00
Vincent Koc
5b96eb0172 fix(e2e): reject flag Parallels smoke values 2026-06-21 23:19:32 +02:00
Vincent Koc
b95b725c83 fix(e2e): reject help flag Crabbox proof values 2026-06-21 23:13:12 +02:00
Shakker
4b881509eb fix: restore media cache state env 2026-06-21 22:12:46 +01:00
Vincent Koc
a03032a272 chore(plugin-sdk): refresh API baseline hash 2026-06-21 23:09:27 +02:00
Shakker
c94ebdbebd test: restore heartbeat state env 2026-06-21 22:07:04 +01:00
Alex Knight
7b46167607 fix(channels): preserve migrated account policies 2026-06-22 07:05:45 +10:00
Vincent Koc
0e53358945 fix(e2e): reject flag Telegram credential values 2026-06-21 23:04:51 +02:00
Vincent Koc
6b45e9af7a fix(scripts): validate iOS node CLI values before help 2026-06-21 23:00:30 +02:00
Vincent Koc
2ef61eb782 chore(deadcode): drop unused exact session lookup 2026-06-22 04:58:21 +08:00
Vincent Koc
6823f56d8e fix(scripts): reject flag device-pair Telegram values 2026-06-21 22:56:54 +02:00
Shakker
3e1d3c5feb fix: isolate provider runtime env cache 2026-06-21 21:54:05 +01:00
Vincent Koc
dfbc9ab246 fix(scripts): reject short flag TUI PTY values 2026-06-21 22:52:21 +02:00
Vincent Koc
179eb15554 chore(deadcode): remove unused state path readers 2026-06-22 04:50:54 +08:00
Shakker
c3b1e926e8 test: route gateway call env resets 2026-06-21 21:46:55 +01:00
Vincent Koc
89768d456b fix(scripts): reject short flag Discord smoke values 2026-06-21 22:45:03 +02:00
Vincent Koc
609d7a14b1 chore(deadcode): remove test-only helper APIs 2026-06-22 04:41:04 +08:00
Vincent Koc
3bae0d6b82 fix(qa): reject short flag gateway smoke values 2026-06-21 22:40:40 +02:00
Vincent Koc
75a997dd7c fix(scripts): reject short flag shrinkwrap refs 2026-06-21 22:40:07 +02:00
Shakker
ab8dc3af52 fix: reuse doctor env snapshot helper 2026-06-21 21:37:56 +01:00
Vincent Koc
bebc5d847d fix(scripts): reject short flag CI timing limits 2026-06-21 22:34:29 +02:00
Vincent Koc
a0f28bd3f5 fix(scripts): reject short flag version values 2026-06-21 22:33:59 +02:00
Shakker
c638617897 test: share agent state-dir env guard 2026-06-21 21:31:10 +01:00
Vincent Koc
b77d6149e1 fix(scripts): reject short flag extension memory values 2026-06-21 22:28:14 +02:00
Vincent Koc
36db108fc1 fix(scripts): reject short flag boundary values 2026-06-21 22:26:44 +02:00
heichaowo
e00c1eebc4 fix(telegram): skip duplicate mirror replies (#95069)
Fix Telegram session transcript duplicate mirror writes after the primary assistant reply already exists.

Thanks @heichaowo!
2026-06-22 01:55:08 +05:30
NIO
32d22d04cc fix(telegram): honor outbound reaction directives (#94977) 2026-06-22 01:54:55 +05:30
Vincent Koc
648ef73bde fix(qa): reject short flag UX evidence paths 2026-06-21 22:20:56 +02:00
Vincent Koc
beebb35de4 fix(scripts): reject short flag values in helper CLIs 2026-06-21 22:19:54 +02:00
Vincent Koc
55959148ca chore(deadcode): remove unused infra wrappers 2026-06-22 04:18:02 +08:00
Vincent Koc
4684bbba97 fix(scripts): reject short flag values in benchmark CLIs 2026-06-21 22:11:19 +02:00
Vincent Koc
409adfbe10 chore(deadcode): drop stale helper APIs 2026-06-22 04:10:02 +08:00
Vincent Koc
2609b97222 fix(scripts): reject short flag values in bench parsers 2026-06-21 22:06:30 +02:00
Vincent Koc
03bc600e67 fix(scripts): reject short flag startup memory paths 2026-06-21 22:06:04 +02:00
Vincent Koc
8102d5ebc3 fix(scripts): reject short flag performance summary paths 2026-06-21 22:02:05 +02:00
Vincent Koc
6399eb8191 fix(scripts): reject short flag vitest profile dirs 2026-06-21 21:53:58 +02:00
Vincent Koc
1a5839fbd8 fix(scripts): reject short flag audit severity values 2026-06-21 21:51:02 +02:00
Vincent Koc
d17045db6f chore(deadcode): drop unused helper exports 2026-06-22 03:49:17 +08:00
Vincent Koc
2a8db1fc23 fix(scripts): reject short flag ownership report values 2026-06-21 21:45:36 +02:00
Vincent Koc
82f43f0a62 fix(scripts): reject short flag values in release docs parsers 2026-06-21 21:39:41 +02:00
Vincent Koc
9adf3d92bd chore(deadcode): remove unused helper paths 2026-06-22 03:39:19 +08:00
Vincent Koc
e21164933a fix(scripts): reject short flag report values 2026-06-21 21:36:26 +02:00
Vincent Koc
1b17517969 fix(scripts): reject short flag closeout values 2026-06-21 21:32:17 +02:00
Vincent Koc
86fea26797 fix(plugin-sdk): stabilize surface report after builds 2026-06-21 21:28:47 +02:00
Vincent Koc
2e7c3ace9c chore(plugin-sdk): refresh API baseline hashes 2026-06-21 21:28:47 +02:00
Vincent Koc
e34204a1e0 fix(scripts): reject short flag package roots 2026-06-21 21:27:57 +02:00
Vincent Koc
6daf9307e0 fix(scripts): reject short flag docker package values 2026-06-21 21:24:30 +02:00
Vincent Koc
ebb670b208 fix(scripts): reject short flag gauntlet values 2026-06-21 21:18:48 +02:00
Vincent Koc
52672c7af1 fix(scripts): reject short flag gateway cpu values 2026-06-21 21:15:21 +02:00
Vincent Koc
690efd2a16 chore(deadcode): inline constant helper stubs 2026-06-22 03:14:04 +08:00
Vincent Koc
102ab759e7 fix(scripts): reject option-shaped package candidate values 2026-06-21 21:11:25 +02:00
Vincent Koc
7da955fae4 fix(config): type slack secret refs 2026-06-21 20:58:33 +02:00
Vincent Koc
06a0148072 chore(deadcode): remove inert browser relay hook 2026-06-22 02:50:55 +08:00
Vincent Koc
edd1d3319c chore(deadcode): dedupe repeated literal lists 2026-06-22 02:35:03 +08:00
Vincent Koc
0ea39a2276 chore(deadcode): remove inert memory provider bootstrap 2026-06-22 02:30:56 +08:00
Sarah Fortune
6fa944e80f [codex] Add Slack relay mode for incoming messages (#94707) 2026-06-21 11:28:33 -07:00
Vincent Koc
4c453c931f fix(test): reject dependency report flag values 2026-06-21 20:23:23 +02:00
Vincent Koc
13b0976c70 fix(release): reject dependency evidence flag values 2026-06-21 20:19:49 +02:00
Vincent Koc
43e00c06c3 fix(docs): reject sync publish flag values 2026-06-21 20:13:50 +02:00
Vincent Koc
03ce3d41b1 fix(ci): reject hosted gate flag values 2026-06-21 20:09:53 +02:00
Vincent Koc
bda05dbc2f fix(release): reject validation flag values 2026-06-21 20:05:22 +02:00
Vincent Koc
7069d95720 fix(test): reject diff ref flag values 2026-06-21 19:59:55 +02:00
Vincent Koc
8086cffd17 fix(test): reject group report flag values 2026-06-21 19:56:25 +02:00
Vincent Koc
d91aee7220 fix(ci): reject flag refs in changed scope 2026-06-21 19:54:57 +02:00
Vincent Koc
c8ab37f6fe chore(deadcode): drop inert legacy workspace doctor check 2026-06-22 01:47:27 +08:00
Vincent Koc
a823cb2b30 fix(test): bound rpc process tree sampling 2026-06-21 19:41:51 +02:00
Vincent Koc
5230ec66ae chore(plugin-sdk): refresh API surface baselines 2026-06-21 19:32:49 +02:00
Vincent Koc
eea777c9fc chore(deadcode): trim stale facade re-exports 2026-06-22 01:16:08 +08:00
Vincent Koc
0b28a72be1 fix(test): reject kova help value bypasses 2026-06-21 19:07:02 +02:00
Vincent Koc
adcba85264 fix(test): reject cpuprofile limit help tokens 2026-06-21 18:56:26 +02:00
Vincent Koc
5b79fa13e2 chore(deadcode): trim doctor alias wrappers 2026-06-22 00:54:26 +08:00
Vincent Koc
124ea48549 fix(test): reject docker timing flag limits 2026-06-21 18:49:24 +02:00
Vincent Koc
12756fc4c8 fix(test): reject env report flag paths 2026-06-21 18:46:05 +02:00
Vincent Koc
5bf459e23b fix(test): reject attestation platform flags 2026-06-21 18:43:05 +02:00
Vincent Koc
d64a27feeb chore(deadcode): drop node daemon runtime alias 2026-06-22 00:41:56 +08:00
Vincent Koc
6c42f73619 fix(test): reject i18n report flag values 2026-06-21 18:37:35 +02:00
Vincent Koc
d460f00eb9 fix(test): reject metadata ref flags 2026-06-21 18:33:10 +02:00
Vincent Koc
b83dce7b33 fix(test): reject rpc rtt flag values 2026-06-21 18:28:28 +02:00
Vincent Koc
d3c907193f fix(test): route qa otel smoke parser 2026-06-21 18:26:26 +02:00
Vincent Koc
b47c930e7e chore(deadcode): trim runtime plugin selection wrappers 2026-06-22 00:24:26 +08:00
Vincent Koc
0befd3c8f2 fix(test): route android release wrappers 2026-06-21 18:19:07 +02:00
Vincent Koc
c2ee9b0be8 fix(gateway): preserve owner MCP tools for agent RPC 2026-06-21 18:14:38 +02:00
Vincent Koc
9d27583190 fix(test): route release signing args 2026-06-21 18:10:35 +02:00
Vincent Koc
04c8c50cc4 fix(test): route testbox env hydration 2026-06-21 18:04:47 +02:00
Vincent Koc
5abf4ce2e2 chore(deadcode): trim reply runtime dead helpers 2026-06-22 00:03:31 +08:00
Vincent Koc
07d5cdec99 fix(test): route ios release wrappers 2026-06-21 17:59:21 +02:00
Vincent Koc
f5f23e739e fix(test): route proxy CA installer 2026-06-21 17:51:44 +02:00
Vincent Koc
0c183283e5 fix(test): route release preflight script 2026-06-21 17:46:59 +02:00
Vincent Koc
514b3365b5 fix(deadcode): move restart sentinels to sqlite 2026-06-21 23:39:38 +08:00
Vincent Koc
2804c24dc6 fix(test): route plugin dependency helpers 2026-06-21 17:32:43 +02:00
Vincent Koc
94c7b5a874 fix(test): route release ref resolver 2026-06-21 17:27:29 +02:00
Vincent Koc
93ec8b8c5c fix(test): route install helper scripts 2026-06-21 17:14:12 +02:00
Vincent Koc
9d83eeaccf fix(test): route release wrapper scripts 2026-06-21 17:08:40 +02:00
Vincent Koc
33eb6ab9de fix(test): route release approval script 2026-06-21 16:59:51 +02:00
Vincent Koc
757ab933f4 fix(test): route release script owners 2026-06-21 16:53:49 +02:00
Vincent Koc
63fdc57b3a fix(test): route mac helper script owners 2026-06-21 16:39:41 +02:00
Vincent Koc
a39a3b74de fix(deadcode): move restart handoffs to sqlite 2026-06-21 22:36:42 +08:00
Vincent Koc
77a859f4ae fix(ci): route mac packaging scripts to macos checks 2026-06-21 16:32:57 +02:00
Peter Lee
84cf64770f fix(whatsapp): wire missing Baileys retry/cache hooks for group message reliability (#94338)
Merged via squash.

Prepared head SHA: ee6de071f7
Co-authored-by: xialonglee <22994703+xialonglee@users.noreply.github.com>
Co-authored-by: mcaxtr <7562095+mcaxtr@users.noreply.github.com>
Reviewed-by: @mcaxtr
2026-06-21 11:32:49 -03:00
Vincent Koc
0ad48dad2c fix(deadcode): move restart intents to sqlite 2026-06-21 22:29:05 +08:00
Marcus Castro
b50a5aebba fix(whatsapp): preserve native quote replies (#95483)
* fix(whatsapp): preserve native quote replies

* feat(plugin-sdk): add quote-reply live transport standard

* test(qa-lab): add WhatsApp quote-reply live scenarios
2026-06-21 11:26:58 -03:00
Vincent Koc
b84665222c fix(ci): regenerate Swift protocol model 2026-06-21 22:19:18 +08:00
snowzlmbot
6441e56465 fix(telegram): materialize streaming progress placeholders (#95183)
* fix(telegram): materialize streaming progress placeholders

* fix(telegram): cancel delayed progress drafts before final

* fix(telegram): satisfy progress placeholder lint

* fix(telegram): cancel delayed draft preview on clear

* refactor(telegram): simplify delayed preview flush

---------

Co-authored-by: snowzlmbot <snowzlmbot@users.noreply.github.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-06-21 19:48:41 +05:30
Vincent Koc
6daabd23f8 fix(deadcode): move config health state to sqlite 2026-06-21 22:07:43 +08:00
Vincent Koc
2f33999898 fix(agents): retain bounded preflight history 2026-06-21 21:58:57 +08:00
Vincent Koc
a60947fb3e fix(agents): keep live tool-result prompts cache-stable 2026-06-21 21:54:35 +08:00
Peter Lee
ac5d219be3 fix(telegram): stop clearing registered webhook on channel restart (#94506)
Prepared head SHA: f721e14a92

Co-authored-by: xialonglee <li.xialong@xydigit.com>
2026-06-21 19:22:49 +05:30
Vincent Koc
ae41b00922 fix(deadcode): move plugin binding approvals to sqlite 2026-06-21 21:52:20 +08:00
Vincent Koc
b28e68e0ce fix(macos): validate DMG layout values 2026-06-21 15:48:52 +02:00
Josh Avant
5d1e649aea fix: route mobile exec approvals to reviewer device (#95175)
* fix: route mobile exec approvals to reviewer device

* fix: surface iOS approval events in foreground

* fix: forward codex approval reviewer device

* test: harden approval reviewer device contract

* test: cover reviewer approval fallback resolvers
2026-06-21 08:47:52 -05:00
Ayaan Zaidi
d6d17709e8 fix(telegram): clear progress draft before tool output (#93002) (thanks @zhangguiping-xydt) 2026-06-21 19:14:57 +05:30
Ayaan Zaidi
6fd6bddb92 style(telegram): trim progress draft dispatch comment 2026-06-21 19:14:57 +05:30
张贵萍0668001030
f4dee99574 fix(telegram): clear progress draft before tool artifacts 2026-06-21 19:14:57 +05:30
张贵萍0668001030
db33402af0 fix(telegram): clear progress draft before verbose tool output 2026-06-21 19:14:57 +05:30
Vincent Koc
088cab5ee4 fix(macos): prefer repo pnpm for packaging 2026-06-21 15:39:17 +02:00
Vincent Koc
bd74a62118 fix(install): use repo pnpm for git installs 2026-06-21 15:34:56 +02:00
Vincent Koc
9f888d95e0 fix(deadcode): move current conversation bindings to sqlite 2026-06-21 21:30:06 +08:00
Vincent Koc
11a2e03bd4 fix(install): detect package manager launcher names 2026-06-21 15:18:57 +02:00
Vincent Koc
5693fcda78 fix(ci): validate artifact package source sha 2026-06-21 15:08:18 +02:00
Vincent Koc
eb00d499d1 fix(deadcode): move update check state to sqlite 2026-06-21 20:57:41 +08:00
Vincent Koc
8a7906c716 fix(qa): reject fractional live token usage 2026-06-21 14:49:29 +02:00
Vincent Koc
c85113e30e fix(qa): escape tool coverage markdown cells 2026-06-21 14:46:38 +02:00
Vincent Koc
bdf81a825f fix(deadcode): move voicewake settings to sqlite 2026-06-21 20:45:17 +08:00
Vincent Koc
d9dfcd6c8a fix(qa): reject impossible confidence counts 2026-06-21 14:38:42 +02:00
Alex Knight
2cafbd0774 fix(plugins): reconcile managed npm root overrides with managed peer pins 2026-06-21 22:33:18 +10:00
Vincent Koc
54b2836eab test(scripts): stabilize gauntlet termination timing 2026-06-21 14:27:10 +02:00
Vincent Koc
5b6f4b2919 fix(test): reject gauntlet flag values 2026-06-21 14:21:16 +02:00
Vincent Koc
c037a34ba7 fix(security): ignore Docker rerun artifact commands 2026-06-21 14:14:57 +02:00
Vincent Koc
12c34fc3a9 fix(security): bound trusted package URL prefixes 2026-06-21 13:51:24 +02:00
Vincent Koc
e366349730 test(deadcode): reuse gateway restart intent writer 2026-06-21 19:49:47 +08:00
Vincent Koc
89a73d08c8 test(deadcode): dedupe wizard prompter helpers 2026-06-21 19:49:46 +08:00
Vincent Koc
e6f41a4df0 fix(test): reject loose env report limits 2026-06-21 13:37:55 +02:00
Vincent Koc
b7fef7fca6 fix(mac): clean failed dSYM merges 2026-06-21 13:32:47 +02:00
Vincent Koc
d1cbe29f3d fix(qa): require sampled Kova metric counts 2026-06-21 13:24:30 +02:00
Alex Knight
9dbc21d283 fix(telegram): materialize rich message line breaks as <br>
Bot API 10.1 rich messages parse structured HTML, so bare newlines
collapse as insignificant whitespace and flatten multi-paragraph replies
and bullet runs into a single line. Materialize logical line breaks as
<br> in prepareTelegramRichHtml -- the shared rich send/edit/draft
chokepoint covering both the Markdown and explicit-HTML text modes --
while keeping newlines literal inside code/pre/math and where they only
separate structural tags (block elements plus table/figure/details
container children, so pretty-printed rich HTML keeps valid markup).

Refs #95409
2026-06-21 21:22:33 +10:00
Vincent Koc
5e86c7eef4 fix(qa): reject non-object mock Anthropic JSON 2026-06-21 13:13:37 +02:00
Vincent Koc
6f3af56952 fix(qa): reject non-object mock OpenAI JSON 2026-06-21 13:10:50 +02:00
Vincent Koc
03ee22666b chore(deadcode): drop retired memory wiki vault metadata 2026-06-21 19:08:39 +08:00
Vincent Koc
0d351b9875 fix(ci): filter ClawSweeper comment dispatches before token minting (#95308)
Merged via squash.

Prepared head SHA: b5389b59e4
Co-authored-by: vincentkoc <25068+vincentkoc@users.noreply.github.com>
Co-authored-by: vincentkoc <25068+vincentkoc@users.noreply.github.com>
Reviewed-by: @vincentkoc
2026-06-21 19:05:08 +08:00
Vincent Koc
f69ba12a37 fix(qa): reject malformed mock OpenAI JSON 2026-06-21 12:58:20 +02:00
Vincent Koc
b796890b97 test(sdk): resolve Windows package taskkill path 2026-06-21 12:52:41 +02:00
Vincent Koc
b5fc9514c0 fix(qa): reject coerced evidence artifact indexes 2026-06-21 12:50:37 +02:00
Vincent Koc
2a140e6e6a fix(daemon): resolve Windows scheduled-task tools 2026-06-21 12:45:49 +02:00
Vincent Koc
6e5f4d685e chore(deadcode): remove stale readability compare script 2026-06-21 18:43:56 +08:00
Vincent Koc
600bace853 fix(qa): validate QA bus poll numbers 2026-06-21 12:41:22 +02:00
Vincent Koc
b8a5dac1a2 fix(qa-lab): resolve Windows PowerShell path 2026-06-21 12:40:57 +02:00
Vincent Koc
15c880aeff fix(infra): share trusted Windows process argv lookup 2026-06-21 12:31:27 +02:00
Vincent Koc
3a53eb5d77 test(deadcode): remove stale heartbeat transcript prune test 2026-06-21 18:25:28 +08:00
Vincent Koc
66e5cfdd86 fix(qa): reject QA Lab host port collisions 2026-06-21 12:23:52 +02:00
Vincent Koc
c4facb2bb3 fix(infra): resolve Windows port inspection tools 2026-06-21 12:22:18 +02:00
Vincent Koc
a70b34a3cb fix(qa-lab): resolve Windows node lookup tool 2026-06-21 12:14:39 +02:00
Vincent Koc
59bf85c586 chore(deadcode): remove stale cron usage helper 2026-06-21 18:12:36 +08:00
Vincent Koc
e486a1d1cf test(scripts): route Codex install assertions 2026-06-21 12:11:01 +02:00
Vincent Koc
72b9bc7303 fix(core): resolve Windows PATH locator tools 2026-06-21 12:09:33 +02:00
Vincent Koc
d3b44442f6 fix(infra): resolve Windows binary lookup tool 2026-06-21 12:02:02 +02:00
Vincent Koc
6ddbcbd460 chore(deadcode): remove stale proof scripts 2026-06-21 18:00:25 +08:00
Vincent Koc
7bd4aab21f test(scripts): route fixture common helper 2026-06-21 11:55:25 +02:00
Vincent Koc
a4c8b17b9e test(scripts): route Parallels lib helpers 2026-06-21 11:52:46 +02:00
Vincent Koc
d87f8325d0 test(agents): fix tools-manager mock typing 2026-06-21 11:49:00 +02:00
Vincent Koc
a5fde9119c fix(agents): resolve Windows extraction tools 2026-06-21 11:49:00 +02:00
Vincent Koc
e6c899dfa5 chore(deadcode): dedupe internal event formatting 2026-06-21 17:47:29 +08:00
Vincent Koc
425f512897 test(scripts): route ClawHub fixture server 2026-06-21 11:42:29 +02:00
Vincent Koc
735d70d9db test(extensions): pin Windows taskkill test roots 2026-06-21 11:37:59 +02:00
Vincent Koc
15300291ed fix(qa-matrix): resolve Windows taskkill path 2026-06-21 11:37:59 +02:00
Vincent Koc
eb7789c8cb test(scripts): route auth profile store assertions 2026-06-21 11:32:59 +02:00
Vincent Koc
f19052b3f3 test(scripts): route doctor install wrapper helper 2026-06-21 11:30:45 +02:00
Vincent Koc
61d1fd1f72 fix(qa-lab): resolve Windows taskkill path 2026-06-21 11:26:30 +02:00
Vincent Koc
1435fc123f test(scripts): route onboard config helpers 2026-06-21 11:23:08 +02:00
Vincent Koc
6c4028e073 fix(scripts): resolve Windows cmd shim launcher 2026-06-21 11:15:14 +02:00
Vincent Koc
9242137ca7 test(scripts): route config reload metadata helper 2026-06-21 11:13:13 +02:00
Vincent Koc
35d7cb0bff chore(deadcode): remove stale qa-matrix wrappers 2026-06-21 17:10:35 +08:00
Vincent Koc
c000e4811d fix(scripts): resolve Windows tools in kitchen sink walk 2026-06-21 11:06:56 +02:00
Vincent Koc
d25549f142 test(scripts): route plugin fixture commands 2026-06-21 11:04:38 +02:00
Vincent Koc
a192b2ea52 fix(windows): resolve taskkill in core spawns 2026-06-21 10:57:41 +02:00
Vincent Koc
7975ec0b11 test(scripts): route incremental line reader 2026-06-21 10:54:09 +02:00
Vincent Koc
e9b694ef9c fix(windows): resolve process inspection tools 2026-06-21 10:47:42 +02:00
Vincent Koc
3b332fd0a4 chore(deadcode): remove stale copilot doctor probes 2026-06-21 16:42:27 +08:00
Vincent Koc
7dd01d15c5 fix(windows): resolve cmd handoff path 2026-06-21 10:41:25 +02:00
Vincent Koc
675c56692a test(scripts): route mock OpenAI fixture helper 2026-06-21 10:31:00 +02:00
Vincent Koc
3f597619c8 test(scripts): route fixture config helper 2026-06-21 10:27:08 +02:00
Vincent Koc
91531ba35c fix(release): resolve taskkill in cross-os checks 2026-06-21 10:24:19 +02:00
Vincent Koc
206bbb01b0 fix(docs): resolve taskkill in i18n codex helper 2026-06-21 10:19:05 +02:00
Vincent Koc
c9758bf2a0 test(scripts): cover e2e text file utilities 2026-06-21 10:16:09 +02:00
Vincent Koc
282eb74128 fix(i18n): resolve taskkill in control ui runner 2026-06-21 10:14:00 +02:00
Vincent Koc
9940110b88 fix(telegram): resolve taskkill in crabbox proof 2026-06-21 10:10:27 +02:00
Vincent Koc
73b35cc3ca fix(bench): resolve taskkill in gateway child cleanup 2026-06-21 10:06:48 +02:00
Vincent Koc
ac0537e363 fix(dev): resolve taskkill in tui pty watcher 2026-06-21 10:02:31 +02:00
Vincent Koc
0321c04663 chore(deadcode): remove stale web artifact helpers 2026-06-21 16:00:38 +08:00
Vincent Koc
78b717a54c fix(plugins): resolve taskkill in bundled runtime smoke 2026-06-21 09:56:30 +02:00
Vincent Koc
7b00fd6c45 test(scripts): route plugin update registry helper 2026-06-21 09:54:59 +02:00
Vincent Koc
1f1155597b fix(telegram): resolve taskkill in credential helper 2026-06-21 09:52:23 +02:00
Vincent Koc
e9e42d5db4 fix(secret-provider): resolve taskkill in e2e cleanup 2026-06-21 09:49:07 +02:00
Vincent Koc
bc2f4ce923 test(scripts): route npm telegram runner 2026-06-21 09:47:52 +02:00
Vincent Koc
7dbae1b2cd fix(scripts): resolve taskkill in boundary artifacts 2026-06-21 09:44:42 +02:00
Vincent Koc
63f2c56222 test(scripts): route e2e helper owners 2026-06-21 09:43:00 +02:00
Vincent Koc
675cae58d7 fix(build): resolve taskkill in tsdown wrapper 2026-06-21 09:40:53 +02:00
Vincent Koc
c372f6ef0b fix(plugins): keep extension tests on public boundaries 2026-06-21 15:39:51 +08:00
Vincent Koc
1d4b712f9a fix(scripts): resolve taskkill in startup metadata 2026-06-21 09:37:35 +02:00
Vincent Koc
e016f0b496 fix(scripts): resolve taskkill in test group report 2026-06-21 09:34:05 +02:00
Vincent Koc
f8d2c4b25a chore(deadcode): remove stale migrate argv wrapper 2026-06-21 15:32:56 +08:00
Vincent Koc
b15f745a60 fix(package): resolve taskkill from system32 in candidate runner 2026-06-21 09:28:31 +02:00
Vincent Koc
8c9c8aad2e fix(qalab): resolve taskkill from system32 in cleanup probes 2026-06-21 09:24:16 +02:00
Vincent Koc
0a7b009647 fix(rpc): resolve taskkill from system32 in script probes 2026-06-21 09:18:06 +02:00
Vincent Koc
fc8542b377 chore(deadcode): remove stale internal exports 2026-06-21 15:14:36 +08:00
Vincent Koc
37a4b565ea test(scripts): mirror declaration route owners 2026-06-21 09:07:10 +02:00
Vincent Koc
6b0210a5fd fix(scripts): resolve taskkill from system32 2026-06-21 09:03:40 +02:00
Vincent Koc
c22e300084 fix(scripts): taskkill managed children via system32 on windows 2026-06-21 08:57:25 +02:00
Vincent Koc
5134dd0c54 test(scripts): route package runner declarations 2026-06-21 08:55:18 +02:00
Vincent Koc
830691b201 fix(memory-host-sdk): taskkill qmd process trees on windows 2026-06-21 08:51:36 +02:00
Vincent Koc
ab39bab52a chore(deadcode): dedupe session lineage patching 2026-06-21 14:48:50 +08:00
Vincent Koc
3f4d1cfcce test(scripts): run macOS app tests for app changes 2026-06-21 08:43:59 +02:00
Vincent Koc
06574920dd fix(sdk): taskkill package e2e trees on windows 2026-06-21 08:33:36 +02:00
Vincent Koc
d46b64df66 test(scripts): gate runtime sidecar baseline changes 2026-06-21 08:28:55 +02:00
Vincent Koc
b06e2f9149 fix(qa-matrix): taskkill scenario cli trees on windows 2026-06-21 08:25:25 +02:00
Vincent Koc
b574da57cf chore(deadcode): share levenshtein distance helper 2026-06-21 14:23:43 +08:00
Dallin Romney
bfe0caefd1 test: route broad flow tests out of unit-fast (#95499) 2026-06-20 23:22:39 -07:00
Vincent Koc
0004cfd59e test(scripts): route prompt snapshot helper changes 2026-06-21 08:16:47 +02:00
Vincent Koc
604aa30189 fix(qa): taskkill lifecycle probe trees on windows 2026-06-21 08:12:29 +02:00
Dallin Romney
5dd30c3995 test: fold HTTP API script proof into QA Lab (#94700)
* test: fold HTTP API script proof into QA Lab

* test: remove folded HTTP API script tests

* test: relax QA native scenario catalog inventory

* test: trim folded QA Lab script cruft

* test: align folded QA coverage ids

* test: keep native QA evidence out of parity tiers

* test: update mirrored QA routing expectation

* test: preserve chat tools profile build guard

* test: avoid overclaiming gateway tool API coverage

* test: pin folded QA coverage ids
2026-06-20 23:10:35 -07:00
Vincent Koc
5b22409389 fix(qa-lab): taskkill gateway children on windows 2026-06-21 08:07:00 +02:00
Vincent Koc
b970d57175 fix(qa-lab): taskkill timed-out cli trees on windows 2026-06-21 08:01:55 +02:00
Dallin Romney
9ab8e466d2 test(qa): make release scorecard categories explicit (#95406) 2026-06-20 23:01:23 -07:00
Vincent Koc
c43822077a fix(qa-lab): force taskkill scenario trees on windows 2026-06-21 07:56:03 +02:00
Vincent Koc
8797564254 chore(deadcode): share deferred test helper 2026-06-21 13:53:24 +08:00
Vincent Koc
273eb88874 fix(runtime-smoke): force taskkill bundled trees on windows 2026-06-21 07:50:42 +02:00
Vincent Koc
140a2fa520 fix(crabbox): force taskkill telegram proof trees on windows 2026-06-21 07:46:57 +02:00
Vincent Koc
c8b48c78d0 fix(qa): force taskkill telegram credential trees on windows 2026-06-21 07:44:10 +02:00
Vincent Koc
29033e67af fix(bench): force taskkill gateway bench trees on windows 2026-06-21 07:41:36 +02:00
Vincent Koc
e9a47fe554 test(scripts): route setup pnpm action helper 2026-06-21 07:40:06 +02:00
Vincent Koc
2f213a1606 test(scripts): route github yaml pinning guards 2026-06-21 07:40:06 +02:00
Vincent Koc
992ddf6310 test(scripts): cover workflow docker routes 2026-06-21 07:40:06 +02:00
Vincent Koc
7b259bd2a4 test(scripts): route github action metadata 2026-06-21 07:40:06 +02:00
Vincent Koc
e91ca8df86 test(scripts): route docs spellcheck config 2026-06-21 07:40:06 +02:00
Vincent Koc
af3e509ab8 fix(scripts): remove private auth monitor defaults 2026-06-21 07:40:06 +02:00
Vincent Koc
dec76bb5eb test(scripts): route pr wrapper scripts 2026-06-21 07:40:05 +02:00
Vincent Koc
862ef1cec1 test(scripts): route doctor switch shims 2026-06-21 07:40:05 +02:00
Vincent Koc
486c9e6ba3 test(scripts): route podman template metadata 2026-06-21 07:40:05 +02:00
Vincent Koc
b2d78abe94 test(scripts): route extensionless helper scripts 2026-06-21 07:40:05 +02:00
Vincent Koc
2f38b5aa2e test(scripts): route dockerfile metadata changes 2026-06-21 07:40:05 +02:00
Vincent Koc
4d17a52924 fix(scripts): force taskkill boundary node steps on windows 2026-06-21 07:36:02 +02:00
Vincent Koc
4b2298e8cb fix(package): force taskkill candidate runner trees on windows 2026-06-21 07:33:04 +02:00
Vincent Koc
f640ca11f9 fix(scripts): force taskkill run-with-env trees on windows 2026-06-21 07:30:11 +02:00
Vincent Koc
2029f87f29 fix(scripts): force taskkill managed trees on windows 2026-06-21 07:26:46 +02:00
Vincent Koc
ca1aa33eba fix(rpc): force taskkill rtt gateway trees on windows 2026-06-21 07:23:54 +02:00
Vincent Koc
5b212162d3 fix(rpc): force taskkill kitchen sink trees on windows 2026-06-21 07:21:04 +02:00
Vincent Koc
3df5207389 fix(testing): force taskkill group report trees on windows 2026-06-21 07:16:31 +02:00
Vincent Koc
78f30a010c fix(build): kill startup metadata trees on windows 2026-06-21 07:13:29 +02:00
Vincent Koc
83785a6e79 fix(build): kill tsdown trees on windows 2026-06-21 07:09:21 +02:00
Vincent Koc
195890f815 fix(dev): kill tui pty watch trees on windows 2026-06-21 07:06:48 +02:00
Vincent Koc
0f8df48a91 fix(release): forward-port 2026.6.9 closeout fixes 2026-06-21 13:05:11 +08:00
Vincent Koc
7b28b73e78 fix(e2e): preserve secret proof windows tree cleanup 2026-06-21 06:59:18 +02:00
Vincent Koc
3650766f26 fix(rpc): kill kitchen sink trees on windows 2026-06-21 06:53:14 +02:00
Vincent Koc
eb03d0ee2b chore(deadcode): share session cost totals 2026-06-21 12:52:23 +08:00
Vincent Koc
38c8b0c196 fix(crabbox): kill telegram proof trees on windows 2026-06-21 06:47:25 +02:00
Vincent Koc
0030a192c8 fix(qa): kill telegram credential trees on windows 2026-06-21 06:42:35 +02:00
Vincent Koc
34806b39cd fix(package): kill candidate resolver trees on windows 2026-06-21 06:37:51 +02:00
Vincent Koc
b0f21f8af7 fix(scripts): kill boundary prep trees on windows 2026-06-21 06:34:10 +02:00
Vincent Koc
5af318b95d fix(testing): kill group report trees on windows 2026-06-21 06:29:14 +02:00
Vincent Koc
8d2e6d7686 fix(scripts): kill run-with-env trees on windows 2026-06-21 06:24:47 +02:00
Vincent Koc
b039e949b6 chore(release): close out 2026.6.9 2026-06-21 12:24:15 +08:00
Vincent Koc
2ca5b7c93e fix(mac): retry DMG detach 2026-06-21 12:24:15 +08:00
Vincent Koc
51d1789cea fix(release): terminate command descendants on signal 2026-06-21 12:24:15 +08:00
Vincent Koc
89e73240a1 test(release): align full validation workflow contract 2026-06-21 12:24:15 +08:00
Vincent Koc
f3ee317f71 fix(ci): deduplicate release Telegram validation 2026-06-21 12:24:15 +08:00
Vincent Koc
ecb82f1be9 fix(plugins): restore StepFun ClawHub release 2026-06-21 12:24:14 +08:00
Vincent Koc
f1a48dac18 test(release): stabilize validation contracts 2026-06-21 12:24:14 +08:00
Vincent Koc
cba9c02095 chore(deadcode): share plugin snapshot fingerprint 2026-06-21 12:23:31 +08:00
Peter Steinberger
715dc718fc fix(opencode-go): align Kimi input with runtime 2026-06-21 00:22:52 -04:00
Vincent Koc
85f71f4c8f fix(scripts): kill managed child trees on windows 2026-06-21 06:19:23 +02:00
Peter Steinberger
66f84a9bf1 fix(opencode-go): add current Go models
Co-authored-by: samson1357924 <samson1357924@gmail.com>
2026-06-21 00:06:55 -04:00
Zak
50c2cc6a45 fix(zai): expose GLM-5.2 reasoning levels [AI-assisted] (#94136)
Merged via squash.

Prepared head SHA: 432214158a
Co-authored-by: BorClaw <268442329+BorClaw@users.noreply.github.com>
Co-authored-by: steipete <58493+steipete@users.noreply.github.com>
Reviewed-by: @steipete
2026-06-20 23:57:49 -04:00
Chunyue Wang
e3ccf8743f fix(channels): resolve native /think menu levels via runtime catalog for live-discovered models (#94067)
Merged via squash.

Prepared head SHA: 079347b8b8
Co-authored-by: openperf <80630709+openperf@users.noreply.github.com>
Co-authored-by: steipete <58493+steipete@users.noreply.github.com>
Reviewed-by: @steipete
2026-06-20 23:56:53 -04:00
Vincent Koc
d4c2fa7aed chore(deadcode): drop unused sdk specifier helper 2026-06-21 11:50:51 +08:00
Nik
6c1041339d fix(agents): classify Zhipu GLM overload as overloaded for failover (#93241)
Merged via squash.

Prepared head SHA: db79e94c89
Co-authored-by: 0xghost42 <151941421+0xghost42@users.noreply.github.com>
Co-authored-by: steipete <58493+steipete@users.noreply.github.com>
Reviewed-by: @steipete
2026-06-20 23:49:39 -04:00
huangjianxiong
db54a3268b fix(zai): fall back to manifest baseUrl for synthesized GLM-5 models (#94461)
Merged via squash.

Prepared head SHA: 445a418187
Co-authored-by: Pandah97 <80405497+Pandah97@users.noreply.github.com>
Co-authored-by: steipete <58493+steipete@users.noreply.github.com>
Reviewed-by: @steipete
2026-06-20 23:49:08 -04:00
Vincent Koc
9750d887f5 chore(deadcode): dedupe plugin lookup table types 2026-06-21 11:08:34 +08:00
Vincent Koc
fce586538a chore(deadcode): drop unused i18n config type 2026-06-21 10:45:56 +08:00
1618 changed files with 52738 additions and 37985 deletions

View File

@@ -4,6 +4,7 @@ import { execFileSync } from "node:child_process";
import { readFileSync, writeFileSync } from "node:fs";
const repo = "openclaw/openclaw";
const commitAssociationQueryBatchSize = 20;
const excludedHandles = new Set(["openclaw", "clawsweeper", "claude", "codex", "steipete"]);
const nonEditorialTypes = new Set([
"build",
@@ -618,13 +619,25 @@ function graphql(query) {
let lastError;
for (let attempt = 0; attempt < 5; attempt += 1) {
try {
return githubApi(["graphql", "-f", `query=${query}`]).data;
const response = githubApi(["graphql", "-f", `query=${query}`]);
if (response?.data && typeof response.data === "object") {
return response.data;
}
const errors = Array.isArray(response?.errors)
? response.errors.map((error) => error?.message).filter(Boolean)
: [];
const detail = [...errors, response?.message].filter(Boolean).join("\n");
throw new Error(
detail
? `GitHub GraphQL response did not include data:\n${detail}`
: "GitHub GraphQL response did not include data.",
);
} catch (error) {
lastError = error;
const message = [error?.message, error?.stdout, error?.stderr].filter(Boolean).join("\n");
// Historical ranges batch hundreds of objects; only retry transient transport failures.
if (
!/(?:operation timed out|ECONNRESET|ETIMEDOUT|EAI_AGAIN|TLS handshake timeout|stream error: .*CANCEL|unexpected end of JSON input|upstream connect error|connection termination|error connecting to api\.github\.com|Unexpected token '<')/i.test(
!/(?:operation timed out|ECONNRESET|ETIMEDOUT|EAI_AGAIN|TLS handshake timeout|stream error: .*CANCEL|unexpected end of JSON input|upstream connect error|connection termination|connection reset by peer|error connecting to api\.github\.com|Unexpected token '<'|something went wrong|temporarily unavailable|internal server error|rate limit)/i.test(
message,
)
) {
@@ -657,8 +670,8 @@ function resolveAssociatedPullRequests(commitHashes, targetTimestamp) {
pending.push({ commitHash, cursor: connection.pageInfo.endCursor });
}
}
for (let index = 0; index < commitHashes.length; index += 40) {
const chunk = commitHashes.slice(index, index + 40);
for (let index = 0; index < commitHashes.length; index += commitAssociationQueryBatchSize) {
const chunk = commitHashes.slice(index, index + commitAssociationQueryBatchSize);
const fields = chunk
.map(
(hash, offset) =>

View File

@@ -22,7 +22,7 @@ paths:
- src/plugins/memory-*.ts
- src/gateway/server-startup-memory.ts
- src/commands/doctor-memory-search.ts
- src/commands/doctor-cron-dreaming-payload-migration.ts
- src/commands/doctor/cron/dreaming-payload-migration.ts
paths-ignore:
- "**/node_modules"

View File

@@ -19,7 +19,6 @@ paths:
- src/plugins/bundled-compat.ts
- src/plugins/bundled-dir.ts
- src/plugins/bundled-plugin-metadata.ts
- src/plugins/bundled-public-surface-runtime-root.ts
- src/plugins/plugin-sdk-dist-alias.ts
- src/plugins/captured-registration.ts
- src/plugins/config-activation-shared.ts
@@ -46,7 +45,6 @@ paths:
- src/plugins/runtime-state.ts
- src/plugins/runtime.ts
- src/plugins/sdk-alias.ts
- src/plugins/source-loader.ts
- src/plugins/types.ts
- src/plugins/validation-diagnostics.ts
- src/plugins/web-provider-public-artifacts*.ts

View File

@@ -51,7 +51,6 @@ paths:
- src/plugins/runtime
- src/plugins/runtime-state.ts
- src/plugins/runtime.ts
- src/plugins/source-loader.ts
- src/plugins/update.ts
- src/plugins/validation-diagnostics.ts
- src/plugin-sdk/*entry*.ts

17
.github/labeler.yml vendored
View File

@@ -41,12 +41,6 @@
- any-glob-to-any-file:
- "extensions/google-meet/**"
- "docs/plugins/google-meet.md"
"plugin: meeting-notes":
- changed-files:
- any-glob-to-any-file:
- "extensions/meeting-notes/**"
- "docs/plugins/meeting-notes.md"
- "src/meeting-notes/**"
"plugin: workboard":
- changed-files:
- any-glob-to-any-file:
@@ -109,6 +103,11 @@
- any-glob-to-any-file:
- "extensions/qqbot/**"
- "docs/channels/qqbot.md"
"channel: raft":
- changed-files:
- any-glob-to-any-file:
- "extensions/raft/**"
- "docs/channels/raft.md"
"channel: qa-channel":
- changed-files:
- any-glob-to-any-file:
@@ -252,12 +251,12 @@
- "src/agents/sandbox*.ts"
- "src/commands/sandbox*.ts"
- "src/cli/sandbox-cli.ts"
- "src/docker-setup.test.ts"
- "src/docker-setup.e2e.test.ts"
- "src/config/**/*sandbox*"
- "docs/cli/sandbox.md"
- "docs/gateway/sandbox*.md"
- "docs/install/docker.md"
- "docs/multi-agent-sandbox-tools.md"
- "docs/tools/multi-agent-sandbox-tools.md"
"agents":
- changed-files:
@@ -270,7 +269,7 @@
- ".github/workflows/opengrep-*.yml"
- ".semgrepignore"
- "docs/cli/security.md"
- "docs/gateway/security.md"
- "docs/gateway/security/**"
- "security/**"
"extensions: admin-http-rpc":

View File

@@ -41,11 +41,32 @@ env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
jobs:
# Keep the canonical main queue quiet long enough for a follow-up push to
# cancel this run before it registers the Blacksmith matrix.
runner-admission:
permissions:
contents: read
runs-on: ubuntu-24.04
timeout-minutes: 3
env:
OPENCLAW_MAIN_CI_DEBOUNCE_SECONDS: "90"
steps:
- name: Debounce canonical main pushes
if: github.event_name == 'push' && github.repository == 'openclaw/openclaw' && github.ref == 'refs/heads/main'
run: |
set -euo pipefail
echo "Waiting ${OPENCLAW_MAIN_CI_DEBOUNCE_SECONDS}s for a superseding main push before Blacksmith admission"
sleep "${OPENCLAW_MAIN_CI_DEBOUNCE_SECONDS}"
- name: Admit non-main CI runs immediately
if: github.event_name != 'push' || github.repository != 'openclaw/openclaw' || github.ref != 'refs/heads/main'
run: echo "No canonical main debounce required"
# Preflight: establish routing truth and job matrices once, then let real
# work fan out from a single source of truth.
preflight:
permissions:
contents: read
needs: [runner-admission]
if: github.event_name != 'pull_request' || !github.event.pull_request.draft
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
timeout-minutes: 20
@@ -197,7 +218,7 @@ jobs:
node --input-type=module <<'EOF'
import { appendFileSync } from "node:fs";
import {
createNodeTestShards,
createNodeTestShardBundles,
} from "./scripts/lib/ci-node-test-plan.mjs";
import {
createChannelContractTestShards,
@@ -272,18 +293,22 @@ jobs:
}
}
const compactPullRequest = isCanonicalRepository && eventName === "pull_request";
const nodeTestShards = runNodeFull
? createNodeTestShards({
? createNodeTestShardBundles({
includeReleaseOnlyPluginShards: false,
compact: compactPullRequest,
}).map((shard) => ({
check_name: shard.checkName,
runtime: "node",
task: "test-shard",
shard_name: shard.shardName,
groups: shard.groups,
configs: shard.configs,
includePatterns: shard.includePatterns,
requires_dist: shard.requiresDist,
runner: shard.runner,
timeout_minutes: shard.timeoutMinutes,
}))
: [];
const nodeTestNonDistShards = nodeTestShards.filter((shard) => !shard.requires_dist);
@@ -320,7 +345,14 @@ jobs:
run_checks_windows: runWindows,
checks_windows_matrix: createMatrix(
runWindows
? [{ check_name: "checks-windows-node-test", runtime: "node", task: "test" }]
? [
{
check_name: "checks-windows-node-test",
runtime: "node",
task: "test",
runner: "blacksmith-8vcpu-windows-2025",
},
]
: [],
),
run_macos_node: runMacos,
@@ -354,6 +386,7 @@ jobs:
security-fast:
permissions:
contents: read
needs: [runner-admission]
if: github.event_name != 'pull_request' || !github.event.pull_request.draft
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && 'blacksmith-4vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
timeout-minutes: 20
@@ -558,7 +591,7 @@ jobs:
contents: read
needs: [preflight]
if: needs.preflight.outputs.run_build_artifacts == 'true'
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && 'blacksmith-32vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && 'blacksmith-16vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
timeout-minutes: 20
outputs:
channels-result: ${{ steps.built_artifact_checks.outputs['channels-result'] }}
@@ -819,6 +852,7 @@ jobs:
timeout-minutes: 60
strategy:
fail-fast: false
max-parallel: 8
matrix: ${{ fromJson(needs.preflight.outputs.checks_fast_core_matrix) }}
steps:
- name: Checkout
@@ -908,6 +942,7 @@ jobs:
timeout-minutes: 60
strategy:
fail-fast: false
max-parallel: 8
matrix: ${{ fromJson(needs.preflight.outputs.plugin_contracts_matrix) }}
steps:
- name: Checkout
@@ -988,6 +1023,7 @@ jobs:
timeout-minutes: 60
strategy:
fail-fast: false
max-parallel: 8
matrix: ${{ fromJson(needs.preflight.outputs.channel_contracts_matrix) }}
steps:
- name: Checkout
@@ -1136,10 +1172,11 @@ jobs:
name: ${{ matrix.check_name }}
needs: [preflight]
if: needs.preflight.outputs.run_checks_node_core_nondist == 'true'
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (matrix.runner || 'blacksmith-8vcpu-ubuntu-2404') || 'ubuntu-24.04') }}
timeout-minutes: 60
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (matrix.runner || 'blacksmith-4vcpu-ubuntu-2404') || 'ubuntu-24.04') }}
timeout-minutes: ${{ matrix.timeout_minutes || 60 }}
strategy:
fail-fast: false
max-parallel: 12
matrix: ${{ fromJson(needs.preflight.outputs.checks_node_core_nondist_matrix) }}
steps:
- name: Checkout
@@ -1198,6 +1235,7 @@ jobs:
- name: Run Node test shard
env:
NODE_OPTIONS: --max-old-space-size=8192
OPENCLAW_NODE_TEST_GROUPS_JSON: ${{ toJson(matrix.groups || null) }}
OPENCLAW_NODE_TEST_CONFIGS_JSON: ${{ toJson(matrix.configs) }}
OPENCLAW_NODE_TEST_INCLUDE_PATTERNS_JSON: ${{ toJson(matrix.includePatterns) }}
OPENCLAW_VITEST_SHARD_NAME: ${{ matrix.shard_name }}
@@ -1212,28 +1250,47 @@ jobs:
import { writeFileSync } from "node:fs";
import { join } from "node:path";
const configs = JSON.parse(process.env.OPENCLAW_NODE_TEST_CONFIGS_JSON ?? "[]");
if (!Array.isArray(configs) || configs.length === 0) {
console.error("Missing node test shard configs");
process.exit(1);
}
const includePatterns = JSON.parse(process.env.OPENCLAW_NODE_TEST_INCLUDE_PATTERNS_JSON ?? "null");
const childEnv = { ...process.env };
if (Array.isArray(includePatterns) && includePatterns.length > 0) {
const includeFile = join(
process.env.RUNNER_TEMP ?? ".",
`node-test-include-${process.env.GITHUB_JOB ?? "local"}-${Date.now()}.json`,
const groups = JSON.parse(process.env.OPENCLAW_NODE_TEST_GROUPS_JSON ?? "null");
const plans = Array.isArray(groups) && groups.length > 0
? groups
: [{
configs: JSON.parse(process.env.OPENCLAW_NODE_TEST_CONFIGS_JSON ?? "[]"),
includePatterns: JSON.parse(
process.env.OPENCLAW_NODE_TEST_INCLUDE_PATTERNS_JSON ?? "null",
),
shard_name: process.env.OPENCLAW_VITEST_SHARD_NAME,
}];
for (const plan of plans) {
const configs = plan.configs;
if (!Array.isArray(configs) || configs.length === 0) {
console.error("Missing node test shard configs");
process.exit(1);
}
const childEnv = {
...process.env,
...(plan.shard_name ? { OPENCLAW_VITEST_SHARD_NAME: plan.shard_name } : {}),
};
if (Array.isArray(plan.includePatterns) && plan.includePatterns.length > 0) {
const includeFile = join(
process.env.RUNNER_TEMP ?? ".",
`node-test-include-${process.env.GITHUB_JOB ?? "local"}-${Date.now()}.json`,
);
writeFileSync(includeFile, JSON.stringify(plan.includePatterns), "utf8");
childEnv.OPENCLAW_VITEST_INCLUDE_FILE = includeFile;
} else {
delete childEnv.OPENCLAW_VITEST_INCLUDE_FILE;
}
const result = spawnSync(
"pnpm",
["exec", "node", "scripts/test-projects.mjs", ...configs],
{
env: childEnv,
stdio: "inherit",
},
);
writeFileSync(includeFile, JSON.stringify(includePatterns), "utf8");
childEnv.OPENCLAW_VITEST_INCLUDE_FILE = includeFile;
}
const result = spawnSync("pnpm", ["exec", "node", "scripts/test-projects.mjs", ...configs], {
env: childEnv,
stdio: "inherit",
});
if ((result.status ?? 1) !== 0) {
process.exit(result.status ?? 1);
if ((result.status ?? 1) !== 0) {
process.exit(result.status ?? 1);
}
}
EOF
@@ -1248,6 +1305,7 @@ jobs:
timeout-minutes: 20
strategy:
fail-fast: false
max-parallel: 8
matrix:
include:
- check_name: check-guards
@@ -1264,7 +1322,7 @@ jobs:
runner: blacksmith-16vcpu-ubuntu-2404
- check_name: check-dependencies
task: dependencies
runner: blacksmith-8vcpu-ubuntu-2404
runner: blacksmith-4vcpu-ubuntu-2404
- check_name: check-test-types
task: test-types
runner: blacksmith-4vcpu-ubuntu-2404
@@ -1385,30 +1443,39 @@ jobs:
name: ${{ matrix.check_name }}
needs: [preflight]
if: ${{ !cancelled() && always() && needs.preflight.outputs.run_check_additional == 'true' }}
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && 'blacksmith-8vcpu-ubuntu-2404' || 'ubuntu-24.04') }}
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'ubuntu-24.04' || (github.repository == 'openclaw/openclaw' && (matrix.runner || 'blacksmith-4vcpu-ubuntu-2404') || 'ubuntu-24.04') }}
timeout-minutes: 20
strategy:
fail-fast: false
max-parallel: 8
matrix:
include:
- check_name: check-additional-boundaries-a
group: boundaries
boundary_shard: 1/4
runner: blacksmith-8vcpu-ubuntu-2404
- check_name: check-additional-boundaries-bcd
group: boundaries
boundary_shard: 2/4,3/4,4/4
runner: blacksmith-8vcpu-ubuntu-2404
- check_name: check-session-accessor-boundary
group: session-accessor-boundary
runner: blacksmith-4vcpu-ubuntu-2404
- check_name: check-session-transcript-reader-boundary
group: session-transcript-reader-boundary
runner: blacksmith-4vcpu-ubuntu-2404
- check_name: check-additional-extension-channels
group: extension-channels
runner: blacksmith-8vcpu-ubuntu-2404
- check_name: check-additional-extension-bundled
group: extension-bundled
runner: blacksmith-8vcpu-ubuntu-2404
- check_name: check-additional-extension-package-boundary
group: extension-package-boundary
runner: blacksmith-8vcpu-ubuntu-2404
- check_name: check-additional-runtime-topology-architecture
group: runtime-topology-architecture
runner: blacksmith-4vcpu-ubuntu-2404
steps:
- name: Checkout
shell: bash
@@ -1751,7 +1818,7 @@ jobs:
name: ${{ matrix.check_name }}
needs: [preflight]
if: needs.preflight.outputs.run_checks_windows == 'true'
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'windows-2025' || (github.repository == 'openclaw/openclaw' && 'blacksmith-16vcpu-windows-2025' || 'windows-2025') }}
runs-on: ${{ github.event_name == 'workflow_dispatch' && 'windows-2025' || (github.repository == 'openclaw/openclaw' && (matrix.runner || 'blacksmith-8vcpu-windows-2025') || 'windows-2025') }}
timeout-minutes: 60
env:
NODE_OPTIONS: --max-old-space-size=8192
@@ -1763,6 +1830,7 @@ jobs:
shell: bash
strategy:
fail-fast: false
max-parallel: 2
matrix: ${{ fromJson(needs.preflight.outputs.checks_windows_matrix) }}
steps:
- name: Checkout
@@ -2092,6 +2160,7 @@ jobs:
timeout-minutes: 20
strategy:
fail-fast: false
max-parallel: 2
matrix: ${{ fromJson(needs.preflight.outputs.android_matrix) }}
steps:
- name: Checkout

View File

@@ -81,9 +81,27 @@ jobs:
repositories: clawsweeper
permission-contents: write
- name: Pre-filter ClawSweeper comment
id: comment_filter
if: ${{ github.event_name == 'issue_comment' }}
env:
COMMENT_BODY: ${{ github.event.comment.body }}
run: |
set -euo pipefail
if grep -Eiq '(^|[[:space:]])@(clawsweeper|openclaw-clawsweeper)\b(\[bot\])?|(^|[[:space:]])/(clawsweeper|review|autoclose|auto([[:space:]]+|-)?merge)\b' <<< "$COMMENT_BODY"; then
echo "is_command=true" >> "$GITHUB_OUTPUT"
else
echo "is_command=false" >> "$GITHUB_OUTPUT"
fi
- name: Create target comment token
id: target_token
if: ${{ github.event_name == 'issue_comment' && env.HAS_CLAWSWEEPER_APP_PRIVATE_KEY == 'true' }}
if: >-
${{
github.event_name == 'issue_comment' &&
steps.comment_filter.outputs.is_command == 'true' &&
env.HAS_CLAWSWEEPER_APP_PRIVATE_KEY == 'true'
}}
uses: actions/create-github-app-token@1b10c78c7865c340bc4f6099eb2f838309f1e8c3 # v3.1.1
with:
client-id: ${{ env.CLAWSWEEPER_APP_CLIENT_ID }}
@@ -213,7 +231,11 @@ jobs:
fi
- name: Acknowledge and dispatch ClawSweeper comment
if: ${{ github.event_name == 'issue_comment' }}
if: >-
${{
github.event_name == 'issue_comment' &&
steps.comment_filter.outputs.is_command == 'true'
}}
env:
DISPATCH_TOKEN: ${{ steps.token.outputs.token }}
TARGET_TOKEN: ${{ steps.target_token.outputs.token }}
@@ -232,10 +254,6 @@ jobs:
. "$RUNNER_TEMP/github-api-backoff.sh"
body_file="$RUNNER_TEMP/clawsweeper-comment-body.txt"
printf '%s\n' "$COMMENT_BODY" > "$body_file"
if ! grep -Eiq '(^|[[:space:]])@(clawsweeper|openclaw-clawsweeper)\b(\[bot\])?|(^|[[:space:]])/(clawsweeper|review|automerge|autoclose)\b' "$body_file"; then
echo "No ClawSweeper command found in comment."
exit 0
fi
if [ -n "$TARGET_TOKEN" ]; then
err="$(mktemp)"
if GH_TOKEN="$TARGET_TOKEN" gh_api_with_retry -X POST \

View File

@@ -96,7 +96,7 @@ on:
- "src/auto-reply/reply/post-compaction-context.ts"
- "src/auto-reply/reply/queue/**"
- "src/auto-reply/reply/startup-context.ts"
- "src/commands/doctor-cron-dreaming-payload-migration.ts"
- "src/commands/doctor/cron/dreaming-payload-migration.ts"
- "src/commands/doctor-memory-search.ts"
- "src/commands/doctor-session-*.ts"
- "src/commands/session-store-targets.ts"
@@ -257,7 +257,7 @@ jobs:
packages/gateway-protocol/src/*|packages/gateway-protocol/src/**/*|src/gateway/method-scopes.ts|src/gateway/server-methods/*|src/gateway/server-methods.ts|src/gateway/server-methods-list.ts)
gateway=true
;;
packages/memory-host-sdk/*|src/commands/doctor-cron-dreaming-payload-migration.ts|src/commands/doctor-memory-search.ts|src/gateway/server-startup-memory.ts|src/memory/*|src/memory-host-sdk/*)
packages/memory-host-sdk/*|src/commands/doctor/cron/dreaming-payload-migration.ts|src/commands/doctor-memory-search.ts|src/gateway/server-startup-memory.ts|src/memory/*|src/memory-host-sdk/*)
memory=true
;;
src/infra/outbound/base-session-key.ts|src/infra/outbound/delivery-queue*.ts|src/infra/outbound/outbound-session.ts|src/infra/outbound/session-binding*.ts|src/infra/outbound/session-context.ts|src/infra/outbound/targets-session.ts)
@@ -295,7 +295,7 @@ jobs:
src/model-catalog/*|src/plugins/*provider*.ts|src/plugins/capability-provider-runtime.ts|src/plugins/compaction-provider.ts|src/plugins/memory-embedding-provider*.ts|src/plugins/memory-embedding-providers*.ts|src/plugins/migration-provider-runtime.ts|src/plugins/synthetic-auth.runtime.ts|src/plugins/web-fetch-providers*.ts|src/plugins/web-search-providers*.ts)
provider=true
;;
src/plugins/activation-planner.ts|src/plugins/api-builder.ts|src/plugins/bundled-*.ts|src/plugins/captured-registration.ts|src/plugins/config-*.ts|src/plugins/discovery.ts|src/plugins/effective-plugin-ids.ts|src/plugins/externalized-bundled-plugins.ts|src/plugins/installed-plugin-index*.ts|src/plugins/loader*.ts|src/plugins/manifest*.ts|src/plugins/module-export.ts|src/plugins/package-entrypoints.ts|src/plugins/plugin-registry*.ts|src/plugins/public-surface*.ts|src/plugins/registry.ts|src/plugins/registry-types.ts|src/plugins/runtime|src/plugins/runtime/*|src/plugins/runtime-state.ts|src/plugins/runtime.ts|src/plugins/sdk-alias.ts|src/plugins/source-loader.ts|src/plugins/types.ts|src/plugins/validation-diagnostics.ts)
src/plugins/activation-planner.ts|src/plugins/api-builder.ts|src/plugins/bundled-*.ts|src/plugins/captured-registration.ts|src/plugins/config-*.ts|src/plugins/discovery.ts|src/plugins/effective-plugin-ids.ts|src/plugins/externalized-bundled-plugins.ts|src/plugins/installed-plugin-index*.ts|src/plugins/loader*.ts|src/plugins/manifest*.ts|src/plugins/module-export.ts|src/plugins/package-entrypoints.ts|src/plugins/plugin-registry*.ts|src/plugins/public-surface*.ts|src/plugins/registry.ts|src/plugins/registry-types.ts|src/plugins/runtime|src/plugins/runtime/*|src/plugins/runtime-state.ts|src/plugins/runtime.ts|src/plugins/sdk-alias.ts|src/plugins/types.ts|src/plugins/validation-diagnostics.ts)
plugin=true
;;
packages/plugin-package-contract/*|packages/plugin-sdk/*)

View File

@@ -70,7 +70,7 @@ on:
default: ""
type: string
npm_telegram_package_spec:
description: Optional published package spec for the package Telegram E2E lane
description: Optional published package spec for the focused package Telegram E2E rerun
required: false
default: ""
type: string
@@ -95,7 +95,7 @@ on:
default: ""
type: string
npm_telegram_provider_mode:
description: Provider mode for the package Telegram E2E lane
description: Provider mode for the focused package Telegram E2E rerun
required: false
default: mock-openai
type: choice
@@ -103,7 +103,7 @@ on:
- mock-openai
- live-frontier
npm_telegram_scenario:
description: Optional comma-separated Telegram scenario ids for the package Telegram lane
description: Optional comma-separated Telegram scenario ids for the focused package Telegram E2E rerun
required: false
default: ""
type: string
@@ -200,14 +200,16 @@ jobs:
if [[ -n "${RELEASE_PACKAGE_SPEC// }" ]]; then
echo "- Published release package: \`${RELEASE_PACKAGE_SPEC}\`"
fi
if [[ -n "${NPM_TELEGRAM_PACKAGE_SPEC// }" ]]; then
if [[ "$RERUN_GROUP" == "npm-telegram" && -n "${NPM_TELEGRAM_PACKAGE_SPEC// }" ]]; then
echo "- Published-package Telegram E2E: \`${NPM_TELEGRAM_PACKAGE_SPEC}\`"
elif [[ -n "${RELEASE_PACKAGE_SPEC// }" ]]; then
elif [[ "$RERUN_GROUP" == "npm-telegram" && -n "${RELEASE_PACKAGE_SPEC// }" ]]; then
echo "- Published-package Telegram E2E: \`${RELEASE_PACKAGE_SPEC}\`"
elif [[ "$RERUN_GROUP" == "all" && "$RELEASE_PROFILE" == "full" ]]; then
echo "- Package Telegram E2E: parent \`release-package-under-test\` artifact"
elif [[ "$RERUN_GROUP" == "npm-telegram" ]]; then
echo "- Package Telegram E2E: focused rerun requires \`release_package_spec\` or \`npm_telegram_package_spec\`"
elif [[ "$RERUN_GROUP" == "all" || "$RERUN_GROUP" == "release-checks" || "$RERUN_GROUP" == "package" ]]; then
echo "- Package Telegram E2E: OpenClaw Release Checks Package Acceptance"
else
echo "- Package Telegram E2E: skipped unless \`release_profile=full\`, \`release_package_spec\`, or \`npm_telegram_package_spec\` is provided"
echo "- Package Telegram E2E: skipped by rerun group"
fi
if [[ -n "${EVIDENCE_PACKAGE_SPEC// }" ]]; then
echo "- Private evidence package proof: \`${EVIDENCE_PACKAGE_SPEC}\`"
@@ -764,80 +766,10 @@ jobs:
dispatch_and_wait openclaw-release-checks.yml "${args[@]}"
prepare_release_package:
name: Prepare release package artifact
needs: [resolve_target, docker_runtime_assets_preflight]
if: ${{ always() && needs.resolve_target.result == 'success' && inputs.npm_telegram_package_spec == '' && inputs.release_package_spec == '' && inputs.rerun_group == 'all' && inputs.release_profile == 'full' && needs.docker_runtime_assets_preflight.result == 'success' }}
runs-on: ubuntu-24.04
timeout-minutes: 15
permissions:
contents: read
packages: write
outputs:
artifact_name: ${{ steps.artifact.outputs.name }}
package_sha256: ${{ steps.package.outputs.sha256 }}
package_version: ${{ steps.package.outputs.package_version }}
source_sha: ${{ steps.package.outputs.source_sha }}
steps:
- name: Checkout trusted workflow ref
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
persist-credentials: true
ref: ${{ github.ref_name }}
fetch-depth: 0
- name: Set artifact metadata
id: artifact
run: echo "name=release-package-under-test" >> "$GITHUB_OUTPUT"
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
node-version: ${{ env.NODE_VERSION }}
install-bun: "true"
install-deps: "false"
- name: Resolve release package artifact
id: package
shell: bash
env:
PACKAGE_REF: ${{ needs.resolve_target.outputs.sha }}
run: |
set -euo pipefail
node scripts/resolve-openclaw-package-candidate.mjs \
--source ref \
--package-ref "$PACKAGE_REF" \
--output-dir .artifacts/docker-e2e-package \
--output-name openclaw-current.tgz \
--metadata .artifacts/docker-e2e-package/package-candidate.json \
--github-output "$GITHUB_OUTPUT"
digest="$(node -p "JSON.parse(require('fs').readFileSync('.artifacts/docker-e2e-package/package-candidate.json', 'utf8')).sha256")"
version="$(node -p "JSON.parse(require('fs').readFileSync('.artifacts/docker-e2e-package/package-candidate.json', 'utf8')).version")"
source_sha="$(node -p "JSON.parse(require('fs').readFileSync('.artifacts/docker-e2e-package/package-candidate.json', 'utf8')).packageSourceSha")"
echo "source_sha=$source_sha" >> "$GITHUB_OUTPUT"
{
echo "## Release package artifact"
echo
echo "- Artifact: \`release-package-under-test\`"
echo "- Package ref: \`$PACKAGE_REF\`"
echo "- SHA-256: \`$digest\`"
echo "- Version: \`$version\`"
echo "- Source SHA: \`$source_sha\`"
} >> "$GITHUB_STEP_SUMMARY"
- name: Upload release package artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: release-package-under-test
path: |
.artifacts/docker-e2e-package/openclaw-current.tgz
.artifacts/docker-e2e-package/package-candidate.json
if-no-files-found: error
npm_telegram:
name: Run package Telegram E2E
needs: [resolve_target, prepare_release_package]
if: ${{ always() && contains(fromJSON('["all","npm-telegram"]'), inputs.rerun_group) && (inputs.npm_telegram_package_spec != '' || inputs.release_package_spec != '' || (inputs.rerun_group == 'all' && inputs.release_profile == 'full')) }}
needs: [resolve_target]
if: ${{ always() && needs.resolve_target.result == 'success' && inputs.rerun_group == 'npm-telegram' && (inputs.npm_telegram_package_spec != '' || inputs.release_package_spec != '') }}
continue-on-error: ${{ startsWith(github.ref, 'refs/heads/tideclaw/alpha/') }}
runs-on: ubuntu-24.04
timeout-minutes: ${{ inputs.release_profile == 'full' && 360 || 60 }}
@@ -853,8 +785,6 @@ jobs:
CHILD_WORKFLOW_REF: ${{ github.ref_name }}
TARGET_SHA: ${{ needs.resolve_target.outputs.sha }}
PACKAGE_SPEC: ${{ inputs.npm_telegram_package_spec || inputs.release_package_spec }}
PACKAGE_ARTIFACT_NAME: ${{ needs.prepare_release_package.outputs.artifact_name }}
PREPARE_PACKAGE_RESULT: ${{ needs.prepare_release_package.result }}
PROVIDER_MODE: ${{ inputs.npm_telegram_provider_mode }}
SCENARIO: ${{ inputs.npm_telegram_scenario }}
run: |
@@ -883,18 +813,7 @@ jobs:
return "$status"
}
args=(-f package_spec="${PACKAGE_SPEC:-openclaw@beta}" -f harness_ref="$TARGET_SHA" -f provider_mode="$PROVIDER_MODE")
if [[ -z "${PACKAGE_SPEC// }" ]]; then
if [[ "$PREPARE_PACKAGE_RESULT" != "success" || -z "${PACKAGE_ARTIFACT_NAME// }" ]]; then
echo "Full release Telegram requires either npm_telegram_package_spec or a prepared release-package-under-test artifact." >&2
exit 1
fi
args+=(
-f package_artifact_name="$PACKAGE_ARTIFACT_NAME"
-f package_artifact_run_id="${GITHUB_RUN_ID}"
-f package_label="full-release-${TARGET_SHA:0:12}"
)
fi
args=(-f package_spec="$PACKAGE_SPEC" -f harness_ref="$TARGET_SHA" -f provider_mode="$PROVIDER_MODE")
if [[ -n "${SCENARIO// }" ]]; then
args+=(-f scenario="$SCENARIO")
fi

View File

@@ -717,7 +717,6 @@ jobs:
published_upgrade_survivor_baselines: ${{ needs.resolve_target.outputs.run_release_soak == 'true' && 'last-stable-4 2026.4.23 2026.5.2 2026.4.15' || '' }}
published_upgrade_survivor_scenarios: ${{ needs.resolve_target.outputs.run_release_soak == 'true' && 'reported-issues' || '' }}
telegram_mode: mock-openai
telegram_scenarios: telegram-help-command,telegram-commands-command,telegram-tools-compact-command,telegram-whoami-command,telegram-status-command,telegram-other-bot-command-gating,telegram-context-command,telegram-mentioned-message-reply,telegram-long-final-reuses-preview,telegram-mention-gating
secrets:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}

View File

@@ -129,11 +129,28 @@ jobs:
trusted_config="$RUNNER_TEMP/pre-commit-base.yaml"
trusted_zizmor_config="$RUNNER_TEMP/zizmor-base.yml"
if ! git cat-file -e "${BASE_SHA}^{commit}" 2>/dev/null; then
timeout --signal=TERM --kill-after=10s 30s git fetch --no-tags --depth=1 origin \
"+${BASE_SHA}:refs/remotes/origin/security-base" ||
fetch_base_ref() {
local ref="$1"
local target="$2"
local fetch_status
for attempt in 1 2 3; do
timeout --signal=TERM --kill-after=10s 30s git fetch --no-tags --depth=1 origin \
"+refs/heads/${BASE_REF}:refs/remotes/origin/${BASE_REF}"
"+${ref}:${target}" && return 0
fetch_status="$?"
if [ "$fetch_status" != "124" ] && [ "$fetch_status" != "137" ]; then
return "$fetch_status"
fi
if [ "$attempt" = "3" ]; then
return "$fetch_status"
fi
echo "::warning::trusted base fetch for '$ref' timed out on attempt $attempt; retrying"
sleep 5
done
}
if ! git cat-file -e "${BASE_SHA}^{commit}" 2>/dev/null; then
fetch_base_ref "$BASE_SHA" "refs/remotes/origin/security-base" ||
fetch_base_ref "refs/heads/${BASE_REF}" "refs/remotes/origin/${BASE_REF}"
fi
if git cat-file -e "${BASE_SHA}:.pre-commit-config.yaml" 2>/dev/null; then

View File

@@ -6,34 +6,34 @@ Docs: https://docs.openclaw.ai
### Highlights
- **Richer Telegram delivery:** Telegram now sends rich HTML, preserves rich markdown and sticker paths, renders progress drafts and command output more faithfully, and keeps mentions and spooled handlers on the right delivery path. (#93286, #93164, #93124, #93364, #93130, #93088, #93281) Thanks @obviyus, @vincentkoc, @goutamadwant, @kesslerio, @NianJiuZst, @SweetSophia, @Marvinthebored, and @aaajiao.
- **Richer Telegram delivery:** Telegram now sends rich HTML, preserves rich markdown and sticker paths, renders progress drafts and command output more faithfully, normalizes HTML tables safely, and keeps mentions and spooled handlers on the right delivery path. (#93286, #93164, #93124, #93364, #93130, #93002, #93088, #93281, #94891, #94856) Thanks @obviyus, @vincentkoc, @goutamadwant, @kesslerio, @NianJiuZst, @SweetSophia, @Marvinthebored, @aaajiao, @zhangguiping-xydt, @zhangqueping, and @jairrab.
- **More dependable agent recovery:** retries, terminal outcomes, usage after compaction, session history repair, and reply reconciliation now keep more interrupted or partial turns moving toward a visible final result. (#92191, #93073, #93228, #93084, #93469, #93291, #90943) Thanks @ai-hpc, @lml2468, @fuller-stack-dev, @Hollychou924, @leno23, @de1tydev, @425072024, @wuwahe3, @drvoss, @yetval, @sandieman2, and @vincentkoc.
- **A stronger Codex integration:** Codex gains automatic plugin approvals, GPT-5.3 Spark OAuth routing, remote-node `exec` as a dynamic tool, and more reliable app-server teardown and terminal outcomes. (#92625, #89133, #93654, #91767, #93287) Thanks @kevinslin, @VACInc, @vincentkoc, @JPKay-AI, and @aliahnaf2013-max.
- **Standalone official provider plugins:** external provider packages are now first-class npm releases, externally installed channel plugins load at Gateway startup, and StepFun is intentionally npm-only because its ClawHub package name is unavailable. (#93470) Thanks @sunlit-deng, @cxdnicole, and @vincentkoc.
- **Standalone official provider plugins:** external provider packages are now first-class npm releases, externally installed channel plugins load at Gateway startup, and StepFun is available from npm and ClawHub. (#93470) Thanks @sunlit-deng, @cxdnicole, and @vincentkoc.
- **More capable web and native clients:** the Control UI adds a session workspace rail and extension health, iOS adds Watch controls, and Android shows chat context. (#92856, #91952, #93387, #92837) Thanks @Solvely-Colin, @jalehman, @joshavant, and @Tosko4.
- **More useful search and skills:** Codex Hosted Search is available, key-free search providers remain deliberate opt-ins, and ClawHub skill installs retain verified source provenance. (#93446, #93616, #93283, #93506) Thanks @fuller-stack-dev, @davemorin, @momothemage, @nmccready-tars, and @vincentkoc.
### Changes
- Providers and auth: add Codex Hosted Search, improve Gemini CLI OAuth behind proxies, and keep external provider onboarding on current choices and package metadata. (#93446, #92815) Thanks @fuller-stack-dev, @yetval, @EvetteYoung, and @vincentkoc.
- Plugins and installs: externalized official providers publish as independent npm packages, Gateway discovers installed channel plugins at startup, and StepFun installs exclusively from npm. (#93470) Thanks @sunlit-deng, @cxdnicole, and @vincentkoc.
- Plugins and installs: externalized official providers publish as independent npm packages, Gateway discovers installed channel plugins at startup, and StepFun installs from npm or ClawHub. (#93470) Thanks @sunlit-deng, @cxdnicole, and @vincentkoc.
- Dashboard and mobile: add a session workspace rail, plugin health in status, compact cron lists, and iOS Watch controls. (#92856, #91952, #93395, #93387) Thanks @Solvely-Colin, @jalehman, @yu-xin-c, @centralpc, @joshavant, and @vincentkoc.
- Codex and skills: add automatic plugin approvals, preserve ClawHub skill provenance, and expose remote-node execution to Codex when a node is connected. (#92625, #93283, #93654) Thanks @kevinslin, @momothemage, @nmccready-tars, @vincentkoc, and @JPKay-AI.
- QA and release engineering: QA scenarios now use YAML, with broader profile evidence and release coverage for the plugin and channel matrix.
- Codex, observability, and skills: add automatic plugin approvals and SecretRefs, preserve ClawHub skill provenance, add OpenTelemetry log export, and expose remote-node execution to Codex when a node is connected. (#92625, #94324, #93283, #94561, #93654) Thanks @kevinslin, @kevinlin-openai, @momothemage, @nmccready-tars, @jesse-merhi, @vincentkoc, and @JPKay-AI.
- QA and release engineering: QA scenarios now use YAML, with broader profile evidence and release coverage for the plugin and channel matrix. Thanks @vincentkoc.
### Fixes
- Security and privacy: redact secrets from debug/config output, block internal HTTP session overrides, audit open-DM tool exposure, and retain plugin write ownership checks. (#93333, #88496, #93443, #92883, #93353) Thanks @Alix-007, @jason-allen-oneal, @coygeek, @RichardCao, @yu-xin-c, @cjg20ss, @eleqtrizit, and @vincentkoc.
- Agent and session runtime: retry thinking-only and empty post-tool turns, prevent duplicate hook execution, preserve fresh usage through compaction, and repair partial JSON/history artifacts. (#92191, #93073, #93009, #93084, #93469) Thanks @ai-hpc, @lml2468, @fuller-stack-dev, @zenglingbiao, @dertbv, @Hollychou924, @leno23, @de1tydev, @425072024, @wuwahe3, @drvoss, and @vincentkoc.
- Channels and replies: fix Telegram rich delivery and ingress recovery, preserve WhatsApp auth and media error reporting, keep Mattermost thread replies intact, and harden Discord action handling. (#93286, #93364, #93281, #93076, #93334, #93424, #93488) Thanks @obviyus, @NianJiuZst, @mcaxtr, @rushindrasinha, @amknight, @lzyyzznl, @darealgege, and @vincentkoc.
- Agent and session runtime: retry thinking-only and empty post-tool turns, prevent duplicate hook execution, preserve pending subagent delivery, preserve fresh usage through compaction, and repair partial JSON/history artifacts. (#92191, #93073, #93009, #93084, #93469, #94349, #92383, #94257) Thanks @ai-hpc, @lml2468, @fuller-stack-dev, @zenglingbiao, @dertbv, @Hollychou924, @leno23, @de1tydev, @425072024, @wuwahe3, @drvoss, @vincentkoc, @sallyom, @oiGaDio, @Hidetsugu55, and @Nas01010101.
- Channels and replies: fix Telegram rich delivery, table rendering, action-error handling, progress draft cleanup before visible tool output, and ingress recovery; preserve command progress detail across channel adapters; retain WhatsApp opening text after a media failure; keep Mattermost thread replies intact; and harden Discord action handling. (#93286, #93364, #93281, #93002, #93076, #93334, #93424, #93488, #94868, #94891, #94856, #94810, #93823) Thanks @obviyus, @NianJiuZst, @mcaxtr, @zhangguiping-xydt, @rushindrasinha, @amknight, @lzyyzznl, @darealgege, @vincentkoc, @zhangqueping, @jairrab, @ZOOWH, @parveshsaini, and @yetval.
- Storage and migrations: avoid SQLite WAL on network filesystems, clean reindex artifacts, keep setup state out of workspace dot-directories, and import default-agent auth profiles into SQLite. (#93454, #92891, #93182, #93295, #93520, #93156) Thanks @vincentkoc, @ZengWen-DT, @Zeng-wen, @potterdigital, @Alix-007, @Pick-cat, @sallyom, @1qh, and @Tazio7.
- Provider and model behavior: fix Gemini CLI proxy OAuth, restore Codex Spark OAuth routing, correct Bedrock embedding model IDs, and preserve configured defaults in embedded runs. (#92815, #89133, #93452, #93428) Thanks @yetval, @EvetteYoung, @VACInc, @LiuwqGit, @aleck31, @zenglingbiao, @danielgerlag, and @vincentkoc.
- CLI, TUI, and apps: accept global flags after subcommands, keep terminal output and activity indicators visible, preserve CJK IME composition, and refresh stale UI state. (#93455, #93460, #93006, #93427, #93498, #93606) Thanks @ooiuuii, @Alix-007, @ZengWen-DT, @Zeng-wen, @AlethiaQuizForge, @Zhaoqj2016, @liuhao1024, @BrianClaw1955, @vincentkoc, and @NicoBoom13.
- Operations and updates: harden official plugin recovery, restart managed Gateways after failed update handoff, avoid Node-specific npm prefixes, and keep package validation paths reliable. (#93325, #92111, #93650) Thanks @vincentkoc, @yetval, @ofan, and @yaanfpv.
- Operations and updates: harden official plugin recovery, restart managed Gateways after failed update handoff, keep safe cron delivery defaults, avoid Node-specific npm prefixes, and keep package validation paths reliable. (#93325, #92111, #93650, #94453, #91685) Thanks @vincentkoc, @yetval, @ofan, @yaanfpv, @jincheng-xydt, @sallyom, @davectr, and @nxmxbbd.
### Complete contribution record
This audited record covers the complete v2026.6.8..HEAD~1 history: 375 merged PRs. The generation manifest also supplies direct commits as editorial input; the grouped notes above prioritize user impact.
This audited record covers the complete v2026.6.8..HEAD history: 423 merged PRs. The generation manifest also supplies direct commits as editorial input; the grouped notes above prioritize user impact.
#### Pull requests
@@ -57,6 +57,7 @@ This audited record covers the complete v2026.6.8..HEAD~1 history: 375 merged PR
- **PR #88792** fix(state): harden sqlite path caching. Thanks @vincentkoc.
- **PR #93022** fix(gateway): repair usage cost aggregation across agents. Thanks @luke-skywalker-open-claw and @stablegenius49.
- **PR #93020** fix(telegram): cool down transient sendChatAction failures. Related #56096. Thanks @Boulea7 and @sumaiazaman and @Pick-cat and @cal-rufus.
- **PR #93002** fix(telegram): clear progress drafts before visible tool output. Thanks @zhangguiping-xydt.
- **PR #89160** fix(agents): detect truncated API responses to prevent silent session hang. Related #89051. Thanks @joelnishanth and @ArthurusDent.
- **PR #93009** fix(agents): make wrapToolWithBeforeToolCallHook idempotent to prevent double hook execution (fixes #92973). Thanks @zenglingbiao and @dertbv.
- **PR #92991** fix(agents): tolerate missing attribution baseUrl. Related #92974. Thanks @samrusani and @Haderach-Ram.
@@ -175,7 +176,7 @@ This audited record covers the complete v2026.6.8..HEAD~1 history: 375 merged PR
- **PR #90003** feat(policy): cover exec approvals artifact. Thanks @giodl73-repo.
- **PR #93448** fix(guards): allow auth profile sqlite reader. Thanks @amknight.
- **PR #93424** fix(mattermost): keep message tool replies in threads. Thanks @amknight and @vincentkoc.
- **PR #93418** fix(telegram): forward Bot API 10.1 rich_message content to agent. Related #93410. Thanks @xzh-xydt and @vincentkoc and @0pen7ech.
- **PR #93418** fix(telegram): forward Bot API 10.1 rich_message content to agent. Related #93410. Thanks @xzh-icenter and @vincentkoc and @0pen7ech.
- **PR #93175** test(qa): taxonomy profiles: includeAllCategories for release profile, update some coverage. Thanks @RomneyDa.
- **PR #93456** fix(agents): handle string assistant message content. Thanks @vincentkoc.
- **PR #93441** fix(outbound): ignore schema-padded poll metadata on send. Related #43015. Thanks @weichengdeng and @charzhou.
@@ -412,6 +413,53 @@ This audited record covers the complete v2026.6.8..HEAD~1 history: 375 merged PR
- **PR #94658** test(sqlite): use shared temp directory helper. Thanks @vincentkoc.
- **PR #92135** fix(openai-embedding): preserve openai/ prefix for non-native base URLs. Related #92124. Thanks @xialonglee and @Kambrian.
- **PR #93737** refactor: add session maintenance transaction seam. Thanks @jalehman.
- **PR #93685** refactor(auto-reply): add lifecycle storage seams. Thanks @jalehman.
- **PR #94349** fix(agents): preserve pending subagent completion announces. Related #93323. Thanks @sallyom and @oiGaDio.
- **PR #93174** test: fold channel message flows into qa e2e. Thanks @RomneyDa.
- **PR #94093** Prevent Codex thread rotation from losing next-step context. Thanks @VACInc.
- **PR #53920** fix(scripts): avoid mutating tracked auth-monitor template during setup. Thanks @JackWuGlobal.
- **PR #94702** Standardize QA coverage IDs on dotted names. Thanks @RomneyDa.
- **PR #81825** fix(skills/1password): stop forcing tmux for desktop app auth (#52540). Thanks @koshaji and @tylerbittner.
- **PR #94725** fix(doctor): warn on volatile SQLite state. Thanks @vincentkoc.
- **PR #88551** fix(agents): skip auth gate for CLI-owned transport. Thanks @yu-xin-c.
- **PR #88581** feat(commands): add /name to rename the current session from chat. Thanks @BSG2000.
- **PR #94324** feat(codex): support app-server SecretRefs. Thanks @kevinlin-openai and @kevinslin.
- **PR #90882** fix: add self-knowledge docs rule to system prompt. Related #90713. Thanks @SutraHsing.
- **PR #94684** fix: #80507 show dry-run output for message send/poll. Thanks @lzyyzznl and @YB0y.
- **PR #93823** fix(whatsapp): keep opening text chunk when first media fails on multi-chunk reply. Thanks @yetval.
- **PR #89203** refactor: route SDK session compatibility through seam. Thanks @jalehman.
- **PR #94453** fix: default cron runMode to "due" instead of "force" (#94270). Thanks @jincheng-xydt and @sallyom and @davectr.
- **PR #94746** fix(note): prevent clack from re-breaking copy-sensitive tokens. Related #94730. Thanks @xzh-icenter and @berkgungor.
- **PR #89904** refactor: route sdk session compatibility through accessor. Thanks @jalehman.
- **PR #86719** fix(skills): retarget stale plugin skill symlinks. Related #85925. Thanks @stevenepalmer and @shakkernerd.
- **PR #94337** fix(tui): show 0 not ? for fresh-session context tokens in footer. Thanks @mushuiyu886.
- **PR #94539** fix(android): group settings by intent. Thanks @Tosko4.
- **PR #92383** fix(gateway): never return an empty chat.history transcript. Thanks @Hidetsugu55.
- **PR #92574** test(browser): cover action-input CLI request bodies. Related #83877. Thanks @yu-xin-c and @davinci282828.
- **PR #92873** test(diffs): add viewerState, toolbar toggle, shadow root, and hydrateProps tests (fixes #83915). Thanks @liuhao1024 and @davinci282828.
- **PR #94257** fix(sessions): preserve Media\* index alignment when reading user-turn fields. Thanks @Nas01010101.
- **PR #94756** fix(codex): bound turn/start text when context budget is non-positive. Related #94748. Thanks @Nas01010101.
- **PR #94729** fix(skills/trello): add curl to requires.bins to match body examples (fixes #94727). Thanks @liuhao1024 and @berkgungor.
- **PR #94790** feat(slack): log INFO receipt for inbound app_mention events. Related #94691. Thanks @ZengWen-DT and @BryceMurray.
- **PR #81696** fix: guard tool event callbacks (AI-assisted). Thanks @enjoylife1243.
- **PR #94809** chore: forward-port alpha release fixes.
- **PR #94612** fix(macos): open NSOpenPanel for embedded Control UI file inputs (#94468). Thanks @bbblending and @DINGDANGMAOUP.
- **PR #89806** fix(feishu): avoid axios interceptor internals. Related #83913. Thanks @sweetcornna and @davinci282828.
- **PR #91923** fix(ios): clean up notification settings state. Thanks @zats.
- **PR #91345** fix: suggest close CLI commands. Related #83999. Thanks @glenn-agent and @HannesOberreiter.
- **PR #94561** Add stdout diagnostics OTEL log exporter. Thanks @jesse-merhi.
- **PR #91013** fix(gateway): ignore stale abort markers for fresh chat events. Related #91012. Thanks @nxmxbbd.
- **PR #89279** fix(tasks): deliver ACP completions to bound Discord threads. Related #84022. Thanks @anyech and @h-mascot.
- **PR #91656** test(cron): expand parseAbsoluteTimeMs test coverage to 39 cases. Related #91654. Thanks @SpecialLeon.
- **PR #94810** fix(telegram): classify sendChatAction 401 by structured error_code, not bare substring match. Related #94787. Thanks @ZOOWH and @parveshsaini.
- **PR #94737** fix(reply): clarify provider internal error copy. Thanks @snowzlmbot.
- **PR #94868** fix(channels): preserve command progress detail. Thanks @vincentkoc.
- **PR #94891** fix(telegram): send progress previews as html text. Thanks @obviyus.
- **PR #94683** fix(outbound): keep direct-only targets out of group sessions. Related #92384. Thanks @scotthuang and @haiwei01.
- **PR #92477** fix: migrate watch app to single-target app (Xcode 27+ compat). Thanks @zats and @joshavant.
- **PR #94812** test(perf): compare saved CLI startup benchmarks. Thanks @FelixIsaac.
- **PR #94856** fix(telegram): normalize all HTML tables before entity-escaping in rich messages. Related #94317. Thanks @zhangqueping and @jairrab.
- **PR #91685** fix(cron): refuse keyless implicit isolated cron delivery inherited from shared agent-main bucket. Thanks @nxmxbbd.
## 2026.6.8

View File

@@ -2,5 +2,5 @@
# Source of truth: apps/android/version.json
# Generated by scripts/android-sync-versioning.ts.
OPENCLAW_ANDROID_VERSION_NAME=2026.6.2
OPENCLAW_ANDROID_VERSION_CODE=2026060201
OPENCLAW_ANDROID_VERSION_NAME=2026.6.9
OPENCLAW_ANDROID_VERSION_CODE=2026060901

View File

@@ -1,3 +1 @@
OpenClaw is now available on Android.
Connect to your OpenClaw Gateway to chat with your assistant, use realtime Talk mode, review approvals, and bring Android device capabilities like camera, location, screen, and notifications into your private automation workflows.
Maintenance update for the current OpenClaw Android release.

View File

@@ -1,4 +1,4 @@
{
"version": "2026.6.2",
"versionCode": 2026060201
"version": "2026.6.9",
"versionCode": 2026060901
}

View File

@@ -1,5 +1,13 @@
# OpenClaw iOS Changelog
## 2026.6.9 - 2026-06-20
Maintenance update for the current OpenClaw release.
- Added Apple Watch controls for common agent actions.
- Improved Gateway setup, notification settings, and share-extension identity handling.
- Updated the Watch app integration for current Xcode compatibility.
## 2026.6.2 - 2026-06-02
OpenClaw is now available on iPhone.

View File

@@ -2,8 +2,8 @@
// Source of truth: apps/ios/version.json
// Generated by scripts/ios-sync-versioning.ts.
OPENCLAW_IOS_VERSION = 2026.6.2
OPENCLAW_MARKETING_VERSION = 2026.6.2
OPENCLAW_IOS_VERSION = 2026.6.9
OPENCLAW_MARKETING_VERSION = 2026.6.9
OPENCLAW_BUILD_VERSION = 1
#include? "../build/Version.xcconfig"

View File

@@ -54,6 +54,8 @@ struct SettingsProTab: View {
@State var locationStatusText: String?
@State var previousLocationModeRaw: String = OpenClawLocationMode.off.rawValue
@State var notificationStatus: SettingsNotificationStatus = .checking
@State var isRequestingNotificationAuthorization = false
@State var showNotificationRelayDisclosure = false
@State var diagnosticsLastRunText = "Not run"
@State var diagnosticsIssueCount: Int?
@State var showTalkIssueDetails = false
@@ -61,15 +63,18 @@ struct SettingsProTab: View {
let initialRoute: SettingsRoute?
let directRoute: SettingsRoute?
let headerLeadingAction: OpenClawSidebarHeaderAction?
let onRouteChange: ((SettingsRoute?) -> Void)?
init(
initialRoute: SettingsRoute? = nil,
directRoute: SettingsRoute? = nil,
headerLeadingAction: OpenClawSidebarHeaderAction? = nil)
headerLeadingAction: OpenClawSidebarHeaderAction? = nil,
onRouteChange: ((SettingsRoute?) -> Void)? = nil)
{
self.initialRoute = initialRoute
self.directRoute = directRoute
self.headerLeadingAction = headerLeadingAction
self.onRouteChange = onRouteChange
}
var body: some View {
@@ -117,6 +122,7 @@ struct SettingsProTab: View {
self.refreshNotificationSettings()
self.applyPendingGatewaySetupLinkIfNeeded()
self.applyInitialRouteIfNeeded()
self.notifyRouteChange()
}
.onChange(of: self.scenePhase) { _, phase in
if phase == .active {
@@ -153,6 +159,9 @@ struct SettingsProTab: View {
.onChange(of: self.appModel.gatewaySetupRequestID) { _, _ in
self.applyPendingGatewaySetupLinkIfNeeded()
}
.onChange(of: self.navigationPath) { _, _ in
self.notifyRouteChange()
}
}
private func settingsModalPresentation(_ content: some View) -> some View {
@@ -217,6 +226,19 @@ struct SettingsProTab: View {
} message: {
Text(self.scannerError ?? "")
}
.alert("Enable OpenClaw Hosted Push Relay?", isPresented: self.$showNotificationRelayDisclosure) {
Button("Continue") {
self.requestNotificationAuthorizationFromSettings()
}
Button("Not Now", role: .cancel) {}
} message: {
Text(self.notificationRelayDisclosureMessage)
}
}
func openNotificationsRouteFromApprovals() {
guard self.directRoute == nil else { return }
self.navigationPath = [.notifications]
}
private func applyInitialRouteIfNeeded() {
@@ -225,4 +247,12 @@ struct SettingsProTab: View {
guard self.navigationPath != [initialRoute] else { return }
self.navigationPath = [initialRoute]
}
private func notifyRouteChange() {
if let directRoute {
self.onRouteChange?(directRoute)
return
}
self.onRouteChange?(self.navigationPath.last)
}
}

View File

@@ -426,15 +426,30 @@ extension SettingsProTab {
self.openNotificationSettings()
return
}
guard self.notificationStatus == .notSet else { return }
if PushBuildConfig.current.usesOpenClawHostedRelay {
self.showNotificationRelayDisclosure = true
return
}
self.requestNotificationAuthorizationFromSettings()
}
func requestNotificationAuthorizationFromSettings() {
guard !self.isRequestingNotificationAuthorization else { return }
self.isRequestingNotificationAuthorization = true
Task {
let granted = await (try? UNUserNotificationCenter.current().requestAuthorization(options: [
.alert,
.badge,
.sound,
])) ?? false
let settings = await UNUserNotificationCenter.current().notificationSettings()
await MainActor.run {
self.notificationStatus = granted ? .allowed : .notAllowed
self.isRequestingNotificationAuthorization = false
self.notificationStatus = SettingsNotificationStatus(settings.authorizationStatus)
guard granted, self.notificationStatus.allowsNotifications else { return }
UIApplication.shared.registerForRemoteNotifications()
}
}
}
@@ -661,6 +676,9 @@ extension SettingsProTab {
if self.appModel.isAppleReviewDemoModeEnabled {
return "Live gateway requests are disabled in demo mode."
}
if self.notificationsNeedAttention {
return "Foreground approvals still appear while OpenClaw is connected."
}
return self.gatewayConnected ? "Gateway requests will appear here." : "Connect to the gateway."
}
@@ -700,7 +718,19 @@ extension SettingsProTab {
}
var approvalsDetail: String {
self.pendingApproval == nil ? "No approvals waiting" : "1 request waiting"
if self.notificationsNeedAttention {
return self.pendingApproval == nil ? "Notifications off" : "1 waiting, notifications off"
}
return self.pendingApproval == nil ? "No approvals waiting" : "1 request waiting"
}
var notificationsNeedAttention: Bool {
switch self.notificationStatus {
case .allowed, .checking:
false
case .notAllowed, .notSet, .unknown:
true
}
}
var approvalItems: [SettingsApprovalItem] {
@@ -771,4 +801,33 @@ extension SettingsProTab {
var notificationActionText: String {
self.notificationStatus.actionTitle
}
var notificationStatusDetail: String {
switch self.notificationStatus {
case .checking:
"Checking iOS notification permission."
case .allowed:
"OpenClaw can show approval prompts and event alerts when the app is not active."
case .notAllowed:
"Notifications have been denied. Enable them in iOS Settings."
case .notSet:
"Enable notifications to receive approval prompts and event alerts outside the app."
case .unknown:
"OpenClaw cannot determine the current notification permission state."
}
}
var notificationRelayDetail: String {
if PushBuildConfig.current.usesOpenClawHostedRelay {
return """
This build uses OpenClaw's hosted push relay at ios-push-relay.openclaw.ai for notification \
delivery data.
"""
}
return "This build is not configured to use OpenClaw's hosted push relay."
}
var notificationRelayDisclosureMessage: String {
"Enabling this sends delivery data through OpenClaw's hosted push relay."
}
}

View File

@@ -308,15 +308,57 @@ extension SettingsProTab {
self.detailStatusCard(
icon: "checkmark.shield.fill",
title: "Approvals",
detail: self.pendingApproval == nil ? "No gateway actions are waiting for review." :
"Review the pending gateway action.",
value: self.pendingApproval == nil ? "clear" : "1 waiting",
color: self.pendingApproval == nil ? OpenClawBrand.ok : OpenClawBrand.warn)
detail: self.notificationsNeedAttention
? "Out-of-app approval alerts need notification permission."
: (self.pendingApproval == nil ? "No gateway actions are waiting for review." :
"Review the pending gateway action."),
value: self.notificationsNeedAttention
? "Alerts Off"
: (self.pendingApproval == nil ? "clear" : "1 waiting"),
color: self.notificationsNeedAttention ? OpenClawBrand.warn :
(self.pendingApproval == nil ? OpenClawBrand.ok : OpenClawBrand.warn))
if self.notificationsNeedAttention {
self.approvalNotificationsWarningCard
}
self.approvalsReviewCard
}
}
var approvalNotificationsWarningCard: some View {
ProCard(radius: SettingsLayout.cardRadius) {
VStack(alignment: .leading, spacing: 12) {
HStack(alignment: .top, spacing: 12) {
ProIconBadge(systemName: "bell.slash.fill", color: OpenClawBrand.warn)
VStack(alignment: .leading, spacing: 4) {
Text("Notifications are off")
.font(.subheadline.weight(.semibold))
Text(
"""
Enable Notifications to receive approval notifications while OpenClaw is not open.
""")
.font(.caption)
.foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true)
}
}
if self.directRoute == nil {
Button {
self.openNotificationsRouteFromApprovals()
} label: {
Label("Open Notifications", systemImage: "bell.badge")
.frame(maxWidth: .infinity)
}
.buttonStyle(.bordered)
.controlSize(.small)
}
}
}
.padding(.horizontal, OpenClawProMetric.pagePadding)
}
var approvalsReviewCard: some View {
ProCard(radius: SettingsLayout.cardRadius) {
VStack(alignment: .leading, spacing: 12) {
@@ -490,7 +532,7 @@ extension SettingsProTab {
self.detailStatusCard(
icon: "bell",
title: "Notifications",
detail: "Approvals and event alerts from OpenClaw.",
detail: self.notificationStatusDetail,
value: self.notificationStatusText,
color: self.notificationStatus.color)
@@ -506,10 +548,25 @@ extension SettingsProTab {
}
.buttonStyle(.borderedProminent)
.controlSize(.small)
.disabled(self.notificationStatus == .checking || self.isRequestingNotificationAuthorization)
Text("OpenClaw uses notifications for approval prompts and mirrored event alerts.")
Text(self.notificationStatusDetail)
.font(.caption)
.foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true)
Divider()
HStack(alignment: .top, spacing: 10) {
Image(systemName: "network")
.font(.caption.weight(.semibold))
.foregroundStyle(OpenClawBrand.accent)
.frame(width: 22, height: 22)
Text(self.notificationRelayDetail)
.font(.caption)
.foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true)
}
}
}
.padding(.horizontal, OpenClawProMetric.pagePadding)

View File

@@ -89,28 +89,48 @@ enum SettingsNotificationStatus: Equatable {
var text: String {
switch self {
case .checking: "Checking"
case .allowed: "Allowed"
case .notAllowed: "Not Allowed"
case .notSet: "Not Set"
case .allowed: "Enabled"
case .notAllowed: "Denied"
case .notSet: "Not Enabled"
case .unknown: "Unknown"
}
}
var actionTitle: String {
switch self {
case .notSet, .checking:
"Request Access"
case .allowed, .notAllowed, .unknown:
"Open System Settings"
case .notSet:
"Enable Notifications"
case .checking:
"Checking"
case .allowed:
"Manage in iOS Settings"
case .notAllowed, .unknown:
"Open iOS Settings"
}
}
var actionIcon: String {
self == .allowed ? "gear" : "bell.badge"
switch self {
case .allowed:
"gear"
case .notAllowed, .unknown:
"gear.badge"
case .checking:
"hourglass"
case .notSet:
"bell.badge"
}
}
var color: Color {
self == .allowed ? OpenClawBrand.ok : .secondary
switch self {
case .allowed:
OpenClawBrand.ok
case .notAllowed, .unknown:
OpenClawBrand.warn
case .checking, .notSet:
.secondary
}
}
var shouldOpenNotificationSettings: Bool {
@@ -121,6 +141,10 @@ enum SettingsNotificationStatus: Equatable {
false
}
}
var allowsNotifications: Bool {
self == .allowed
}
}
enum SettingsDiagnosticIssue: String, Equatable, CaseIterable {

View File

@@ -2,11 +2,14 @@ import SwiftUI
private struct ExecApprovalPromptDialogModifier: ViewModifier {
@Environment(NodeAppModel.self) private var appModel: NodeAppModel
let suppressedApprovalID: String?
func body(content: Content) -> some View {
content
.overlay {
if let prompt = self.appModel.pendingExecApprovalPrompt {
if let prompt = self.appModel.pendingExecApprovalPrompt,
prompt.id != self.suppressedApprovalID
{
ZStack {
Color.black.opacity(0.38)
.ignoresSafeArea()
@@ -58,7 +61,7 @@ private struct ExecApprovalPromptCard: View {
VStack(alignment: .leading, spacing: 6) {
Text("Exec approval required")
.font(.headline)
Text("OpenClaw opened from a notification. Review this exec request before continuing.")
Text("Review this exec request before continuing. Your decision will be sent back to the gateway.")
.font(.subheadline)
.foregroundStyle(.secondary)
}
@@ -188,7 +191,7 @@ private struct ExecApprovalPromptMetadataRow: View {
}
extension View {
func execApprovalPromptDialog() -> some View {
self.modifier(ExecApprovalPromptDialogModifier())
func execApprovalPromptDialog(suppressedApprovalID: String? = nil) -> some View {
self.modifier(ExecApprovalPromptDialogModifier(suppressedApprovalID: suppressedApprovalID))
}
}

View File

@@ -0,0 +1,102 @@
import SwiftUI
private struct NotificationPermissionGuidanceDialogModifier: ViewModifier {
@Environment(NodeAppModel.self) private var appModel: NodeAppModel
let openNotifications: (String) -> Void
func body(content: Content) -> some View {
content
.overlay {
if let prompt = self.appModel.pendingNotificationPermissionGuidancePrompt {
ZStack {
Color.black.opacity(0.38)
.ignoresSafeArea()
NotificationPermissionGuidanceCard(
onOpenNotifications: {
let approvalId = prompt.approvalId
self.appModel.dismissNotificationPermissionGuidancePrompt(
suppressFuture: false)
self.openNotifications(approvalId)
},
onDismiss: {
self.appModel.dismissNotificationPermissionGuidancePrompt(
suppressFuture: false)
},
onSuppressFuture: {
self.appModel.dismissNotificationPermissionGuidancePrompt(
suppressFuture: true)
})
.padding(.horizontal, 20)
.frame(maxWidth: 460)
.transition(.scale(scale: 0.98).combined(with: .opacity))
}
.zIndex(2)
.id(prompt.id)
}
}
.animation(
.easeInOut(duration: 0.18),
value: self.appModel.pendingNotificationPermissionGuidancePrompt?.id)
}
}
private struct NotificationPermissionGuidanceCard: View {
let onOpenNotifications: () -> Void
let onDismiss: () -> Void
let onSuppressFuture: () -> Void
var body: some View {
VStack(alignment: .leading, spacing: 14) {
VStack(alignment: .leading, spacing: 6) {
Text("Notifications are off")
.font(.headline)
Text(
"""
Exec approvals can only be reviewed while OpenClaw is open and connected.
Enable Notifications to receive approval notifications while OpenClaw is not open.
""")
.font(.subheadline)
.foregroundStyle(.secondary)
.fixedSize(horizontal: false, vertical: true)
}
VStack(spacing: 10) {
Button {
self.onOpenNotifications()
} label: {
Text("Open Notifications Settings")
.frame(maxWidth: .infinity)
}
.buttonStyle(.borderedProminent)
Button(role: .cancel) {
self.onDismiss()
} label: {
Text("Not Now")
.frame(maxWidth: .infinity)
}
.buttonStyle(.bordered)
Button {
self.onSuppressFuture()
} label: {
Text("Don't show again")
.frame(maxWidth: .infinity)
}
.buttonStyle(.bordered)
}
.controlSize(.large)
.frame(maxWidth: .infinity)
}
.padding(18)
.proPanelSurface(tint: OpenClawBrand.warn, radius: 20, isProminent: true)
}
}
extension View {
func notificationPermissionGuidanceDialog(openNotifications: @escaping (String) -> Void) -> some View {
self.modifier(NotificationPermissionGuidanceDialogModifier(openNotifications: openNotifications))
}
}

View File

@@ -23,6 +23,10 @@ private struct WatchChatPreview {
var statusText: String?
}
private struct ExecApprovalGatewayEventPayload: Decodable {
var id: String
}
/// Ensures notification requests return promptly even if the system prompt blocks.
private final class NotificationInvokeLatch<T: Sendable>: @unchecked Sendable {
private let lock = NSLock()
@@ -83,6 +87,11 @@ final class NodeAppModel {
}
}
struct NotificationPermissionGuidancePrompt: Identifiable, Equatable {
let id = UUID()
let approvalId: String
}
private enum ExecApprovalResolutionOutcome {
case resolved
case stale
@@ -96,6 +105,8 @@ final class NodeAppModel {
}
private let deepLinkLogger = Logger(subsystem: "ai.openclawfoundation.app", category: "DeepLink")
private nonisolated static let execApprovalNotificationGuidanceSuppressedKey =
"notifications.execApprovalGuidance.suppressed"
private let pushWakeLogger = Logger(subsystem: "ai.openclawfoundation.app", category: "PushWake")
private let pendingActionLogger = Logger(subsystem: "ai.openclawfoundation.app", category: "PendingAction")
private let locationWakeLogger = Logger(subsystem: "ai.openclawfoundation.app", category: "LocationWake")
@@ -156,6 +167,7 @@ final class NodeAppModel {
private(set) var pendingExecApprovalPromptResolving: Bool = false
private(set) var pendingExecApprovalPromptErrorText: String?
private var pendingExecApprovalPromptRequestGeneration: Int = 0
private(set) var pendingNotificationPermissionGuidancePrompt: NotificationPermissionGuidancePrompt?
private var queuedAgentDeepLinkPrompt: AgentDeepLinkPrompt?
private var lastAgentDeepLinkPromptAt: Date = .distantPast
@ObservationIgnored private var queuedAgentDeepLinkPromptTask: Task<Void, Never>?
@@ -895,26 +907,50 @@ final class NodeAppModel {
for await evt in stream {
if Task.isCancelled { return }
guard let payload = evt.payload else { continue }
switch evt.event {
case "voicewake.changed":
struct Payload: Decodable { var triggers: [String] }
guard let decoded = try? GatewayPayloadDecoding.decode(payload, as: Payload.self) else { continue }
let triggers = VoiceWakePreferences.sanitizeTriggerWords(decoded.triggers)
VoiceWakePreferences.saveTriggerWords(triggers)
case "talk.mode":
struct Payload: Decodable {
var enabled: Bool
var phase: String?
}
guard let decoded = try? GatewayPayloadDecoding.decode(payload, as: Payload.self) else { continue }
self.applyTalkModeSync(enabled: decoded.enabled, phase: decoded.phase)
default:
continue
}
await self.handleOperatorGatewayServerEvent(evt)
}
}
}
private func handleOperatorGatewayServerEvent(_ evt: EventFrame) async {
guard let payload = evt.payload else { return }
switch evt.event {
case "voicewake.changed":
struct Payload: Decodable { var triggers: [String] }
guard let decoded = try? GatewayPayloadDecoding.decode(payload, as: Payload.self) else { return }
let triggers = VoiceWakePreferences.sanitizeTriggerWords(decoded.triggers)
VoiceWakePreferences.saveTriggerWords(triggers)
case "talk.mode":
struct Payload: Decodable {
var enabled: Bool
var phase: String?
}
guard let decoded = try? GatewayPayloadDecoding.decode(payload, as: Payload.self) else { return }
self.applyTalkModeSync(enabled: decoded.enabled, phase: decoded.phase)
case ExecApprovalNotificationBridge.requestedKind:
guard let approvalId = Self.execApprovalEventID(from: payload) else { return }
await self.presentNotificationPermissionGuidanceForExecApprovalIfNeeded(approvalId: approvalId)
await self.presentExecApprovalNotificationPrompt(
ExecApprovalNotificationPrompt(approvalId: approvalId))
case ExecApprovalNotificationBridge.resolvedKind:
guard let approvalId = Self.execApprovalEventID(from: payload) else { return }
await self.handleExecApprovalResolvedRemotePush(approvalId: approvalId)
default:
return
}
}
private nonisolated static func execApprovalEventID(from payload: AnyCodable) -> String? {
guard let decoded = try? GatewayPayloadDecoding.decode(
payload,
as: ExecApprovalGatewayEventPayload.self)
else {
return nil
}
let approvalId = decoded.id.trimmingCharacters(in: .whitespacesAndNewlines)
return approvalId.isEmpty ? nil : approvalId
}
private func applyTalkModeSync(enabled: Bool, phase: String?) {
_ = phase
guard self.talkMode.isEnabled != enabled else { return }
@@ -1332,8 +1368,8 @@ final class NodeAppModel {
error: OpenClawNodeError(code: .invalidRequest, message: "INVALID_REQUEST: empty notification"))
}
let finalStatus = await self.requestNotificationAuthorizationIfNeeded()
guard finalStatus == .authorized || finalStatus == .provisional || finalStatus == .ephemeral else {
let status = await self.notificationAuthorizationStatus()
guard Self.isNotificationAuthorizationAllowed(status) else {
return BridgeInvokeResponse(
id: req.id,
ok: false,
@@ -1385,9 +1421,18 @@ final class NodeAppModel {
error: OpenClawNodeError(code: .invalidRequest, message: "INVALID_REQUEST: empty chat.push text"))
}
let finalStatus = await self.requestNotificationAuthorizationIfNeeded()
let shouldSpeak = params.speak ?? true
let status = await self.notificationAuthorizationStatus()
let notificationsAllowed = Self.isNotificationAuthorizationAllowed(status)
if !notificationsAllowed, !shouldSpeak {
return BridgeInvokeResponse(
id: req.id,
ok: false,
error: OpenClawNodeError(code: .unavailable, message: "NOT_AUTHORIZED: notifications"))
}
let messageId = UUID().uuidString
if finalStatus == .authorized || finalStatus == .provisional || finalStatus == .ephemeral {
if notificationsAllowed {
let addResult = await self.runNotificationCall(timeoutSeconds: 2.0) { [notificationCenter] in
let content = UNMutableNotificationContent()
content.title = "OpenClaw"
@@ -1408,7 +1453,7 @@ final class NodeAppModel {
}
}
if params.speak ?? true {
if shouldSpeak {
let toSpeak = text
Task { @MainActor in
try? await TalkSystemSpeechSynthesizer.shared.speak(text: toSpeak)
@@ -1420,26 +1465,6 @@ final class NodeAppModel {
return BridgeInvokeResponse(id: req.id, ok: true, payloadJSON: json)
}
private func requestNotificationAuthorizationIfNeeded() async -> NotificationAuthorizationStatus {
let status = await self.notificationAuthorizationStatus()
guard status == .notDetermined else { return status }
// Avoid hanging invoke requests if the permission prompt is never answered.
_ = await self.runNotificationCall(timeoutSeconds: 2.0) { [notificationCenter] in
_ = try await notificationCenter.requestAuthorization(options: [.alert, .sound, .badge])
}
let updatedStatus = await self.notificationAuthorizationStatus()
if Self.isNotificationAuthorizationAllowed(updatedStatus) {
// Refresh APNs registration immediately after the first permission grant so the
// gateway can receive a push registration without requiring an app relaunch.
await MainActor.run {
UIApplication.shared.registerForRemoteNotifications()
}
}
return updatedStatus
}
private func notificationAuthorizationStatus() async -> NotificationAuthorizationStatus {
let result = await self.runNotificationCall(timeoutSeconds: 1.5) { [notificationCenter] in
await notificationCenter.authorizationStatus()
@@ -1463,6 +1488,29 @@ final class NodeAppModel {
}
}
private func presentNotificationPermissionGuidanceForExecApprovalIfNeeded(approvalId: String) async {
guard !self.execApprovalNotificationGuidanceSuppressed else { return }
let status = await self.notificationAuthorizationStatus()
guard !Self.isNotificationAuthorizationAllowed(status) else { return }
self.pendingNotificationPermissionGuidancePrompt =
NotificationPermissionGuidancePrompt(approvalId: approvalId)
}
var execApprovalNotificationGuidanceSuppressed: Bool {
UserDefaults.standard.bool(forKey: Self.execApprovalNotificationGuidanceSuppressedKey)
}
func dismissNotificationPermissionGuidancePrompt(suppressFuture: Bool) {
if suppressFuture {
UserDefaults.standard.set(true, forKey: Self.execApprovalNotificationGuidanceSuppressedKey)
}
self.pendingNotificationPermissionGuidancePrompt = nil
}
func resetExecApprovalNotificationGuidanceSuppression() {
UserDefaults.standard.removeObject(forKey: Self.execApprovalNotificationGuidanceSuppressedKey)
}
private func runNotificationCall<T: Sendable>(
timeoutSeconds: Double,
operation: @escaping @Sendable () async throws -> T) async -> Result<T, NotificationCallError>
@@ -2335,10 +2383,6 @@ extension NodeAppModel {
nodeOptions: nodeOptions,
sessionBox: sessionBox)
}
// QR bootstrap onboarding should surface the system notification permission
// prompt immediately so visible APNs alerts work without a second manual step.
_ = await self.requestNotificationAuthorizationIfNeeded()
}
private func refreshBackgroundReconnectSuppressionIfNeeded(source: String) {
@@ -3916,11 +3960,15 @@ extension NodeAppModel {
let hadWatchPrompt = self.watchExecApprovalPromptsByID[normalizedApprovalID] != nil
let hadPendingPrompt = self.pendingExecApprovalPrompt?.id == normalizedApprovalID
let hadPendingRecoveryID = self.pendingWatchExecApprovalRecoveryIDs.contains(normalizedApprovalID)
guard hadWatchPrompt || hadPendingPrompt || hadPendingRecoveryID else {
let hadGuidancePrompt = self.pendingNotificationPermissionGuidancePrompt?.approvalId == normalizedApprovalID
let hadApprovalSurface = hadWatchPrompt || hadPendingPrompt || hadPendingRecoveryID
guard hadApprovalSurface || hadGuidancePrompt else {
return
}
await self.publishWatchExecApprovalExpired(approvalId: normalizedApprovalID, reason: .resolved)
if hadApprovalSurface {
await self.publishWatchExecApprovalExpired(approvalId: normalizedApprovalID, reason: .resolved)
}
self.clearPendingExecApprovalPromptIfMatches(normalizedApprovalID)
}
@@ -4401,10 +4449,17 @@ extension NodeAppModel {
private func clearPendingExecApprovalPromptIfMatches(_ approvalId: String) {
let normalizedApprovalID = approvalId.trimmingCharacters(in: .whitespacesAndNewlines)
self.clearNotificationPermissionGuidancePromptIfMatches(normalizedApprovalID)
guard self.pendingExecApprovalPrompt?.id == normalizedApprovalID else { return }
self.dismissPendingExecApprovalPrompt()
}
private func clearNotificationPermissionGuidancePromptIfMatches(_ approvalId: String) {
let normalizedApprovalID = approvalId.trimmingCharacters(in: .whitespacesAndNewlines)
guard self.pendingNotificationPermissionGuidancePrompt?.approvalId == normalizedApprovalID else { return }
self.pendingNotificationPermissionGuidancePrompt = nil
}
private nonisolated static func isApprovalNotificationStaleError(_ error: Error) -> Bool {
guard let gatewayError = error as? GatewayResponseError else { return false }
if gatewayError.code != "INVALID_REQUEST" {
@@ -5110,6 +5165,20 @@ extension NodeAppModel {
self.pendingExecApprovalPrompt
}
func _test_pendingNotificationPermissionGuidancePrompt() -> NotificationPermissionGuidancePrompt? {
self.pendingNotificationPermissionGuidancePrompt
}
func _debug_presentNotificationPermissionGuidancePromptForScreenshot() {
self.resetExecApprovalNotificationGuidanceSuppression()
self.pendingNotificationPermissionGuidancePrompt =
NotificationPermissionGuidancePrompt(approvalId: "screenshot-exec-approval")
}
func _test_resetExecApprovalNotificationGuidanceSuppression() {
self.resetExecApprovalNotificationGuidanceSuppression()
}
func _test_recordPendingWatchExecApprovalRecoveryID(_ approvalId: String) {
self.appendPendingWatchExecApprovalRecoveryID(approvalId)
}
@@ -5139,6 +5208,14 @@ extension NodeAppModel {
isBackgrounded: isBackgrounded)
}
nonisolated static func _test_execApprovalEventID(from payload: AnyCodable) -> String? {
self.execApprovalEventID(from: payload)
}
func _test_handleOperatorGatewayServerEvent(_ event: EventFrame) async {
await self.handleOperatorGatewayServerEvent(event)
}
nonisolated static func _test_watchExecApprovalIDsNeedingFetch(
candidateIDs: [String],
cachedApprovalIDs: [String]) -> [String]
@@ -5235,24 +5312,6 @@ extension NodeAppModel {
func _test_restartGatewaySessionsAfterForegroundStaleConnection() async {
await self.restartGatewaySessionsAfterForegroundStaleConnection()
}
func _test_handleSuccessfulBootstrapGatewayOnboarding() async {
await self.handleSuccessfulBootstrapGatewayOnboarding(
url: URL(string: "wss://gateway.example")!,
stableID: "test-gateway",
token: nil,
password: nil,
nodeOptions: GatewayConnectOptions(
role: "node",
scopes: [],
caps: [],
commands: [],
permissions: [:],
clientId: "openclaw-ios",
clientMode: "node",
clientDisplayName: nil),
sessionBox: nil)
}
}
#endif
// swiftlint:enable type_body_length file_length

View File

@@ -409,7 +409,7 @@ enum WatchPromptNotificationBridge {
let title = params.title.trimmingCharacters(in: .whitespacesAndNewlines)
let body = params.body.trimmingCharacters(in: .whitespacesAndNewlines)
guard !title.isEmpty || !body.isEmpty else { return }
guard await self.requestNotificationAuthorizationIfNeeded() else { return }
guard await self.isNotificationAuthorizationAllowed() else { return }
let normalizedActions = (params.actions ?? []).compactMap { action -> OpenClawWatchAction? in
let id = action.id.trimmingCharacters(in: .whitespacesAndNewlines)
@@ -516,29 +516,10 @@ enum WatchPromptNotificationBridge {
}
}
private static func requestNotificationAuthorizationIfNeeded() async -> Bool {
private static func isNotificationAuthorizationAllowed() async -> Bool {
let center = UNUserNotificationCenter.current()
let status = await self.notificationAuthorizationStatus(center: center)
switch status {
case .authorized, .provisional, .ephemeral:
return true
case .notDetermined:
let granted = await (try? center.requestAuthorization(options: [.alert, .sound, .badge])) ?? false
if !granted { return false }
let updatedStatus = await self.notificationAuthorizationStatus(center: center)
if self.isAuthorizationStatusAllowed(updatedStatus) {
// Refresh APNs registration immediately after the first permission grant so the
// gateway can receive a push registration without requiring an app relaunch.
await MainActor.run {
UIApplication.shared.registerForRemoteNotifications()
}
}
return self.isAuthorizationStatusAllowed(updatedStatus)
case .denied:
return false
@unknown default:
return false
}
return self.isAuthorizationStatusAllowed(status)
}
private static func isAuthorizationStatusAllowed(_ status: UNAuthorizationStatus) -> Bool {
@@ -635,6 +616,9 @@ struct OpenClawApp: App {
UserDefaults.standard.set(true, forKey: "gateway.hasConnectedOnce")
UserDefaults.standard.set(true, forKey: "onboarding.quickSetupDismissed")
appModel.enterScreenshotFixtureMode()
if Self.screenshotNotificationGuidanceEnabled {
appModel._debug_presentNotificationPermissionGuidancePromptForScreenshot()
}
}
#endif
OpenClawAppModelRegistry.appModel = appModel
@@ -686,6 +670,14 @@ struct OpenClawApp: App {
#endif
}
private static var screenshotNotificationGuidanceEnabled: Bool {
#if DEBUG
ProcessInfo.processInfo.arguments.contains("--openclaw-screenshot-notification-guidance")
#else
false
#endif
}
@MainActor
private func applyAppearancePreference() {
let style = self.appearancePreference.userInterfaceStyle

View File

@@ -22,6 +22,20 @@ struct PushBuildConfig {
let apnsEnvironment: PushAPNsEnvironment
static let current = PushBuildConfig()
static let openClawHostedRelayHost = "ios-push-relay.openclaw.ai"
var usesOpenClawHostedRelay: Bool {
guard self.transport == .relay, self.distribution == .official else { return false }
guard let relayBaseURL = self.relayBaseURL,
let components = URLComponents(url: relayBaseURL, resolvingAgainstBaseURL: false)
else {
return false
}
return components.scheme?.lowercased() == "https"
&& components.host?.lowercased() == Self.openClawHostedRelayHost
&& components.user == nil
&& components.password == nil
}
init(bundle: Bundle = .main) {
self.transport = Self.readEnum(

View File

@@ -24,6 +24,8 @@ struct RootTabs: View {
AppAppearancePreference.system.rawValue
@State private var selectedTab: AppTab = Self.initialTab
@State private var selectedSidebarDestination: SidebarDestination = Self.initialSidebarDestination
@State private var selectedSettingsRoute: SettingsRoute? = Self.initialSidebarDestination.settingsRoute
@State private var selectedSettingsRouteRequestID: Int = 0
@State private var isSidebarVisible: Bool = Self.initialSidebarVisibility ?? false
@State private var sidebarVisibilityUserOverridden: Bool = Self.initialSidebarVisibility != nil
@State private var isSidebarDrawerLayout: Bool = false
@@ -39,6 +41,7 @@ struct RootTabs: View {
@State private var didApplyInitialAppearance: Bool = false
@State private var didApplyInitialChatSession: Bool = false
@State private var handledGatewaySetupRequestID: Int = 0
@State private var suppressedExecApprovalPromptIDForNotificationSettings: String?
private static var initialTab: AppTab {
let arguments = ProcessInfo.processInfo.arguments
@@ -161,8 +164,10 @@ struct RootTabs: View {
.tabItem { Label("Agent", systemImage: "person.2.fill") }
.tag(AppTab.agent)
SettingsProTab(initialRoute: self.selectedSidebarDestination.settingsRoute)
.id(self.selectedSidebarDestination.settingsRoute.map { "\($0)" } ?? "settings")
SettingsProTab(
initialRoute: self.selectedSettingsRoute,
onRouteChange: self.handleSettingsRouteChange)
.id(self.settingsTabViewID)
.tabItem { Label("Settings", systemImage: "gearshape.fill") }
.tag(AppTab.settings)
}
@@ -235,7 +240,7 @@ struct RootTabs: View {
private var sidebarDetailShell: some View {
self.sidebarDetail
.id(self.selectedSidebarDestination.id)
.id(self.sidebarDetailShellID)
}
private var sidebarColumn: some View {
@@ -463,11 +468,21 @@ struct RootTabs: View {
headerLeadingAction: self.sidebarHeaderLeadingAction,
gatewayAction: { self.selectSidebarDestination(.gateway) })
case .settings:
SettingsProTab(headerLeadingAction: self.sidebarHeaderLeadingAction)
if let selectedSettingsRoute {
SettingsProTab(
directRoute: selectedSettingsRoute,
headerLeadingAction: self.sidebarHeaderLeadingAction,
onRouteChange: self.handleSettingsRouteChange)
} else {
SettingsProTab(
headerLeadingAction: self.sidebarHeaderLeadingAction,
onRouteChange: self.handleSettingsRouteChange)
}
case .gateway:
SettingsProTab(
directRoute: self.selectedSidebarDestination.settingsRoute ?? .gateway,
headerLeadingAction: self.sidebarHeaderLeadingAction)
directRoute: self.selectedSettingsRoute ?? self.selectedSidebarDestination.settingsRoute ?? .gateway,
headerLeadingAction: self.sidebarHeaderLeadingAction,
onRouteChange: self.handleSettingsRouteChange)
}
}
@@ -492,6 +507,21 @@ struct RootTabs: View {
return UIDevice.current.userInterfaceIdiom
}
private var sidebarDetailShellID: String {
let routeID = self.selectedSettingsRoute.map { "\($0)" } ?? "root"
return "\(self.selectedSidebarDestination.id):\(routeID):\(self.selectedSettingsRouteRequestID)"
}
private var settingsTabViewID: String {
let routeID = self.selectedSettingsRoute.map { "\($0)" } ?? "settings"
return "\(routeID):\(self.selectedSettingsRouteRequestID)"
}
private var activeExecApprovalPromptSuppressionID: String? {
guard self.selectedTab == .settings, self.selectedSettingsRoute == .notifications else { return nil }
return self.suppressedExecApprovalPromptIDForNotificationSettings
}
private var shouldCollapseSidebarAfterSelection: Bool {
Self.shouldCollapseSidebarAfterSelection(
layoutMode: self.isSidebarDrawerLayout ? .drawer : .split)
@@ -705,6 +735,11 @@ struct RootTabs: View {
.onChange(of: self.appModel.gatewaySetupRequestID) { _, _ in
self.maybeOpenSettingsForGatewaySetup()
}
.onChange(of: self.appModel.pendingExecApprovalPrompt?.id) { _, newValue in
if newValue != self.suppressedExecApprovalPromptIDForNotificationSettings {
self.suppressedExecApprovalPromptIDForNotificationSettings = nil
}
}
}
private func rootPresentation(_ content: some View) -> some View {
@@ -742,7 +777,12 @@ struct RootTabs: View {
}
.gatewayTrustPromptAlert()
.deepLinkAgentPromptAlert()
.execApprovalPromptDialog()
.execApprovalPromptDialog(
suppressedApprovalID: self.activeExecApprovalPromptSuppressionID)
.notificationPermissionGuidanceDialog(openNotifications: { approvalId in
self.suppressedExecApprovalPromptIDForNotificationSettings = approvalId
self.selectSettingsRoute(.notifications)
})
}
private var appearancePreference: AppAppearancePreference {
@@ -874,9 +914,15 @@ struct RootTabs: View {
private func homeCanvasName(for agent: AgentSummary) -> String {
self.normalized(agent.name) ?? agent.id
}
}
extension RootTabs {
private func selectSidebarDestination(_ destination: SidebarDestination) {
if destination.settingsRoute != .notifications {
self.suppressedExecApprovalPromptIDForNotificationSettings = nil
}
self.selectedSidebarDestination = destination
self.selectedSettingsRoute = destination.settingsRoute
self.selectedTab = destination.appTab
guard self.usesSidebarTabs, self.shouldCollapseSidebarAfterSelection else { return }
withAnimation(.easeInOut(duration: 0.22)) {
@@ -884,6 +930,31 @@ struct RootTabs: View {
}
}
private func selectSettingsRoute(_ route: SettingsRoute) {
if route != .notifications {
self.suppressedExecApprovalPromptIDForNotificationSettings = nil
}
self.selectedSettingsRoute = route
self.selectedSettingsRouteRequestID &+= 1
self.selectedSidebarDestination = .settings
self.selectedTab = .settings
guard self.usesSidebarTabs, self.shouldCollapseSidebarAfterSelection else { return }
withAnimation(.easeInOut(duration: 0.22)) {
self.setSidebarVisible(false)
}
}
private func handleSettingsRouteChange(_ route: SettingsRoute?) {
guard route != .notifications else { return }
if route == nil {
self.selectedSettingsRoute = nil
if self.selectedTab == .settings {
self.selectedSidebarDestination = .settings
}
}
self.suppressedExecApprovalPromptIDForNotificationSettings = nil
}
private func showSidebar() {
self.sidebarVisibilityUserOverridden = true
withAnimation(.easeInOut(duration: 0.22)) {

View File

@@ -16,7 +16,6 @@ enum NotificationAuthorizationStatus {
protocol NotificationCentering: Sendable {
func authorizationStatus() async -> NotificationAuthorizationStatus
func requestAuthorization(options: UNAuthorizationOptions) async throws -> Bool
func add(_ request: UNNotificationRequest) async throws
func removePendingNotificationRequests(withIdentifiers identifiers: [String]) async
func removeDeliveredNotifications(withIdentifiers identifiers: [String]) async
@@ -48,10 +47,6 @@ struct LiveNotificationCenter: NotificationCentering, @unchecked Sendable {
}
}
func requestAuthorization(options: UNAuthorizationOptions) async throws -> Bool {
try await self.center.requestAuthorization(options: options)
}
func add(_ request: UNNotificationRequest) async throws {
try await withCheckedThrowingContinuation { (cont: CheckedContinuation<Void, Error>) in
self.center.add(request) { error in

View File

@@ -50,6 +50,7 @@ Sources/Gateway/GatewayDiscoveryModel.swift
Sources/Gateway/GatewayHealthMonitor.swift
Sources/Gateway/GatewayProblemView.swift
Sources/Gateway/GatewayQuickSetupSheet.swift
Sources/Gateway/NotificationPermissionGuidanceDialog.swift
Sources/Gateway/GatewayServiceResolver.swift
Sources/Gateway/GatewaySettingsStore.swift
Sources/Gateway/GatewayTrustPromptAlert.swift

View File

@@ -14,10 +14,6 @@ private final class MockNotificationCenter: NotificationCentering, @unchecked Se
self.authorization
}
func requestAuthorization(options _: UNAuthorizationOptions) async throws -> Bool {
true
}
func add(_ request: UNNotificationRequest) async throws {
self.addedRequests.append(request)
}

View File

@@ -200,25 +200,16 @@ private final class MockWatchMessagingService: @preconcurrency WatchMessagingSer
private final class MockBootstrapNotificationCenter: NotificationCentering, @unchecked Sendable {
var status: NotificationAuthorizationStatus = .notDetermined
var requestAuthorizationResult = false
var requestAuthorizationCalls = 0
var addCalls = 0
func authorizationStatus() async -> NotificationAuthorizationStatus {
self.status
}
func requestAuthorization(options _: UNAuthorizationOptions) async throws -> Bool {
self.requestAuthorizationCalls += 1
if self.requestAuthorizationResult {
self.status = .authorized
} else {
self.status = .denied
}
return self.requestAuthorizationResult
func add(_: UNNotificationRequest) async throws {
self.addCalls += 1
}
func add(_: UNNotificationRequest) async throws {}
func removePendingNotificationRequests(withIdentifiers _: [String]) async {}
func removeDeliveredNotifications(withIdentifiers _: [String]) async {}
@@ -1160,6 +1151,35 @@ private final class MockBootstrapNotificationCenter: NotificationCentering, @unc
isBackgrounded: false))
}
@Test func execApprovalEventIDDecodesGatewayPayload() {
#expect(NodeAppModel._test_execApprovalEventID(from: AnyCodable(["id": " approval-1 "])) == "approval-1")
#expect(NodeAppModel._test_execApprovalEventID(from: AnyCodable(["id": " "])) == nil)
#expect(NodeAppModel._test_execApprovalEventID(from: AnyCodable(["other": "approval-1"])) == nil)
}
@Test @MainActor func operatorGatewayResolvedEventClearsPendingApprovalPrompt() async throws {
let appModel = NodeAppModel()
try appModel._test_presentExecApprovalPrompt(
#require(
NodeAppModel._test_makeExecApprovalPrompt(
id: "approval-event-resolved",
commandText: "echo clear",
allowedDecisions: ["allow-once", "deny"],
host: "gateway",
nodeId: nil,
agentId: nil,
expiresAtMs: Int(Date().timeIntervalSince1970 * 1000) + 60000)))
await appModel._test_handleOperatorGatewayServerEvent(EventFrame(
type: "event",
event: ExecApprovalNotificationBridge.resolvedKind,
payload: AnyCodable(["id": "approval-event-resolved"]),
seq: nil,
stateversion: nil))
#expect(appModel._test_pendingExecApprovalPrompt() == nil)
}
@Test func watchExecApprovalHydrateFetchesOnlyMissingIDs() {
let idsToFetch = NodeAppModel._test_watchExecApprovalIDsNeedingFetch(
candidateIDs: ["cached", "pending", "cached", "other", "", " pending "],
@@ -1201,13 +1221,65 @@ private final class MockBootstrapNotificationCenter: NotificationCentering, @unc
hasStoredOperatorToken: false))
}
@Test @MainActor func successfulBootstrapOnboardingRequestsNotificationAuthorization() async {
@Test @MainActor func operatorGatewayRequestedEventShowsNotificationGuidanceWhenNotificationsOff() async throws {
let center = MockBootstrapNotificationCenter()
center.status = .notDetermined
let appModel = NodeAppModel(notificationCenter: center)
appModel._test_resetExecApprovalNotificationGuidanceSuppression()
defer { appModel._test_resetExecApprovalNotificationGuidanceSuppression() }
await appModel._test_handleSuccessfulBootstrapGatewayOnboarding()
await appModel._test_handleOperatorGatewayServerEvent(EventFrame(
type: "event",
event: ExecApprovalNotificationBridge.requestedKind,
payload: AnyCodable(["id": "approval-notifications-off"]),
seq: nil,
stateversion: nil))
#expect(center.requestAuthorizationCalls == 1)
let prompt = try #require(appModel._test_pendingNotificationPermissionGuidancePrompt())
#expect(prompt.approvalId == "approval-notifications-off")
}
@Test @MainActor func suppressedOperatorGatewayRequestedEventDoesNotShowNotificationGuidance() async {
let center = MockBootstrapNotificationCenter()
center.status = .denied
let appModel = NodeAppModel(notificationCenter: center)
appModel._test_resetExecApprovalNotificationGuidanceSuppression()
defer { appModel._test_resetExecApprovalNotificationGuidanceSuppression() }
appModel.dismissNotificationPermissionGuidancePrompt(suppressFuture: true)
await appModel._test_handleOperatorGatewayServerEvent(EventFrame(
type: "event",
event: ExecApprovalNotificationBridge.requestedKind,
payload: AnyCodable(["id": "approval-suppressed"]),
seq: nil,
stateversion: nil))
#expect(appModel._test_pendingNotificationPermissionGuidancePrompt() == nil)
}
@Test @MainActor func operatorGatewayResolvedEventClearsNotificationGuidancePrompt() async throws {
let center = MockBootstrapNotificationCenter()
center.status = .denied
let appModel = NodeAppModel(notificationCenter: center)
appModel._test_resetExecApprovalNotificationGuidanceSuppression()
defer { appModel._test_resetExecApprovalNotificationGuidanceSuppression() }
await appModel._test_handleOperatorGatewayServerEvent(EventFrame(
type: "event",
event: ExecApprovalNotificationBridge.requestedKind,
payload: AnyCodable(["id": "approval-guidance-resolved"]),
seq: nil,
stateversion: nil))
_ = try #require(appModel._test_pendingNotificationPermissionGuidancePrompt())
await appModel._test_handleOperatorGatewayServerEvent(EventFrame(
type: "event",
event: ExecApprovalNotificationBridge.resolvedKind,
payload: AnyCodable(["id": "approval-guidance-resolved"]),
seq: nil,
stateversion: nil))
#expect(appModel._test_pendingNotificationPermissionGuidancePrompt() == nil)
}
@Test func clearingBootstrapTokenStripsReconnectConfigEvenWithoutPersistence() throws {
@@ -1269,6 +1341,78 @@ private final class MockBootstrapNotificationCenter: NotificationCentering, @unc
#expect(res.error?.message.contains("CAMERA_DISABLED") == true)
}
@Test @MainActor func systemNotifyReturnsUnavailableWhenNotificationsOff() async throws {
let center = MockBootstrapNotificationCenter()
center.status = .notDetermined
let appModel = NodeAppModel(notificationCenter: center)
let params = OpenClawSystemNotifyParams(title: "Approval", body: "Review request")
let paramsData = try JSONEncoder().encode(params)
let req = BridgeInvokeRequest(
id: "notify-off",
command: OpenClawSystemCommand.notify.rawValue,
paramsJSON: String(decoding: paramsData, as: UTF8.self))
let res = await appModel._test_handleInvoke(req)
#expect(res.ok == false)
#expect(res.error?.code == .unavailable)
#expect(res.error?.message == "NOT_AUTHORIZED: notifications")
#expect(center.addCalls == 0)
}
@Test @MainActor func systemNotifySchedulesWhenNotificationsAreAlreadyAllowed() async throws {
let center = MockBootstrapNotificationCenter()
center.status = .authorized
let appModel = NodeAppModel(notificationCenter: center)
let params = OpenClawSystemNotifyParams(title: "Approval", body: "Review request")
let paramsData = try JSONEncoder().encode(params)
let req = BridgeInvokeRequest(
id: "notify-on",
command: OpenClawSystemCommand.notify.rawValue,
paramsJSON: String(decoding: paramsData, as: UTF8.self))
let res = await appModel._test_handleInvoke(req)
#expect(res.ok)
#expect(center.addCalls == 1)
}
@Test @MainActor func chatPushWithoutSpeechReturnsUnavailableWhenNotificationsOff() async throws {
let center = MockBootstrapNotificationCenter()
center.status = .notDetermined
let appModel = NodeAppModel(notificationCenter: center)
let params = OpenClawChatPushParams(text: "Build finished", speak: false)
let paramsData = try JSONEncoder().encode(params)
let req = BridgeInvokeRequest(
id: "chat-push-off",
command: OpenClawChatCommand.push.rawValue,
paramsJSON: String(decoding: paramsData, as: UTF8.self))
let res = await appModel._test_handleInvoke(req)
#expect(res.ok == false)
#expect(res.error?.code == .unavailable)
#expect(res.error?.message == "NOT_AUTHORIZED: notifications")
#expect(center.addCalls == 0)
}
@Test @MainActor func chatPushSchedulesWhenNotificationsAreAlreadyAllowed() async throws {
let center = MockBootstrapNotificationCenter()
center.status = .authorized
let appModel = NodeAppModel(notificationCenter: center)
let params = OpenClawChatPushParams(text: "Build finished", speak: false)
let paramsData = try JSONEncoder().encode(params)
let req = BridgeInvokeRequest(
id: "chat-push-on",
command: OpenClawChatCommand.push.rawValue,
paramsJSON: String(decoding: paramsData, as: UTF8.self))
let res = await appModel._test_handleInvoke(req)
#expect(res.ok)
#expect(center.addCalls == 1)
}
@Test @MainActor func handleInvokeRejectsInvalidScreenFormat() async {
let appModel = NodeAppModel()
let params = OpenClawScreenRecordParams(format: "gif")

View File

@@ -1,8 +1,8 @@
import Foundation
import Testing
@Suite struct RootTabsSourceGuardTests {
@Test func hiddenSidebarRevealUsesDestinationHeaderWithoutReservedRail() throws {
struct RootTabsSourceGuardTests {
@Test func `hidden sidebar reveal uses destination header without reserved rail`() throws {
let source = try String(contentsOf: Self.rootTabsSourceURL(), encoding: .utf8)
let componentSource = try String(contentsOf: Self.proComponentsSourceURL(), encoding: .utf8)
@@ -38,7 +38,7 @@ import Testing
#expect(!source.contains("shouldShowOverviewHeaderSidebarReveal"))
}
@Test func iPadSplitUsesSlidingSidebarWhilePortraitKeepsDrawerOverlay() throws {
@Test func `i pad split uses sliding sidebar while portrait keeps drawer overlay`() throws {
let source = try String(contentsOf: Self.rootTabsSourceURL(), encoding: .utf8)
let splitContent = try Self.extract(
source,
@@ -67,7 +67,7 @@ import Testing
#expect(!drawerContent.contains("NavigationSplitView"))
}
@Test func sidebarKeepsNavigationModelDestinationOnly() throws {
@Test func `sidebar keeps navigation model destination only`() throws {
let source = try String(contentsOf: Self.rootTabsSourceURL(), encoding: .utf8)
let navigationSource = try String(contentsOf: Self.rootTabsNavigationSourceURL(), encoding: .utf8)
let sidebarColumn = try Self.extract(
@@ -114,7 +114,7 @@ import Testing
#expect(navigationSource.contains("SidebarGroup(title: \"REFERENCE\", destinations: [.docs])"))
}
@Test func sidebarRoutesUseDestinationHeadersInsteadOfRepeatedProductBranding() throws {
@Test func `sidebar routes use destination headers instead of repeated product branding`() throws {
let rootSource = try String(contentsOf: Self.rootTabsSourceURL(), encoding: .utf8)
let agentOverviewSource = try String(contentsOf: Self.agentProTabOverviewSourceURL(), encoding: .utf8)
let docsSource = try String(contentsOf: Self.docsSourceURL(), encoding: .utf8)
@@ -148,7 +148,7 @@ import Testing
#expect(!docsSource.contains("Text(\"OpenClaw Docs\")"))
}
@Test func agentsDirectRouteKeepsSingleSidebarControl() throws {
@Test func `agents direct route keeps single sidebar control`() throws {
let source = try String(contentsOf: Self.agentProTabSourceURL(), encoding: .utf8)
let destinationsSource = try String(contentsOf: Self.agentProTabDestinationsSourceURL(), encoding: .utf8)
let nodesSource = try String(contentsOf: Self.agentProNodesDestinationSourceURL(), encoding: .utf8)
@@ -165,7 +165,7 @@ import Testing
#expect(dreamingSource.contains("OpenClawSidebarHeaderLeadingSlot(action: headerLeadingAction)"))
}
@Test func routedHeadersUseSharedAdaptiveLayout() throws {
@Test func `routed headers use shared adaptive layout`() throws {
let componentsSource = try String(contentsOf: Self.proComponentsSourceURL(), encoding: .utf8)
let featureChromeSource = try String(contentsOf: Self.iPadSidebarScreenChromeSourceURL(), encoding: .utf8)
let docsSource = try String(contentsOf: Self.docsSourceURL(), encoding: .utf8)
@@ -187,7 +187,7 @@ import Testing
#expect(settingsSource.contains("OpenClawAdaptiveHeaderRow("))
}
@Test func phoneHubKeepsDocsAsDestinationOnly() throws {
@Test func `phone hub keeps docs as destination only`() throws {
let source = try String(contentsOf: Self.phoneHubSourceURL(), encoding: .utf8)
#expect(source.contains("case .docs:"))
@@ -198,7 +198,7 @@ import Testing
#expect(!source.contains("https://docs.openclaw.ai"))
}
@Test func rootShellPreviewMatrixCoversPhoneAndIPadStates() throws {
@Test func `root shell preview matrix covers phone and I pad states`() throws {
let source = try String(contentsOf: Self.rootTabsSourceURL(), encoding: .utf8)
#expect(source.contains("#Preview(\n \"Shell iPhone portrait\""))
@@ -211,7 +211,7 @@ import Testing
#expect(source.contains("#Preview(\n \"Shell iPad gateway error\""))
}
@Test func sharedChatPreviewMatrixCoversConnectionStates() throws {
@Test func `shared chat preview matrix covers connection states`() throws {
let source = try String(contentsOf: Self.sharedChatPreviewSourceURL(), encoding: .utf8)
#expect(source.contains("#Preview(\"Chat connected\")"))
@@ -226,7 +226,7 @@ import Testing
#expect(source.contains("Gateway not connected. Check Tailscale and retry."))
}
@Test func phoneHubKeepsContentAboveFloatingTabBar() throws {
@Test func `phone hub keeps content above floating tab bar`() throws {
let source = try String(contentsOf: Self.phoneHubSourceURL(), encoding: .utf8)
#expect(source.contains(".safeAreaPadding(.bottom, self.bottomScrollInset)"))
@@ -235,7 +235,7 @@ import Testing
#expect(!source.contains("bottomTabBarClearance"))
}
@Test func phoneHubHeaderStaysTaskFirst() throws {
@Test func `phone hub header stays task first`() throws {
let source = try String(contentsOf: Self.phoneHubSourceURL(), encoding: .utf8)
#expect(source.contains("private var gatewayActionRow: some View"))
@@ -253,7 +253,7 @@ import Testing
#expect(!source.contains("private func metric(label:"))
}
@Test func workboardUsesRealGatewayMethods() throws {
@Test func `workboard uses real gateway methods`() throws {
let source = try String(contentsOf: Self.iPadWorkboardScreenSourceURL(), encoding: .utf8)
#expect(source.contains("workboard.cards.list"))
@@ -268,7 +268,7 @@ import Testing
#expect(!source.contains("Multi-column queue control"))
}
@Test func workboardCreateActionSurfacesUnavailableReasons() throws {
@Test func `workboard create action surfaces unavailable reasons`() throws {
let source = try String(contentsOf: Self.iPadWorkboardScreenSourceURL(), encoding: .utf8)
let createFunction = try Self.extract(
source,
@@ -295,7 +295,7 @@ import Testing
#expect(createFunction.contains("return true"))
}
@Test func taskScopeControlsSendRealGatewayParams() throws {
@Test func `task scope controls send real gateway params`() throws {
let source = try Self.iPadTaskFeatureScreensSource()
#expect(source.contains("private var boardScopeMenu: some View"))
@@ -314,7 +314,7 @@ import Testing
"params: EmptyParams(),\n timeoutSeconds: 20)\n let response = try JSONDecoder().decode(IPadSkillProposalManifest.self"))
}
@Test func compactTaskRowsKeepPhoneNativeActions() throws {
@Test func `compact task rows keep phone native actions`() throws {
let source = try Self.iPadTaskFeatureScreensSource()
let compactControls = try Self.extract(
source,
@@ -348,7 +348,7 @@ import Testing
#expect(compactControls.contains("Label(\"Dispatch\""))
}
@Test func skillWorkshopUsesKanbanLanesOnWideIPad() throws {
@Test func `skill workshop uses kanban lanes on wide I pad`() throws {
let source = try String(contentsOf: Self.iPadSkillWorkshopScreenSourceURL(), encoding: .utf8)
let previewSource = try String(contentsOf: Self.iPadSidebarFeaturePreviewsSourceURL(), encoding: .utf8)
let content = try Self.extract(
@@ -376,7 +376,7 @@ import Testing
#expect(previewSource.contains("status: \"manual_QA\""))
}
@Test func compactTaskRowsHavePopulatedPhonePreviews() throws {
@Test func `compact task rows have populated phone previews`() throws {
let source = try String(contentsOf: Self.iPadSidebarFeaturePreviewsSourceURL(), encoding: .utf8)
#expect(source.contains("#Preview(\"Workboard phone queue rows\")"))
@@ -387,7 +387,7 @@ import Testing
#expect(source.contains("IPadSkillWorkshopPreviewFixtures.proposals"))
}
@Test func taskScreenPreviewMatricesCoverPrimaryStates() throws {
@Test func `task screen preview matrices cover primary states`() throws {
let source = try String(contentsOf: Self.iPadSidebarFeaturePreviewsSourceURL(), encoding: .utf8)
#expect(source.contains("#Preview(\"Workboard states\")"))
@@ -412,7 +412,7 @@ import Testing
#expect(source.contains("\"manual_QA\""))
}
@Test func activityPreviewMatrixCoversConnectionStates() throws {
@Test func `activity preview matrix covers connection states`() throws {
let source = try String(contentsOf: Self.iPadSidebarFeaturePreviewsSourceURL(), encoding: .utf8)
#expect(source.contains("#Preview(\"Activity states\")"))
@@ -426,7 +426,7 @@ import Testing
#expect(source.contains("title: \"Loading sessions\""))
}
@Test func routedFeatureScreensReuseSharedProComponents() throws {
@Test func `routed feature screens reuse shared pro components`() throws {
let source = try Self.iPadTaskFeatureScreensSource()
let componentsSource = try String(contentsOf: Self.proComponentsSourceURL(), encoding: .utf8)
let channelsSource = try String(contentsOf: Self.channelsSourceURL(), encoding: .utf8)
@@ -445,20 +445,22 @@ import Testing
#expect(componentsSource.contains("struct ProStatusRow"))
}
@Test func activityScreenStaysSplitFromTaskFeatureScreens() throws {
@Test func `activity screen stays split from task feature screens`() throws {
let taskSource = try Self.iPadTaskFeatureScreensSource()
let activitySource = try String(contentsOf: Self.iPadActivityScreenSourceURL(), encoding: .utf8)
let appModelSource = try String(contentsOf: Self.nodeAppModelSourceURL(), encoding: .utf8)
let projectSource = try String(contentsOf: Self.xcodeProjectSourceURL(), encoding: .utf8)
#expect(activitySource.contains("struct IPadActivityScreen: View"))
#expect(activitySource.contains("IOSGatewayChatTransport(gateway: self.appModel.operatorSession)"))
#expect(activitySource.contains("self.appModel.makeChatTransport()"))
#expect(appModelSource.contains("return IOSGatewayChatTransport(gateway: self.operatorSession)"))
#expect(activitySource.contains("IPadSidebarScreenChrome("))
#expect(!taskSource.contains("struct IPadActivityScreen"))
#expect(!taskSource.contains("import OpenClawChatUI"))
#expect(projectSource.contains("IPadActivityScreen.swift in Sources"))
}
@Test func routedFeatureChromeStaysSplitFromTaskFeatureScreens() throws {
@Test func `routed feature chrome stays split from task feature screens`() throws {
let taskSource = try Self.iPadTaskFeatureScreensSource()
let chromeSource = try String(contentsOf: Self.iPadSidebarScreenChromeSourceURL(), encoding: .utf8)
let projectSource = try String(contentsOf: Self.xcodeProjectSourceURL(), encoding: .utf8)
@@ -470,7 +472,7 @@ import Testing
#expect(projectSource.contains("IPadSidebarScreenChrome.swift in Sources"))
}
@Test func routedFeatureChromeKeepsGatewayPillActionable() throws {
@Test func `routed feature chrome keeps gateway pill actionable`() throws {
let chromeSource = try String(contentsOf: Self.iPadSidebarScreenChromeSourceURL(), encoding: .utf8)
let featureSource = try Self.iPadTaskFeatureScreensSource()
let rootSource = try String(contentsOf: Self.rootTabsSourceURL(), encoding: .utf8)
@@ -486,14 +488,18 @@ import Testing
.count == 1)
}
@Test func routedGatewayPillsOpenGatewaySettings() throws {
@Test func `routed gateway pills open gateway settings`() throws {
let rootSource = try String(contentsOf: Self.rootTabsSourceURL(), encoding: .utf8)
let agentSource = try String(contentsOf: Self.agentProTabSourceURL(), encoding: .utf8)
let agentOverviewSource = try String(contentsOf: Self.agentProTabOverviewSourceURL(), encoding: .utf8)
let overviewSource = try String(contentsOf: Self.commandCenterSourceURL(), encoding: .utf8)
let chatSource = try String(contentsOf: Self.chatProTabSourceURL(), encoding: .utf8)
let docsSource = try String(contentsOf: Self.docsSourceURL(), encoding: .utf8)
let settingsTabSource = try String(contentsOf: Self.settingsProTabSourceURL(), encoding: .utf8)
let settingsSource = try String(contentsOf: Self.settingsProTabSectionsSourceURL(), encoding: .utf8)
let notificationGuidanceSource = try String(
contentsOf: Self.notificationPermissionGuidanceDialogSourceURL(),
encoding: .utf8)
#expect(rootSource.matches(of: /openSettings: \{ self\.selectSidebarDestination\(\.gateway\) \}/).count >= 2)
#expect(rootSource.matches(of: /gatewayAction: \{ self\.selectSidebarDestination\(\.gateway\) \}/).count == 1)
@@ -512,15 +518,39 @@ import Testing
#expect(docsSource.contains("let gatewayAction: (() -> Void)?"))
#expect(settingsSource.contains("NavigationLink(value: SettingsRoute.gateway)"))
#expect(rootSource.contains("case .settings:"))
#expect(rootSource.contains("SettingsProTab(headerLeadingAction: self.sidebarHeaderLeadingAction)"))
#expect(rootSource.contains("directRoute: self.selectedSidebarDestination.settingsRoute ?? .gateway"))
#expect(rootSource.contains("SettingsProTab(initialRoute: self.selectedSidebarDestination.settingsRoute)"))
#expect(rootSource
.matches(of: /SettingsProTab\(\s*headerLeadingAction: self\.sidebarHeaderLeadingAction,/)
.count >= 1)
#expect(rootSource
.contains(
"directRoute: self.selectedSettingsRoute ?? self.selectedSidebarDestination.settingsRoute ?? .gateway"))
#expect(rootSource.matches(of: /SettingsProTab\(\s*initialRoute: self\.selectedSettingsRoute,/).count == 1)
#expect(rootSource.contains(".id(self.settingsTabViewID)"))
#expect(rootSource.contains("@State private var selectedSettingsRouteRequestID: Int = 0"))
#expect(rootSource.contains("self.selectedSettingsRouteRequestID &+= 1"))
#expect(rootSource.contains("@State private var suppressedExecApprovalPromptIDForNotificationSettings"))
#expect(rootSource.contains("private var activeExecApprovalPromptSuppressionID: String?"))
#expect(rootSource.contains("suppressedApprovalID: self.activeExecApprovalPromptSuppressionID"))
#expect(rootSource.contains("if destination.settingsRoute != .notifications"))
#expect(rootSource.contains("if route != .notifications"))
#expect(rootSource.contains("if route == nil"))
#expect(rootSource.contains("self.selectedSettingsRoute = nil"))
#expect(rootSource.contains("self.selectedSidebarDestination = .settings"))
#expect(rootSource.contains("self.suppressedExecApprovalPromptIDForNotificationSettings = approvalId"))
#expect(rootSource.contains("onRouteChange: self.handleSettingsRouteChange"))
#expect(rootSource.contains("private func handleSettingsRouteChange(_ route: SettingsRoute?)"))
#expect(settingsTabSource.contains("let onRouteChange: ((SettingsRoute?) -> Void)?"))
#expect(settingsTabSource.contains("self.onRouteChange?(self.navigationPath.last)"))
#expect(notificationGuidanceSource.contains("onSuppressFuture"))
#expect(notificationGuidanceSource.contains("suppressFuture: true"))
#expect(notificationGuidanceSource.contains("Text(\"Don't show again\")"))
#expect(rootSource.contains("private func selectSettingsRoute(_ route: SettingsRoute)"))
#expect(settingsSource.contains("title: \"Channels / Integrations\""))
#expect(settingsSource.contains("route: .channels"))
#expect(docsSource.contains(".accessibilityHint(\"Opens Settings / Gateway\")"))
}
@Test func gatewaySettingsKeepsPairingTrustDiagnosticsAndTailscaleActions() throws {
@Test func `gateway settings keeps pairing trust diagnostics and tailscale actions`() throws {
let settingsSource = try String(contentsOf: Self.settingsProTabSourceURL(), encoding: .utf8)
let sectionsSource = try String(contentsOf: Self.settingsProTabSectionsSourceURL(), encoding: .utf8)
let actionsSource = try String(contentsOf: Self.settingsProTabActionsSourceURL(), encoding: .utf8)
@@ -566,7 +596,7 @@ import Testing
#expect(controllerSource.contains("trustRotatedGatewayCertificate(from problem: GatewayConnectionProblem)"))
}
@Test func gatewaySettingsPreviewMatrixCoversPrimaryStates() throws {
@Test func `gateway settings preview matrix covers primary states`() throws {
let supportSource = try String(contentsOf: Self.settingsProTabSupportSourceURL(), encoding: .utf8)
#expect(supportSource.contains("#Preview(\"Gateway settings states\")"))
@@ -585,12 +615,15 @@ import Testing
#expect(supportSource.contains("self.previewButton(\"Diagnose\""))
}
@Test func nativeChatUsesGatewayTransport() throws {
@Test func `native chat uses gateway transport`() throws {
let chatSource = try String(contentsOf: Self.chatProTabSourceURL(), encoding: .utf8)
let channelsSource = try String(contentsOf: Self.channelsSourceURL(), encoding: .utf8)
let settingsSectionsSource = try String(contentsOf: Self.settingsProTabSectionsSourceURL(), encoding: .utf8)
let appModelSource = try String(contentsOf: Self.nodeAppModelSourceURL(), encoding: .utf8)
#expect(chatSource.contains("IOSGatewayChatTransport(gateway: self.appModel.operatorSession)"))
#expect(channelsSource.contains("Message routing and external channel clients."))
#expect(chatSource.matches(of: /self\.appModel\.makeChatTransport\(\)/).count == 2)
#expect(appModelSource.contains("return IOSGatewayChatTransport(gateway: self.operatorSession)"))
#expect(settingsSectionsSource.contains("Message routing and external channel clients."))
#expect(channelsSource.contains("\"clickclack\": SettingsChannelFallbackMetadata"))
#expect(channelsSource.contains("label: \"ClickClack\""))
#expect(channelsSource.contains("Self-hosted chat bot routing."))
@@ -603,6 +636,13 @@ import Testing
.appendingPathComponent("Sources/RootTabs.swift")
}
private static func nodeAppModelSourceURL() -> URL {
URL(fileURLWithPath: #filePath)
.deletingLastPathComponent()
.deletingLastPathComponent()
.appendingPathComponent("Sources/Model/NodeAppModel.swift")
}
private static func phoneHubSourceURL() -> URL {
URL(fileURLWithPath: #filePath)
.deletingLastPathComponent()
@@ -746,6 +786,13 @@ import Testing
.appendingPathComponent("Sources/Design/SettingsProTab.swift")
}
private static func notificationPermissionGuidanceDialogSourceURL() -> URL {
URL(fileURLWithPath: #filePath)
.deletingLastPathComponent()
.deletingLastPathComponent()
.appendingPathComponent("Sources/Gateway/NotificationPermissionGuidanceDialog.swift")
}
private static func settingsProTabActionsSourceURL() -> URL {
URL(fileURLWithPath: #filePath)
.deletingLastPathComponent()

View File

@@ -1,3 +1,5 @@
OpenClaw is now available on iPhone.
Maintenance update for the current OpenClaw release.
Connect to your OpenClaw Gateway to chat with your assistant, use realtime Talk mode, review approvals, share content from iOS, and bring device capabilities like camera, location, screen, and notifications into your private automation workflows.
- Added Apple Watch controls for common agent actions.
- Improved Gateway setup, notification settings, and share-extension identity handling.
- Updated the Watch app integration for current Xcode compatibility.

View File

@@ -1,3 +1,3 @@
{
"version": "2026.6.2"
"version": "2026.6.9"
}

View File

@@ -15,9 +15,9 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>2026.6.2</string>
<string>2026.6.9</string>
<key>CFBundleVersion</key>
<string>2026060200</string>
<string>2026060900</string>
<key>CFBundleIconFile</key>
<string>OpenClaw</string>
<key>CFBundleURLTypes</key>

View File

@@ -69,6 +69,17 @@
"fileName"
]
},
"api": {
"emoji": "🌐",
"title": "API",
"detailKeys": [
"url",
"endpoint",
"path",
"method",
"name"
]
},
"browser": {
"emoji": "🌐",
"title": "Browser",

View File

@@ -6592,6 +6592,7 @@ public struct ExecApprovalRequestParams: Codable, Sendable {
public let turnsourceto: AnyCodable?
public let turnsourceaccountid: AnyCodable?
public let turnsourcethreadid: AnyCodable?
public let approvalreviewerdeviceids: [String]?
public let requiredeliveryroute: Bool?
public let suppressdelivery: Bool?
public let timeoutms: Int?
@@ -6618,6 +6619,7 @@ public struct ExecApprovalRequestParams: Codable, Sendable {
turnsourceto: AnyCodable?,
turnsourceaccountid: AnyCodable?,
turnsourcethreadid: AnyCodable?,
approvalreviewerdeviceids: [String]?,
requiredeliveryroute: Bool? = nil,
suppressdelivery: Bool? = nil,
timeoutms: Int?,
@@ -6643,6 +6645,7 @@ public struct ExecApprovalRequestParams: Codable, Sendable {
self.turnsourceto = turnsourceto
self.turnsourceaccountid = turnsourceaccountid
self.turnsourcethreadid = turnsourcethreadid
self.approvalreviewerdeviceids = approvalreviewerdeviceids
self.requiredeliveryroute = requiredeliveryroute
self.suppressdelivery = suppressdelivery
self.timeoutms = timeoutms
@@ -6670,6 +6673,7 @@ public struct ExecApprovalRequestParams: Codable, Sendable {
case turnsourceto = "turnSourceTo"
case turnsourceaccountid = "turnSourceAccountId"
case turnsourcethreadid = "turnSourceThreadId"
case approvalreviewerdeviceids = "approvalReviewerDeviceIds"
case requiredeliveryroute = "requireDeliveryRoute"
case suppressdelivery = "suppressDelivery"
case timeoutms = "timeoutMs"
@@ -7273,7 +7277,9 @@ public struct ChatSendParams: Codable, Sendable {
public let sessionid: String?
public let message: String
public let thinking: String?
public let fastmode: Bool?
public let fastmodevalue: AnyCodable?
public var fastmode: Bool? { fastmodevalue?.value as? Bool }
public let fastautoonseconds: Int?
public let deliver: Bool?
public let originatingchannel: String?
public let originatingto: String?
@@ -7286,6 +7292,46 @@ public struct ChatSendParams: Codable, Sendable {
public let suppresscommandinterpretation: Bool?
public let idempotencykey: String
public init(
sessionkey: String,
agentid: String? = nil,
sessionid: String?,
message: String,
thinking: String?,
fastmodevalue: AnyCodable?,
fastautoonseconds: Int?,
deliver: Bool?,
originatingchannel: String?,
originatingto: String?,
originatingaccountid: String?,
originatingthreadid: String?,
attachments: [AnyCodable]?,
timeoutms: Int?,
systeminputprovenance: [String: AnyCodable]?,
systemprovenancereceipt: String?,
suppresscommandinterpretation: Bool?,
idempotencykey: String)
{
self.sessionkey = sessionkey
self.agentid = agentid
self.sessionid = sessionid
self.message = message
self.thinking = thinking
self.fastmodevalue = fastmodevalue
self.fastautoonseconds = fastautoonseconds
self.deliver = deliver
self.originatingchannel = originatingchannel
self.originatingto = originatingto
self.originatingaccountid = originatingaccountid
self.originatingthreadid = originatingthreadid
self.attachments = attachments
self.timeoutms = timeoutms
self.systeminputprovenance = systeminputprovenance
self.systemprovenancereceipt = systemprovenancereceipt
self.suppresscommandinterpretation = suppresscommandinterpretation
self.idempotencykey = idempotencykey
}
public init(
sessionkey: String,
agentid: String? = nil,
@@ -7305,23 +7351,25 @@ public struct ChatSendParams: Codable, Sendable {
suppresscommandinterpretation: Bool?,
idempotencykey: String)
{
self.sessionkey = sessionkey
self.agentid = agentid
self.sessionid = sessionid
self.message = message
self.thinking = thinking
self.fastmode = fastmode
self.deliver = deliver
self.originatingchannel = originatingchannel
self.originatingto = originatingto
self.originatingaccountid = originatingaccountid
self.originatingthreadid = originatingthreadid
self.attachments = attachments
self.timeoutms = timeoutms
self.systeminputprovenance = systeminputprovenance
self.systemprovenancereceipt = systemprovenancereceipt
self.suppresscommandinterpretation = suppresscommandinterpretation
self.idempotencykey = idempotencykey
self.init(
sessionkey: sessionkey,
agentid: agentid,
sessionid: sessionid,
message: message,
thinking: thinking,
fastmodevalue: fastmode.map { AnyCodable($0) },
fastautoonseconds: nil,
deliver: deliver,
originatingchannel: originatingchannel,
originatingto: originatingto,
originatingaccountid: originatingaccountid,
originatingthreadid: originatingthreadid,
attachments: attachments,
timeoutms: timeoutms,
systeminputprovenance: systeminputprovenance,
systemprovenancereceipt: systemprovenancereceipt,
suppresscommandinterpretation: suppresscommandinterpretation,
idempotencykey: idempotencykey)
}
private enum CodingKeys: String, CodingKey {
@@ -7330,7 +7378,8 @@ public struct ChatSendParams: Codable, Sendable {
case sessionid = "sessionId"
case message
case thinking
case fastmode = "fastMode"
case fastmodevalue = "fastMode"
case fastautoonseconds = "fastAutoOnSeconds"
case deliver
case originatingchannel = "originatingChannel"
case originatingto = "originatingTo"

View File

@@ -1,4 +1,4 @@
ac06b6c20a93a8543ec1bd3748ef4f7bdae5006839dd93b3fff874d0da4244aa config-baseline.json
e7965566fdaedef445bcd562141f4f3ea1a499cf8ea5956418af7c98049bf242 config-baseline.core.json
2d735389858305509528e74329b6f8c65d311e1471c3b4e91dc17aaab8e63a80 config-baseline.channel.json
0039da0cf2ba2845b37db52c4cf3a0f25e367cf3d2d507c5d6f8a5e5bdfdc4d4 config-baseline.plugin.json
3ac3be8b7e201eb577854806a9806ba90acbfb2616e14b3ffd1169f188620303 config-baseline.json
2923c1120c0369aeca6646cd67f7264590c6a1f4e5bc3157a04d7661324c6868 config-baseline.core.json
769899651e2769833ae7e9c8fbf402e55f3d5e32da6bfe21a9659cc35d1f07bb config-baseline.channel.json
d2e2114f1cd43dc894fe1a4836677b42a2a5af825537d6c4a932da832d58a590 config-baseline.plugin.json

View File

@@ -1,2 +1,2 @@
6f442c09ff2fa618f6f68cc866091a713d2c730090380dd726a9845f4d0fd9bd plugin-sdk-api-baseline.json
d6b1929a42117759a3d0908fb68866e721ee7f0840279dce905a975b461c5b67 plugin-sdk-api-baseline.jsonl
172fe4e143964c0a20525428ff3e6c7631856a7d51c6ad48959a35c72363a410 plugin-sdk-api-baseline.json
a4c18ea9f0b0d2c22183bf8c082e757b7f9852b4c518c8b8cb62a21a9dd766e9 plugin-sdk-api-baseline.jsonl

View File

@@ -183,7 +183,7 @@ Model-selection precedence for isolated jobs is:
3. User-selected stored cron session model override
4. Agent/default model selection
Fast mode follows the resolved live selection too. If the selected model config has `params.fastMode`, isolated cron uses that by default. A stored session `fastMode` override still wins over config in either direction.
Fast mode follows the resolved live selection too. If the selected model config has `params.fastMode`, isolated cron uses that by default. A stored session `fastMode` override still wins over config in either direction. Auto mode uses the selected model's `params.fastAutoOnSeconds` cutoff when present, defaulting to 60 seconds.
If an isolated run hits a live model-switch handoff, cron retries with the switched provider/model and persists that live selection for the active run before retrying. When the switch also carries a new auth profile, cron persists that auth profile override for the active run too. Retries are bounded: after the initial attempt plus 2 switch retries, cron aborts instead of looping forever.

View File

@@ -63,9 +63,9 @@ If `plugins.allow` is a non-empty restrictive list, explicitly selecting
ClickClack in channel setup or running `openclaw plugins enable clickclack`
appends `clickclack` to that list. Onboarding installation uses the same
explicit-selection behavior. These paths do not override `plugins.deny` or a
global `plugins.enabled: false` setting. Direct `openclaw plugins install
clickclack` follows the normal plugin-install policy and also records ClickClack
in an existing allowlist.
global `plugins.enabled: false` setting. Direct
`openclaw plugins install @openclaw/clickclack` follows the normal
plugin-install policy and also records ClickClack in an existing allowlist.
## Multiple bots

View File

@@ -94,28 +94,28 @@ Use this checklist when you already know your old BlueBubbles config and want th
iMessage and BlueBubbles share a lot of channel-level config. The keys that change are mostly transport (REST server vs local CLI). Behavior keys (`dmPolicy`, `groupPolicy`, `allowFrom`, etc.) keep the same meaning.
| BlueBubbles | bundled iMessage | Notes |
| ---------------------------------------------------------- | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `channels.bluebubbles.enabled` | `channels.imessage.enabled` | Same semantics. |
| `channels.bluebubbles.serverUrl` | _(removed)_ | No REST server — the plugin spawns `imsg rpc` over stdio. |
| `channels.bluebubbles.password` | _(removed)_ | No webhook authentication needed. |
| _(implicit)_ | `channels.imessage.cliPath` | Path to `imsg` (default `imsg`); use a wrapper script for SSH. |
| _(implicit)_ | `channels.imessage.dbPath` | Optional Messages.app `chat.db` override; auto-detected when omitted. |
| _(implicit)_ | `channels.imessage.remoteHost` | `host` or `user@host` — only needed when `cliPath` is an SSH wrapper and you want SCP attachment fetches. |
| `channels.bluebubbles.dmPolicy` | `channels.imessage.dmPolicy` | Same values (`pairing` / `allowlist` / `open` / `disabled`). |
| `channels.bluebubbles.allowFrom` | `channels.imessage.allowFrom` | Pairing approvals carry over by handle, not by token. |
| `channels.bluebubbles.groupPolicy` | `channels.imessage.groupPolicy` | Same values (`allowlist` / `open` / `disabled`). |
| `channels.bluebubbles.groupAllowFrom` | `channels.imessage.groupAllowFrom` | Same. |
| `channels.bluebubbles.groups` | `channels.imessage.groups` | **Copy this verbatim, including any `groups: { "*": { ... } }` wildcard entry.** Per-group `requireMention`, `tools`, `toolsBySender` carry over. With `groupPolicy: "allowlist"`, an empty or missing `groups` block silently drops every group message — see "Group registry footgun" below. |
| `channels.bluebubbles.sendReadReceipts` | `channels.imessage.sendReadReceipts` | Default `true`. With the bundled plugin this only fires when the private API probe is up. |
| `channels.bluebubbles.includeAttachments` | `channels.imessage.includeAttachments` | Same shape, **same off-by-default**. If you had attachments flowing on BlueBubbles you must re-set this explicitly on the iMessage block — it does not carry over implicitly, and inbound photos/media will be silently dropped with no `Inbound message` log line until you do. |
| `channels.bluebubbles.attachmentRoots` | `channels.imessage.attachmentRoots` | Local roots; same wildcard rules. |
| _(N/A)_ | `channels.imessage.remoteAttachmentRoots` | Only used when `remoteHost` is set for SCP fetches. |
| `channels.bluebubbles.mediaMaxMb` | `channels.imessage.mediaMaxMb` | Default 16 MB on iMessage (BlueBubbles default was 8 MB). Set explicitly if you want to keep the lower cap. |
| `channels.bluebubbles.textChunkLimit` | `channels.imessage.textChunkLimit` | Default 4000 on both. |
| `channels.bluebubbles.coalesceSameSenderDms` | `channels.imessage.coalesceSameSenderDms` | Same opt-in. DM-only — group chats keep instant per-message dispatch on both channels. Widens the default inbound debounce to 2500 ms when enabled without an explicit `messages.inbound.byChannel.imessage`. See [iMessage docs § Coalescing split-send DMs](/channels/imessage#coalescing-split-send-dms-command--url-in-one-composition). |
| `channels.bluebubbles.enrichGroupParticipantsFromContacts` | _(N/A)_ | iMessage already reads sender display names from `chat.db`. |
| `channels.bluebubbles.actions.*` | `channels.imessage.actions.*` | Per-action toggles: `reactions`, `edit`, `unsend`, `reply`, `sendWithEffect`, `renameGroup`, `setGroupIcon`, `addParticipant`, `removeParticipant`, `leaveGroup`, `sendAttachment`. |
| BlueBubbles | bundled iMessage | Notes |
| ---------------------------------------------------------- | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `channels.bluebubbles.enabled` | `channels.imessage.enabled` | Same semantics. |
| `channels.bluebubbles.serverUrl` | _(removed)_ | No REST server — the plugin spawns `imsg rpc` over stdio. |
| `channels.bluebubbles.password` | _(removed)_ | No webhook authentication needed. |
| _(implicit)_ | `channels.imessage.cliPath` | Path to `imsg` (default `imsg`); use a wrapper script for SSH. |
| _(implicit)_ | `channels.imessage.dbPath` | Optional Messages.app `chat.db` override; auto-detected when omitted. |
| _(implicit)_ | `channels.imessage.remoteHost` | `host` or `user@host` — only needed when `cliPath` is an SSH wrapper and you want SCP attachment fetches. |
| `channels.bluebubbles.dmPolicy` | `channels.imessage.dmPolicy` | Same values (`pairing` / `allowlist` / `open` / `disabled`). |
| `channels.bluebubbles.allowFrom` | `channels.imessage.allowFrom` | Pairing approvals carry over by handle, not by token. |
| `channels.bluebubbles.groupPolicy` | `channels.imessage.groupPolicy` | Same values (`allowlist` / `open` / `disabled`). |
| `channels.bluebubbles.groupAllowFrom` | `channels.imessage.groupAllowFrom` | Same. |
| `channels.bluebubbles.groups` | `channels.imessage.groups` | **Copy this verbatim, including any `groups: { "*": { ... } }` wildcard entry.** Per-group `requireMention`, `tools`, `toolsBySender` carry over. With `groupPolicy: "allowlist"`, an empty or missing `groups` block silently drops every group message — see "Group registry footgun" below. |
| `channels.bluebubbles.sendReadReceipts` | `channels.imessage.sendReadReceipts` | Default `true`. With the bundled plugin this only fires when the private API probe is up. |
| `channels.bluebubbles.includeAttachments` | `channels.imessage.includeAttachments` | Same shape, **same off-by-default**. If you had attachments flowing on BlueBubbles you must re-set this explicitly on the iMessage block — it does not carry over implicitly, and inbound photos/media will be silently dropped with no `Inbound message` log line until you do. |
| `channels.bluebubbles.attachmentRoots` | `channels.imessage.attachmentRoots` | Local roots; same wildcard rules. |
| _(N/A)_ | `channels.imessage.remoteAttachmentRoots` | Only used when `remoteHost` is set for SCP fetches. |
| `channels.bluebubbles.mediaMaxMb` | `channels.imessage.mediaMaxMb` | Default 16 MB on iMessage (BlueBubbles default was 8 MB). Set explicitly if you want to keep the lower cap. |
| `channels.bluebubbles.textChunkLimit` | `channels.imessage.textChunkLimit` | Default 4000 on both. |
| `channels.bluebubbles.coalesceSameSenderDms` | `channels.imessage.coalesceSameSenderDms` | Same opt-in. DM-only — group chats keep instant per-message dispatch on both channels. Widens the default inbound debounce to 7000 ms when enabled without an explicit `messages.inbound.byChannel.imessage` or global `messages.inbound.debounceMs`. See [iMessage docs § Coalescing split-send DMs](/channels/imessage#coalescing-split-send-dms-command--url-in-one-composition). |
| `channels.bluebubbles.enrichGroupParticipantsFromContacts` | _(N/A)_ | iMessage already reads sender display names from `chat.db`. |
| `channels.bluebubbles.actions.*` | `channels.imessage.actions.*` | Per-action toggles: `reactions`, `edit`, `unsend`, `reply`, `sendWithEffect`, `renameGroup`, `setGroupIcon`, `addParticipant`, `removeParticipant`, `leaveGroup`, `sendAttachment`. |
Multi-account configs (`channels.bluebubbles.accounts.*`) translate one-to-one to `channels.imessage.accounts.*`.

View File

@@ -681,7 +681,7 @@ The two rows arrive at OpenClaw ~0.8-2.0 s apart on most setups. Without coalesc
}
```
With the flag on and no explicit `messages.inbound.byChannel.imessage`, the debounce window widens to **2500 ms** (the legacy default is 0 ms — no debouncing). The wider window is required because Apple's split-send cadence of 0.8-2.0 s does not fit in a tighter default.
With the flag on and no explicit `messages.inbound.byChannel.imessage` or global `messages.inbound.debounceMs`, the debounce window widens to **7000 ms** (the legacy default is 0 ms — no debouncing). The wider window is required because Apple's URL-preview split-send cadence can stretch to several seconds while Messages.app emits the preview row.
To tune the window yourself:
@@ -690,10 +690,8 @@ The two rows arrive at OpenClaw ~0.8-2.0 s apart on most setups. Without coalesc
messages: {
inbound: {
byChannel: {
// 2500 ms works for most setups; raise to 4000 ms if your Mac is
// slow or under memory pressure (observed gap can stretch past 2 s
// then).
imessage: 2500,
// 7000 ms covers observed Messages.app URL-preview delays.
imessage: 7000,
},
},
},
@@ -715,15 +713,15 @@ The two rows arrive at OpenClaw ~0.8-2.0 s apart on most setups. Without coalesc
The "Flag on" column shows behavior on an `imsg` build that emits `balloon_bundle_id`. On older `imsg` builds that emit no balloon metadata at all, the rows below marked "Two turns" / "N turns" instead fall back to a legacy merge (one turn): OpenClaw cannot structurally tell a split-send from separate sends, so it preserves the pre-metadata merge. Precise separation activates once the build emits balloon metadata.
| User composes | `chat.db` produces | Flag off (default) | Flag on + window (imsg emits balloon metadata) |
| ------------------------------------------------------------------ | ----------------------------------- | --------------------------------------- | ------------------------------------------------ |
| `Dump https://example.com` (one send) | 2 rows ~1 s apart | Two agent turns: "Dump" alone, then URL | One turn: merged text `Dump https://example.com` |
| `Save this 📎image.jpg caption` (attachment + text) | 2 rows without URL balloon metadata | Two turns | Two turns (legacy merge on metadata-less builds) |
| `/status` (standalone command) | 1 row | Instant dispatch | **Wait up to window, then dispatch** |
| URL pasted alone | 1 row | Instant dispatch | Wait up to window, then dispatch |
| Text + URL sent as two deliberate separate messages, minutes apart | 2 rows outside window | Two turns | Two turns (window expires between them) |
| Rapid flood (>10 small DMs inside window) | N rows without URL balloon metadata | N turns | N turns (legacy merge on metadata-less builds) |
| Two people typing in a group chat | N rows from M senders | M+ turns (one per sender bucket) | M+ turns — group chats are not coalesced |
| User composes | `chat.db` produces | Flag off (default) | Flag on + window (imsg emits balloon metadata) |
| ------------------------------------------------------------------ | ----------------------------------- | --------------------------------------- | --------------------------------------------------------------------------------------------------- |
| `Dump https://example.com` (one send) | 2 rows ~1 s apart | Two agent turns: "Dump" alone, then URL | One turn: merged text `Dump https://example.com` |
| `Save this 📎image.jpg caption` (attachment + text) | 2 rows without URL balloon metadata | Two turns | Two turns after metadata is observed; one merged turn on old/pre-latch metadata-less sessions |
| `/status` (standalone command) | 1 row | Instant dispatch | **Wait up to window, then dispatch** |
| URL pasted alone | 1 row | Instant dispatch | Wait up to window, then dispatch |
| Text + URL sent as two deliberate separate messages, minutes apart | 2 rows outside window | Two turns | Two turns (window expires between them) |
| Rapid flood (>10 small DMs inside window) | N rows without URL balloon metadata | N turns | N turns after metadata is observed; one bounded merged turn on old/pre-latch metadata-less sessions |
| Two people typing in a group chat | N rows from M senders | M+ turns (one per sender bucket) | M+ turns — group chats are not coalesced |
## Inbound recovery after a bridge or gateway restart

View File

@@ -39,9 +39,10 @@ Text is supported everywhere; media and reactions vary by channel.
- [Nextcloud Talk](/channels/nextcloud-talk) - Self-hosted chat via Nextcloud Talk (bundled plugin).
- [Nostr](/channels/nostr) - Decentralized DMs via NIP-04 (bundled plugin).
- [QQ Bot](/channels/qqbot) - QQ Bot API; private chat, group chat, and rich media (bundled plugin).
- [Raft](/channels/raft) - Raft CLI wake bridge for human and agent collaboration (external plugin).
- [Signal](/channels/signal) - signal-cli; privacy-focused.
- [Slack](/channels/slack) - Bolt SDK; workspace apps.
- [SMS](/channels/sms) - Twilio-backed SMS through the Gateway webhook (bundled plugin).
- [SMS](/channels/sms) - Twilio-backed SMS through the Gateway webhook (official plugin).
- [Synology Chat](/channels/synology-chat) - Synology NAS Chat via outgoing+incoming webhooks (bundled plugin).
- [Telegram](/channels/telegram) - Bot API via grammY; supports groups.
- [Tlon](/channels/tlon) - Urbit-based messenger (bundled plugin).

View File

@@ -7,12 +7,18 @@ read_when:
---
Use IRC when you want OpenClaw in classic channels (`#room`) and direct messages.
IRC ships as a bundled plugin, but it is configured in the main config under `channels.irc`.
Install the official IRC plugin, then configure it under `channels.irc`.
## Quick start
1. Enable IRC config in `~/.openclaw/openclaw.json`.
2. Set at least:
1. Install the plugin:
```bash
openclaw plugins install @openclaw/irc
```
2. Enable IRC config in `~/.openclaw/openclaw.json`.
3. Set at least:
```json5
{
@@ -31,7 +37,7 @@ IRC ships as a bundled plugin, but it is configured in the main config under `ch
Prefer a private IRC server for bot coordination. If you intentionally use a public IRC network, common choices include Libera.Chat, OFTC, and Snoonet. Avoid predictable public channels for bot or swarm backchannel traffic.
3. Start/restart gateway:
4. Start/restart gateway:
```bash
openclaw gateway run

View File

@@ -32,7 +32,7 @@ Details: [Plugins](/tools/plugin)
<Steps>
<Step title="Ensure plugin is available">
Current packaged OpenClaw releases already bundle it. Older/custom installs can add it manually with the commands above.
Install `@openclaw/mattermost` with the command above, then restart the Gateway if it is already running.
</Step>
<Step title="Create a Mattermost bot">
Create a Mattermost bot account and copy the **bot token**.

147
docs/channels/raft.md Normal file
View File

@@ -0,0 +1,147 @@
---
summary: "Raft External Agent support through the Raft CLI wake bridge"
read_when:
- You want to connect OpenClaw to a Raft workspace
- You are configuring a Raft External Agent
- You are debugging Raft wake delivery
title: "Raft"
sidebarTitle: "Raft"
---
Raft support connects an OpenClaw agent to a Raft External Agent through the local
Raft CLI. Raft sends authenticated wake hints to the Gateway. The agent then uses
the Raft CLI to check and send messages.
## Install
Raft is an official external plugin. Install it on the Gateway host:
```bash
openclaw plugins install @openclaw/raft
openclaw gateway restart
```
Details: [Plugins](/tools/plugin)
## Prerequisites
- A Raft workspace with an External Agent.
- The Raft CLI installed on the same host as the OpenClaw Gateway.
- A Raft CLI profile that is already signed in and associated with that External Agent.
The plugin does not store Raft credentials. The Raft CLI keeps that authentication
in its own profile.
## Configure
Set the profile in config:
```json5
{
channels: {
raft: {
enabled: true,
profile: "openclaw",
},
},
}
```
For the default account, you can instead set `RAFT_PROFILE` in the Gateway
environment:
```bash
RAFT_PROFILE=openclaw
```
Use a named account when one Gateway connects to more than one Raft External Agent:
```json5
{
channels: {
raft: {
accounts: {
support: {
profile: "support-agent",
},
engineering: {
profile: "engineering-agent",
},
},
},
},
}
```
The interactive setup flow records the same profile:
```bash
openclaw channels setup raft
```
## How It Works
When the Gateway starts, the plugin:
1. Opens a loopback-only HTTP wake endpoint on an ephemeral port.
2. Starts `raft --profile <profile> agent bridge` with that endpoint and a
per-process token.
3. Accepts only authenticated, content-free wake hints with a replay identity from the local bridge.
4. Requires one of `eventId`, `attemptId`, `messageId`, `delivery_id`, `wake_id`, or `id`.
5. Deduplicates recent retried wake deliveries by bridge event id, including across Gateway restarts.
6. Returns a stable runtime session for the current bridge and an empty activity-drain batch for the Raft CLI protocol.
7. Starts one serialized OpenClaw agent turn for each accepted wake.
The bridge owns Raft delivery retries and reconnects. The OpenClaw turn receives
only a wake notice, not a copied Raft message body. It uses the CLI to read
pending messages and to send its response:
```bash
raft --profile openclaw message check
raft --profile openclaw message send
```
<Note>
Raft is not a normal push-message transport. OpenClaw does not automatically
send the model's final text back through the bridge, so the agent must use the
Raft CLI after processing a wake.
</Note>
## Verify
Check that OpenClaw can find the CLI and has a configured profile:
```bash
openclaw channels status --probe
openclaw plugins inspect raft --runtime --json
```
Then send a message to the Raft External Agent. The Gateway log should show the
Raft bridge starting, followed by an inbound wake. The agent should use the
configured Raft profile to check its pending messages.
## Troubleshooting
<AccordionGroup>
<Accordion title="Raft CLI is missing">
Install the Raft CLI on the Gateway host and make `raft` available on the
service's `PATH`. Verify it with `raft --help`, then restart the Gateway.
</Accordion>
<Accordion title="The bridge exits immediately">
Verify the configured profile is signed in and belongs to the intended
Raft External Agent. Run `raft --profile <profile> agent bridge` directly
to see the CLI diagnostic.
</Accordion>
<Accordion title="A wake arrives but no Raft response is sent">
This is expected when the agent does not invoke the Raft CLI. The wake
bridge does not carry message bodies or automatic final replies. Check the
agent's tool policy and ensure it can run `raft --profile <profile> message
check` and `message send`.
</Accordion>
</AccordionGroup>
## References
- [Raft](https://raft.build/)
- [Raft documentation](https://docs.raft.build/welcome/)
- [Hermes Raft integration](https://hermes-agent.nousresearch.com/docs/user-guide/messaging/raft)

View File

@@ -20,12 +20,18 @@ Status: external CLI integration. Gateway talks to `signal-cli` over HTTP — ei
## Quick setup (beginner)
1. Use a **separate Signal number** for the bot (recommended).
2. Install `signal-cli` (Java required if you use the JVM build).
3. Choose one setup path:
2. Install the OpenClaw plugin:
```bash
openclaw plugins install @openclaw/signal
```
3. Install `signal-cli` (Java required if you use the JVM build).
4. Choose one setup path:
- **Path A (QR link):** `signal-cli link -n "OpenClaw"` and scan with Signal.
- **Path B (SMS register):** register a dedicated number with captcha + SMS verification.
4. Configure OpenClaw and restart the gateway.
5. Send a first DM and approve pairing (`openclaw pairing approve signal <CODE>`).
5. Configure OpenClaw and restart the gateway.
6. Send a first DM and approve pairing (`openclaw pairing approve signal <CODE>`).
Minimal config:
@@ -266,6 +272,7 @@ Groups:
- Container mode: the gateway sends via REST API and receives via WebSocket.
- Inbound messages are normalized into the shared channel envelope.
- Replies always route back to the same number or group.
- Replies to inbound messages include native Signal quote metadata when the backend accepts the inbound timestamp and author; if quote metadata is missing or rejected, OpenClaw sends the reply as a normal message.
## Media + limits

View File

@@ -1,11 +1,11 @@
---
summary: "Slack setup and runtime behavior (Socket Mode + HTTP Request URLs)"
summary: "Slack setup and runtime behavior (Socket Mode, HTTP Request URLs, and relay mode)"
read_when:
- Setting up Slack or debugging Slack socket/HTTP mode
- Setting up Slack or debugging Slack socket, HTTP, or relay mode
title: "Slack"
---
Production-ready for DMs and channels via Slack app integrations. Default mode is Socket Mode; HTTP Request URLs are also supported.
Production-ready for DMs and channels via Slack app integrations. Default mode is Socket Mode; HTTP Request URLs are also supported. Relay mode is intended for managed deployments where a trusted router owns Slack ingress.
<CardGroup cols={3}>
<Card title="Pairing" icon="link" href="/channels/pairing">
@@ -41,6 +41,37 @@ Both transports are production-ready and reach feature parity for messaging, sla
**Pick HTTP Request URLs** when running multiple Gateway replicas behind a load balancer, when outbound WSS is blocked but inbound HTTPS is allowed, or when you already terminate Slack webhooks at a reverse proxy.
</Note>
### Relay mode
Relay mode separates Slack ingress from the OpenClaw gateway. A trusted router owns the
single Slack Socket Mode connection, chooses a destination gateway, and forwards a typed
event over an authenticated websocket. The gateway continues to use its bot token for
outbound Slack Web API calls.
```json5
{
channels: {
slack: {
mode: "relay",
botToken: { source: "env", provider: "default", id: "SLACK_BOT_TOKEN" },
relay: {
url: "wss://router.example.com/gateway/ws",
authToken: { source: "env", provider: "default", id: "SLACK_RELAY_AUTH_TOKEN" },
gatewayId: "team-gateway",
},
},
},
}
```
The relay URL must use `wss://` unless it targets localhost. Treat the bearer token and
router route table as part of the Slack authorization boundary: routed events enter the
normal Slack message handler as authorized activations. A router-provided `slack_identity`
in the websocket `hello` frame can set the default outbound username and icon; an explicit
identity supplied by the caller still wins. The relay connection reconnects with the same
bounded backoff timing used by Socket Mode and clears the router-provided identity whenever
it disconnects.
## Install
Install Slack before configuring the channel:
@@ -863,7 +894,8 @@ The default manifest enables the Slack App Home **Home** tab and subscribes to `
- `botToken` + `appToken` are required for Socket Mode.
- HTTP mode requires `botToken` + `signingSecret`.
- `botToken`, `appToken`, `signingSecret`, and `userToken` accept plaintext
- Relay mode requires `botToken` plus `relay.url`, `relay.authToken`, and `relay.gatewayId`; it does not use an app token or signing secret.
- `botToken`, `appToken`, `signingSecret`, `relay.authToken`, and `userToken` accept plaintext
strings or SecretRef objects.
- Config tokens override env fallback.
- `SLACK_BOT_TOKEN` / `SLACK_APP_TOKEN` env fallback applies only to the default account.

View File

@@ -24,6 +24,7 @@ OpenClaw can receive and send SMS through a Twilio phone number or Messaging Ser
You need:
- The official SMS plugin installed with `openclaw plugins install @openclaw/sms`.
- A Twilio account with an SMS-capable phone number, or a Twilio Messaging Service.
- The Twilio Account SID and Auth Token.
- A public HTTPS URL that reaches your OpenClaw Gateway.
@@ -34,6 +35,11 @@ Use one Twilio number for both SMS and Voice Call if the number has both capabil
## Quick Setup
<Steps>
<Step title="Install the plugin">
```bash
openclaw plugins install @openclaw/sms
```
</Step>
<Step title="Create or choose a Twilio sender">
In Twilio, open **Phone Numbers > Manage > Active numbers** and choose an SMS-capable number. Save:

View File

@@ -336,7 +336,8 @@ curl "https://api.telegram.org/bot<bot_token>/getUpdates"
Requirement:
- `channels.telegram.streaming` is `off | partial | block | progress` (default: `partial`)
- `progress` keeps one editable status draft for tool progress, clears it at completion, and sends the final answer as a normal message
- short initial answer previews are debounced, then materialized after a bounded delay if the run is still active
- `progress` keeps one editable status draft for tool progress, shows the stable status label when answer activity arrives before tool progress, clears it at completion, and sends the final answer as a normal message
- `streaming.preview.toolProgress` controls whether tool/progress updates reuse the same edited preview message (default: `true` when preview streaming is active)
- `streaming.preview.commandText` controls command/exec detail inside those tool-progress lines: `raw` (default, preserves released behavior) or `status` (tool label only)
- `streaming.progress.commentary` (default: `false`) opts into assistant commentary/preamble text in the temporary progress draft

View File

@@ -8,38 +8,51 @@ read_when:
- You are changing ClawSweeper dispatch or GitHub activity forwarding
---
OpenClaw CI runs on every push to `main` and every pull request. The `preflight` job classifies the diff and turns expensive lanes off when only unrelated areas changed. Manual `workflow_dispatch` runs intentionally bypass smart scoping and fan out the full graph for release candidates and broad validation. Android lanes stay opt-in through `include_android`. Release-only plugin coverage lives in the separate [`Plugin Prerelease`](#plugin-prerelease) workflow and only runs from [`Full Release Validation`](#full-release-validation) or an explicit manual dispatch.
OpenClaw CI runs on every push to `main` and every pull request. Canonical
`main` pushes first pass through a 90-second hosted-runner admission window.
The existing `CI` concurrency group cancels that waiting run when a newer
commit lands, so sequential merges do not each register a full Blacksmith
matrix. Pull requests and manual dispatches skip the wait. The `preflight` job
then classifies the diff and turns expensive lanes off when only unrelated
areas changed. Manual `workflow_dispatch` runs intentionally bypass smart
scoping and fan out the full graph for release candidates and broad
validation. Android lanes stay opt-in through `include_android`. Release-only
plugin coverage lives in the separate [`Plugin Prerelease`](#plugin-prerelease)
workflow and only runs from [`Full Release Validation`](#full-release-validation)
or an explicit manual dispatch.
## Pipeline overview
| Job | Purpose | When it runs |
| ---------------------------------- | --------------------------------------------------------------------------------------------------------- | ---------------------------------- |
| `preflight` | Detect docs-only changes, changed scopes, changed extensions, and build the CI manifest | Always on non-draft pushes and PRs |
| `security-fast` | Private key detection, changed-workflow audit via `zizmor`, and production lockfile audit | Always on non-draft pushes and PRs |
| `check-dependencies` | Production Knip dependency-only pass plus the unused-file allowlist guard | Node-relevant changes |
| `build-artifacts` | Build `dist/`, Control UI, built-CLI smoke checks, embedded built-artifact checks, and reusable artifacts | Node-relevant changes |
| `checks-fast-core` | Fast Linux correctness lanes such as bundled, protocol, and CI-routing checks | Node-relevant changes |
| `checks-fast-contracts-plugins-*` | Two sharded plugin contract checks | Node-relevant changes |
| `checks-fast-contracts-channels-*` | Two sharded channel contract checks | Node-relevant changes |
| `checks-node-core-*` | Core Node test shards, excluding channel, bundled, contract, and extension lanes | Node-relevant changes |
| `check-*` | Sharded main local gate equivalent: prod types, lint, guards, test types, and strict smoke | Node-relevant changes |
| `check-additional-*` | Architecture, sharded boundary/prompt drift, extension guards, package boundary, and runtime topology | Node-relevant changes |
| `checks-node-compat-node22` | Node 22 compatibility build and smoke lane | Manual CI dispatch for releases |
| `check-docs` | Docs formatting, lint, and broken-link checks | Docs changed |
| `skills-python` | Ruff + pytest for Python-backed skills | Python-skill-relevant changes |
| `checks-windows` | Windows-specific process/path tests plus shared runtime import specifier regressions | Windows-relevant changes |
| `macos-node` | macOS TypeScript test lane using the shared built artifacts | macOS-relevant changes |
| `macos-swift` | Swift lint, build, and tests for the macOS app | macOS-relevant changes |
| `android` | Android unit tests for both flavors plus one debug APK build | Android-relevant changes |
| `test-performance-agent` | Daily Codex slow-test optimization after trusted activity | Main CI success or manual dispatch |
| `openclaw-performance` | Daily/on-demand Kova runtime performance reports with mock-provider, deep-profile, and GPT 5.5 live lanes | Scheduled and manual dispatch |
| Job | Purpose | When it runs |
| ---------------------------------- | --------------------------------------------------------------------------------------------------------- | --------------------------------------------------- |
| `preflight` | Detect docs-only changes, changed scopes, changed extensions, and build the CI manifest | Always on non-draft pushes and PRs |
| `runner-admission` | Hosted 90-second debounce for canonical `main` pushes before Blacksmith work is registered | Every CI run; sleep only on canonical `main` pushes |
| `security-fast` | Private key detection, changed-workflow audit via `zizmor`, and production lockfile audit | Always on non-draft pushes and PRs |
| `check-dependencies` | Production Knip dependency-only pass plus the unused-file allowlist guard | Node-relevant changes |
| `build-artifacts` | Build `dist/`, Control UI, built-CLI smoke checks, embedded built-artifact checks, and reusable artifacts | Node-relevant changes |
| `checks-fast-core` | Fast Linux correctness lanes such as bundled, protocol, and CI-routing checks | Node-relevant changes |
| `checks-fast-contracts-plugins-*` | Two sharded plugin contract checks | Node-relevant changes |
| `checks-fast-contracts-channels-*` | Two sharded channel contract checks | Node-relevant changes |
| `checks-node-core-*` | Core Node test shards, excluding channel, bundled, contract, and extension lanes | Node-relevant changes |
| `check-*` | Sharded main local gate equivalent: prod types, lint, guards, test types, and strict smoke | Node-relevant changes |
| `check-additional-*` | Architecture, sharded boundary/prompt drift, extension guards, package boundary, and runtime topology | Node-relevant changes |
| `checks-node-compat-node22` | Node 22 compatibility build and smoke lane | Manual CI dispatch for releases |
| `check-docs` | Docs formatting, lint, and broken-link checks | Docs changed |
| `skills-python` | Ruff + pytest for Python-backed skills | Python-skill-relevant changes |
| `checks-windows` | Windows-specific process/path tests plus shared runtime import specifier regressions | Windows-relevant changes |
| `macos-node` | macOS TypeScript test lane using the shared built artifacts | macOS-relevant changes |
| `macos-swift` | Swift lint, build, and tests for the macOS app | macOS-relevant changes |
| `android` | Android unit tests for both flavors plus one debug APK build | Android-relevant changes |
| `test-performance-agent` | Daily Codex slow-test optimization after trusted activity | Main CI success or manual dispatch |
| `openclaw-performance` | Daily/on-demand Kova runtime performance reports with mock-provider, deep-profile, and GPT 5.5 live lanes | Scheduled and manual dispatch |
## Fail-fast order
1. `preflight` decides which lanes exist at all. The `docs-scope` and `changed-scope` logic are steps inside this job, not standalone jobs.
2. `security-fast`, `check-*`, `check-additional-*`, `check-docs`, and `skills-python` fail quickly without waiting on the heavier artifact and platform matrix jobs.
3. `build-artifacts` overlaps with the fast Linux lanes so downstream consumers can start as soon as the shared build is ready.
4. Heavier platform and runtime lanes fan out after that: `checks-fast-core`, `checks-fast-contracts-plugins-*`, `checks-fast-contracts-channels-*`, `checks-node-core-*`, `checks-windows`, `macos-node`, `macos-swift`, and `android`.
1. `runner-admission` waits only for canonical `main` pushes; a newer push cancels the run before Blacksmith registration.
2. `preflight` decides which lanes exist at all. The `docs-scope` and `changed-scope` logic are steps inside this job, not standalone jobs.
3. `security-fast`, `check-*`, `check-additional-*`, `check-docs`, and `skills-python` fail quickly without waiting on the heavier artifact and platform matrix jobs.
4. `build-artifacts` overlaps with the fast Linux lanes so downstream consumers can start as soon as the shared build is ready.
5. Heavier platform and runtime lanes fan out after that: `checks-fast-core`, `checks-fast-contracts-plugins-*`, `checks-fast-contracts-channels-*`, `checks-node-core-*`, `checks-windows`, `macos-node`, `macos-swift`, and `android`.
GitHub may mark superseded jobs as `cancelled` when a newer push lands on the same PR or `main` ref. Treat that as CI noise unless the newest run for the same ref is also failing. Matrix jobs use `fail-fast: false`, and `build-artifacts` reports embedded channel, core-support-boundary, and gateway-watch failures directly instead of queuing tiny verifier jobs. The automatic CI concurrency key is versioned (`CI-v7-*`) so a GitHub-side zombie in an old queue group cannot indefinitely block newer main runs. Manual full-suite runs use `CI-manual-v1-*` and do not cancel in-progress runs.
@@ -74,7 +87,15 @@ Scope logic lives in `scripts/ci-changed-scope.mjs` and is covered by unit tests
- **CI routing-only edits, selected cheap core-test fixture edits, and narrow plugin contract helper/test-routing edits** use a fast Node-only manifest path: `preflight`, security, and a single `checks-fast-core` task. That path skips build artifacts, Node 22 compatibility, channel contracts, full core shards, bundled-plugin shards, and additional guard matrices when the change is limited to the routing or helper surfaces the fast task exercises directly.
- **Windows Node checks** are scoped to Windows-specific process/path wrappers, npm/pnpm/UI runner helpers, package manager config, and the CI workflow surfaces that execute that lane; unrelated source, plugin, install-smoke, and test-only changes stay on the Linux Node lanes.
The slowest Node test families are split or balanced so each job stays small without over-reserving runners: plugin contracts and channel contracts each run as two weighted Blacksmith-backed shards with the standard GitHub runner fallback, core unit fast/support lanes run separately, core runtime infra is split between state, process/config, shared, and three cron domain shards, auto-reply runs as balanced workers (with the reply subtree split into agent-runner, dispatch, and commands/state-routing shards), and agentic gateway/server configs are split across chat/auth/model/http-plugin/runtime/startup lanes instead of waiting on built artifacts. Broad browser, QA, media, and miscellaneous plugin tests use their dedicated Vitest configs instead of the shared plugin catch-all. Include-pattern shards record timing entries using the CI shard name, so `.artifacts/vitest-shard-timings.json` can distinguish a whole config from a filtered shard. `check-additional-*` keeps package-boundary compile/canary work together and separates runtime topology architecture from gateway watch coverage; the boundary guard list is striped into one prompt-heavy shard and one combined shard for the remaining guard stripes, each running selected independent guards concurrently and printing per-check timings. The expensive Codex happy-path prompt snapshot drift check runs as its own additional job for manual CI and for prompt-affecting changes only, so normal unrelated Node changes do not wait behind cold prompt snapshot generation and the boundary shards stay balanced while prompt drift is still pinned to the PR that caused it; the same flag skips prompt snapshot Vitest generation inside the built-artifact core support-boundary shard. Gateway watch, channel tests, and the core support-boundary shard run concurrently inside `build-artifacts` after `dist/` and `dist-runtime/` are already built.
The slowest Node test families are split or balanced so each job stays small without over-reserving runners: plugin contracts and channel contracts each run as two weighted Blacksmith-backed shards with the standard GitHub runner fallback, core unit fast/support lanes run separately, core runtime infra is split between state, process/config, shared, and three cron domain shards, auto-reply runs as balanced workers (with the reply subtree split into agent-runner, dispatch, and commands/state-routing shards), and agentic gateway/server configs are split across chat/auth/model/http-plugin/runtime/startup lanes instead of waiting on built artifacts. Normal CI then packs only isolated infra include-pattern shards into deterministic bundles of at most 64 test files, reducing the Node matrix without merging non-isolated command/cron, stateful agents-core, or gateway/server suites; heavy fixed suites stay on 8 vCPU while the bundled and lower-weight lanes use 4 vCPU. Pull requests on the canonical repository use an additional compact admission plan: the same per-config groups run in isolated subprocesses inside the current 34-job Linux Node plan, so a single PR does not register the full 70-plus-job Node matrix. `main` pushes, manual dispatches, and release gates retain the full matrix. Broad browser, QA, media, and miscellaneous plugin tests use their dedicated Vitest configs instead of the shared plugin catch-all. Include-pattern shards record timing entries using the CI shard name, so `.artifacts/vitest-shard-timings.json` can distinguish a whole config from a filtered shard. `check-additional-*` keeps package-boundary compile/canary work together and separates runtime topology architecture from gateway watch coverage; the boundary guard list is striped into one prompt-heavy shard and one combined shard for the remaining guard stripes, each running selected independent guards concurrently and printing per-check timings. The expensive Codex happy-path prompt snapshot drift check runs as its own additional job for manual CI and for prompt-affecting changes only, so normal unrelated Node changes do not wait behind cold prompt snapshot generation and the boundary shards stay balanced while prompt drift is still pinned to the PR that caused it; the same flag skips prompt snapshot Vitest generation inside the built-artifact core support-boundary shard. Gateway watch, channel tests, and the core support-boundary shard run concurrently inside `build-artifacts` after `dist/` and `dist-runtime/` are already built.
Once admitted, canonical Linux CI permits up to 12 concurrent Node jobs and 8 for
the smaller fast/check lanes; Windows and Android stay at two because those
runner pools are narrower.
The compact PR plan emits 18 Node jobs for the current suite: whole-config
groups are batched in isolated subprocesses with a 120-minute batch timeout,
while include-pattern groups share the same bounded job budget.
Android CI runs both `testPlayDebugUnitTest` and `testThirdPartyDebugUnitTest` and then builds the Play debug APK. The third-party flavor has no separate source set or manifest; its unit-test lane still compiles the flavor with the SMS/call-log BuildConfig flags, while avoiding a duplicate debug APK packaging job on every Android-relevant push.
@@ -111,15 +132,15 @@ gh workflow run full-release-validation.yml --ref main -f ref=<branch-or-sha>
## Runners
| Runner | Jobs |
| -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ubuntu-24.04` | Manual CI dispatch and non-canonical repository fallbacks, workflow-sanity, labeler, auto-response, docs workflows outside CI, and install-smoke preflight so the Blacksmith matrix can queue earlier |
| `blacksmith-4vcpu-ubuntu-2404` | `CodeQL Critical Quality`, `preflight`, `security-fast`, lower-weight extension shards, `checks-fast-core`, plugin/channel contract shards, `checks-node-compat-node22`, `check-guards`, `check-prod-types`, and `check-test-types` |
| `blacksmith-8vcpu-ubuntu-2404` | Linux Node test shards, bundled plugin test shards, `check-additional-*` shards, `check-dependencies`, and `android` |
| `blacksmith-16vcpu-ubuntu-2404` | `build-artifacts`, `check-lint` (CPU-sensitive enough that 8 vCPU cost more than they saved); install-smoke Docker builds (32-vCPU queue time cost more than it saved) |
| `blacksmith-16vcpu-windows-2025` | `checks-windows` |
| `blacksmith-6vcpu-macos-15` | `macos-node` on `openclaw/openclaw`; forks fall back to `macos-15` |
| `blacksmith-12vcpu-macos-26` | `macos-swift` on `openclaw/openclaw`; forks fall back to `macos-26` |
| Runner | Jobs |
| ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ubuntu-24.04` | Manual CI dispatch and non-canonical repository fallbacks, workflow-sanity, labeler, auto-response, docs workflows outside CI, and install-smoke preflight so the Blacksmith matrix can queue earlier |
| `blacksmith-4vcpu-ubuntu-2404` | `CodeQL Critical Quality`, `preflight`, `security-fast`, lower-weight extension shards, `checks-fast-core`, plugin/channel contract shards, most bundled/lower-weight Linux Node shards, `check-guards`, `check-prod-types`, `check-test-types`, selected `check-additional-*` shards, and `check-dependencies` |
| `blacksmith-8vcpu-ubuntu-2404` | Retained heavy Linux Node suites, boundary/extension-heavy `check-additional-*` shards, and `android` |
| `blacksmith-16vcpu-ubuntu-2404` | `build-artifacts`, `check-lint` (CPU-sensitive enough that 8 vCPU cost more than they saved); install-smoke Docker builds (32-vCPU queue time cost more than it saved) |
| `blacksmith-8vcpu-windows-2025` | `checks-windows` |
| `blacksmith-6vcpu-macos-15` | `macos-node` on `openclaw/openclaw`; forks fall back to `macos-15` |
| `blacksmith-12vcpu-macos-26` | `macos-swift` on `openclaw/openclaw`; forks fall back to `macos-26` |
Canonical-repo CI keeps Blacksmith as the default runner path for normal push and pull-request runs. `workflow_dispatch` and non-canonical repository runs use GitHub-hosted runners, but normal canonical runs do not currently probe Blacksmith queue health or automatically fall back to GitHub-hosted labels when Blacksmith is unavailable.
@@ -177,7 +198,7 @@ Every lane uploads GitHub artifacts. When `CLAWGRIT_REPORTS_TOKEN` is configured
## Full Release Validation
`Full Release Validation` is the manual umbrella workflow for "run everything before release." It accepts a branch, tag, or full commit SHA, dispatches the manual `CI` workflow with that target, dispatches `Plugin Prerelease` for release-only plugin/package/static/Docker proof, and dispatches `OpenClaw Release Checks` for install smoke, package acceptance, cross-OS package checks, QA Lab parity, Matrix, and Telegram lanes. Stable and full profiles always include exhaustive live/E2E and Docker release-path soak coverage; the beta profile can opt in with `run_release_soak=true`. With `rerun_group=all` and `release_profile=full`, it also runs `NPM Telegram Beta E2E` against the `release-package-under-test` artifact from release checks. After publishing, pass `release_package_spec` to reuse the shipped npm package across release checks, Package Acceptance, Docker, cross-OS, and Telegram without rebuilding. Use `npm_telegram_package_spec` only when Telegram must prove a different package. The Codex plugin live package lane uses the same selected state by default: published `release_package_spec=openclaw@<tag>` derives `codex_plugin_spec=npm:@openclaw/codex@<tag>`, while SHA/artifact runs pack `extensions/codex` from the selected ref. Set `codex_plugin_spec` explicitly for custom plugin sources such as `npm:`, `npm-pack:`, or `git:` specs.
`Full Release Validation` is the manual umbrella workflow for "run everything before release." It accepts a branch, tag, or full commit SHA, dispatches the manual `CI` workflow with that target, dispatches `Plugin Prerelease` for release-only plugin/package/static/Docker proof, and dispatches `OpenClaw Release Checks` for install smoke, package acceptance, cross-OS package checks, QA Lab parity, Matrix, and Telegram lanes. Stable and full profiles always include exhaustive live/E2E and Docker release-path soak coverage; the beta profile can opt in with `run_release_soak=true`. The canonical package Telegram E2E runs inside Package Acceptance, so a full candidate does not start a duplicate live poller. After publishing, pass `release_package_spec` to reuse the shipped npm package across release checks, Package Acceptance, Docker, cross-OS, and Telegram without rebuilding. Use `npm_telegram_package_spec` only for a focused published-package Telegram rerun. The Codex plugin live package lane uses the same selected state by default: published `release_package_spec=openclaw@<tag>` derives `codex_plugin_spec=npm:@openclaw/codex@<tag>`, while SHA/artifact runs pack `extensions/codex` from the selected ref. Set `codex_plugin_spec` explicitly for custom plugin sources such as `npm:`, `npm-pack:`, or `git:` specs.
See [Full release validation](/reference/full-release-validation) for the
stage matrix, exact workflow job names, profile differences, artifacts, and

View File

@@ -197,7 +197,7 @@ Isolated cron resolves the active model in this order:
### Fast mode
Isolated cron fast mode follows the resolved live model selection. Model config `params.fastMode` applies by default, but a stored session `fastMode` override still wins over config.
Isolated cron fast mode follows the resolved live model selection. Model config `params.fastMode` applies by default, but a stored session `fastMode` override still wins over config. When the resolved mode is `auto`, the cutoff uses the selected model's `params.fastAutoOnSeconds` value, defaulting to 60 seconds.
### Live model switch retries

View File

@@ -165,10 +165,15 @@ When you set `--url`, the CLI does not fall back to config or environment creden
```bash
openclaw gateway health --url ws://127.0.0.1:18789
openclaw gateway health --port 18789
```
The HTTP `/healthz` endpoint is a liveness probe: it returns once the server can answer HTTP. The HTTP `/readyz` endpoint is stricter and stays red while startup plugin sidecars, channels, or configured hooks are still settling. Local or authenticated detailed readiness responses include an `eventLoop` diagnostic block with event-loop delay, event-loop utilization, CPU core ratio, and a `degraded` flag.
<ParamField path="--port <port>" type="number">
Target a local loopback Gateway on this port. This overrides `OPENCLAW_GATEWAY_URL` and `OPENCLAW_GATEWAY_PORT` for the health call.
</ParamField>
### `gateway usage-cost`
Fetch usage-cost summaries from session logs.
@@ -340,8 +345,13 @@ If multiple probe targets are reachable, it prints all of them. An SSH tunnel, T
```bash
openclaw gateway probe
openclaw gateway probe --json
openclaw gateway probe --port 18789
```
<ParamField path="--port <port>" type="number">
Use this port for the local loopback probe target and SSH tunnel remote port. Without `--url`, this selects the local loopback target instead of configured gateway environment URL, environment port, or remote targets.
</ParamField>
<AccordionGroup>
<Accordion title="Interpretation">
- `Reachable: yes` means at least one target accepted a WebSocket connect.

View File

@@ -230,8 +230,8 @@ canonical subscription `github-copilot` provider and is **never** selected by
The harness claims its provider, runtime, CLI session key, and auth profile
prefix in `extensions/copilot/doctor-contract-api.ts`, which
`openclaw doctor` auto-loads. For configuration, auth, transcript mirroring,
compaction, the doctor probe surface, and the broader PI vs Codex vs Copilot
SDK decision, see [GitHub Copilot agent runtime](/plugins/copilot).
compaction, the declarative doctor contract, and the broader PI vs Codex vs
Copilot SDK decision, see [GitHub Copilot agent runtime](/plugins/copilot).
## Compatibility contract

View File

@@ -333,7 +333,7 @@ Gateway model capability checks also read explicit `models.providers.<id>.models
### Moonshot AI (Kimi)
Moonshot ships as a bundled provider plugin. Use the built-in provider by default, and add an explicit `models.providers.moonshot` entry only when you need to override the base URL or model metadata:
Install `@openclaw/moonshot-provider` before onboarding. Add an explicit `models.providers.moonshot` entry only when you need to override the base URL or model metadata:
- Provider: `moonshot`
- Auth: `MOONSHOT_API_KEY`

View File

@@ -302,13 +302,13 @@ Live transport runners should import the shared scenario ids, baseline
coverage helpers, and scenario-selection helper from
`openclaw/plugin-sdk/qa-live-transport-scenarios`.
| Lane | Canary | Mention gating | Bot-to-bot | Allowlist block | Top-level reply | Restart resume | Thread follow-up | Thread isolation | Reaction observation | Help command | Native command registration |
| -------- | ------ | -------------- | ---------- | --------------- | --------------- | -------------- | ---------------- | ---------------- | -------------------- | ------------ | --------------------------- |
| Matrix | x | x | x | x | x | x | x | x | x | | |
| Telegram | x | x | x | | | | | | | x | |
| Discord | x | x | x | | | | | | | | x |
| Slack | x | x | x | x | x | x | x | x | | | |
| WhatsApp | x | x | | x | x | x | | | x | x | |
| Lane | Canary | Mention gating | Bot-to-bot | Allowlist block | Top-level reply | Quote reply | Restart resume | Thread follow-up | Thread isolation | Reaction observation | Help command | Native command registration |
| -------- | ------ | -------------- | ---------- | --------------- | --------------- | ----------- | -------------- | ---------------- | ---------------- | -------------------- | ------------ | --------------------------- |
| Matrix | x | x | x | x | x | | x | x | x | x | | |
| Telegram | x | x | x | | | | | | | | x | |
| Discord | x | x | x | | | | | | | | | x |
| Slack | x | x | x | x | x | | x | x | x | | | |
| WhatsApp | x | x | | x | x | x | x | | | x | x | |
This keeps `qa-channel` as the broad product-behavior suite while Matrix,
Telegram, and other live transports share one explicit transport-contract checklist.
@@ -731,8 +731,9 @@ Scenario catalog (`extensions/qa-lab/src/live-transports/whatsapp/whatsapp-live.
`whatsapp-whoami-command`, `whatsapp-context-command`,
`whatsapp-native-new-command`.
- Reply and final-output behavior: `whatsapp-tool-only-usage-footer`,
`whatsapp-reply-to-message`, `whatsapp-reply-context-isolation`,
`whatsapp-reply-delivery-shape`, `whatsapp-stream-final-message-accounting`.
`whatsapp-reply-to-message`, `whatsapp-group-reply-to-message`,
`whatsapp-reply-context-isolation`, `whatsapp-reply-delivery-shape`,
`whatsapp-stream-final-message-accounting`.
- Inbound media and structured messages: `whatsapp-inbound-image-caption`,
`whatsapp-audio-preflight`, `whatsapp-inbound-structured-messages`,
`whatsapp-group-audio-gating`. These send real WhatsApp image, audio,
@@ -749,9 +750,9 @@ Scenario catalog (`extensions/qa-lab/src/live-transports/whatsapp/whatsapp-live.
`whatsapp-approval-plugin-native`.
- Status reactions: `whatsapp-status-reactions`.
The catalog currently contains 35 scenarios. The `live-frontier` default lane is
kept small at 8 scenarios for fast smoke coverage. The `mock-openai` default
lane runs 29 deterministic scenarios through the real WhatsApp transport while
The catalog currently contains 36 scenarios. The `live-frontier` default lane is
kept small at 10 scenarios for fast smoke coverage. The `mock-openai` default
lane runs 31 deterministic scenarios through the real WhatsApp transport while
mocking only model output. Approval scenarios and a few heavier/blocking checks
remain explicit by scenario id.

View File

@@ -160,9 +160,10 @@ Legacy key migration:
Telegram:
- Uses `sendMessage` + `editMessageText` preview updates across DMs and group/topics.
- Short initial previews are still debounced for push-notification UX, but Telegram now materializes them after a bounded delay so active runs do not stay visually silent.
- Final text edits the active preview in place; long finals reuse that message for the first chunk and send only the remaining chunks.
- `block` mode rotates the preview into a new message at `streaming.preview.chunk.maxChars` (default 800, capped at Telegram's 4096 edit limit); other modes grow one preview up to 4096 characters.
- `progress` mode keeps tool progress in an editable status draft, clears that draft at completion, and sends the final answer through normal delivery.
- `progress` mode keeps tool progress in an editable status draft, materializes the status label when answer streaming is active but no tool line is available yet, clears that draft at completion, and sends the final answer through normal delivery.
- If the final edit fails before the completed text is confirmed, OpenClaw uses normal final delivery and cleans up the stale preview.
- Preview streaming is skipped when Telegram block streaming is explicitly enabled (to avoid double-streaming).
- `/reasoning stream` can write reasoning to a transient preview that is deleted after final delivery.

View File

@@ -1122,6 +1122,7 @@
"channels/mattermost",
"channels/nextcloud-talk",
"channels/nostr",
"channels/raft",
"channels/tlon",
"channels/synology-chat",
"channels/twitch"

View File

@@ -249,9 +249,10 @@ Shared defaults for bounded runtime context surfaces.
- `toolResultMaxChars`: advanced live tool-result ceiling used for persisted
results and overflow recovery. Leave unset for the model-context auto cap:
`16000` chars below 100K tokens, `32000` chars at 100K+ tokens, and `64000`
chars at 200K+ tokens. The effective cap is still limited to about 30% of the
model context window. `openclaw doctor --deep` prints the effective cap, and
doctor warns only when an explicit override is stale or has no effect.
chars at 200K+ tokens. Explicit values up to `1000000` are accepted for
long-context models, but the effective cap is still limited to about 30% of
the model context window. `openclaw doctor --deep` prints the effective cap,
and doctor warns only when an explicit override is stale or has no effect.
- `postCompactionMaxChars`: AGENTS.md excerpt cap used during post-compaction
refresh injection.
@@ -1098,7 +1099,7 @@ for provider examples and precedence.
- `skills`: optional per-agent skill allowlist. If omitted, the agent inherits `agents.defaults.skills` when set; an explicit list replaces defaults instead of merging, and `[]` means no skills.
- `thinkingDefault`: optional per-agent default thinking level (`off | minimal | low | medium | high | xhigh | adaptive | max`). Overrides `agents.defaults.thinkingDefault` for this agent when no per-message or session override is set. The selected provider/model profile controls which values are valid; for Google Gemini, `adaptive` keeps provider-owned dynamic thinking (`thinkingLevel` omitted on Gemini 3/3.1, `thinkingBudget: -1` on Gemini 2.5).
- `reasoningDefault`: optional per-agent default reasoning visibility (`on | off | stream`). Overrides `agents.defaults.reasoningDefault` for this agent when no per-message or session reasoning override is set.
- `fastModeDefault`: optional per-agent default for fast mode (`true | false`). Applies when no per-message or session fast-mode override is set.
- `fastModeDefault`: optional per-agent default for fast mode (`"auto" | true | false`). Applies when no per-message or session fast-mode override is set.
- `models`: optional per-agent model catalog/runtime overrides keyed by full `provider/model` ids. Use `models["provider/model"].agentRuntime` for per-agent runtime exceptions.
- `runtime`: optional per-agent runtime descriptor. Use `type: "acp"` with `runtime.acp` defaults (`agent`, `backend`, `mode`, `cwd`) when the agent should default to ACP harness sessions.
- `identity.avatar`: workspace-relative path, `http(s)` URL, or `data:` URI.

View File

@@ -160,7 +160,6 @@ must be paired with `--lint`; regular doctor and repair runs reject them.
- State integrity and permissions checks (sessions, transcripts, state dir).
- Config file permission checks (chmod 600) when running locally.
- Model auth health: checks OAuth expiry, can refresh expiring tokens, and reports auth-profile cooldown/disabled states.
- Extra workspace dir detection (`~/openclaw`).
</Accordion>
<Accordion title="Gateway, services, and supervisors">
@@ -469,14 +468,14 @@ That stages grounded durable candidates into the short-term dreaming store while
<Accordion title="10. systemd linger (Linux)">
If running as a systemd user service, doctor ensures lingering is enabled so the gateway stays alive after logout.
</Accordion>
<Accordion title="11. Workspace status (skills, plugins, and legacy dirs)">
<Accordion title="11. Workspace status (skills, plugins, and TaskFlows)">
Doctor prints a summary of the workspace state for the default agent:
- **Skills status**: counts eligible, missing-requirements, and allowlist-blocked skills.
- **Legacy workspace dirs**: warns when `~/openclaw` or other legacy workspace directories exist alongside the current workspace.
- **Plugin status**: counts enabled/disabled/errored plugins; lists plugin IDs for any errors; reports bundle plugin capabilities.
- **Plugin compatibility warnings**: flags plugins that have compatibility issues with the current runtime.
- **Plugin diagnostics**: surfaces any load-time warnings or errors emitted by the plugin registry.
- **TaskFlow recovery**: surfaces suspicious managed TaskFlows that need manual inspection or cancellation.
</Accordion>
<Accordion title="11b. Bootstrap file size">

View File

@@ -445,6 +445,7 @@ enumeration of `src/gateway/server-methods/*.ts`.
- `sessions.get` returns the full stored session row.
- Chat execution still uses `chat.history`, `chat.send`, `chat.abort`, and `chat.inject`. `chat.history` is display-normalized for UI clients: inline directive tags are stripped from visible text, plain-text tool-call XML payloads (including `<tool_call>...</tool_call>`, `<function_call>...</function_call>`, `<tool_calls>...</tool_calls>`, `<function_calls>...</function_calls>`, and truncated tool-call blocks) and leaked ASCII/full-width model control tokens are stripped, pure silent-token assistant rows such as exact `NO_REPLY` / `no_reply` are omitted, and oversized rows can be replaced with placeholders.
- `chat.message.get` is the additive bounded full-message reader for a single visible transcript entry. Clients pass `sessionKey`, optional `agentId` when the session selection is agent-scoped, plus a transcript `messageId` previously surfaced through `chat.history`, and the Gateway returns the same display-normalized projection without the lightweight history truncation cap when the stored entry is still available and not oversized.
- `chat.send` accepts one-turn `fastMode: "auto"` to use fast mode for model calls started before the auto cutoff, then start later retry, fallback, tool-result, or continuation calls without fast mode. The cutoff defaults to 60 seconds and can be configured per model with `agents.defaults.models["<provider>/<model>"].params.fastAutoOnSeconds`. A `chat.send` caller can pass one-turn `fastAutoOnSeconds` to override the cutoff for that request.
</Accordion>

View File

@@ -174,6 +174,7 @@ troubleshooting, see the main [FAQ](/help/faq).
- **Per session:** send `/fast on` while the session is using `openai/gpt-5.5`.
- **Per model default:** set `agents.defaults.models["openai/gpt-5.5"].params.fastMode` to `true`.
- **Automatic cutoff:** use `/fast auto` or `params.fastMode: "auto"` to start new model calls fast until the auto cutoff, then start later retry, fallback, tool-result, or continuation calls without fast mode. The cutoff defaults to 60 seconds; set `params.fastAutoOnSeconds` on the active model to change it.
Example:
@@ -184,7 +185,8 @@ troubleshooting, see the main [FAQ](/help/faq).
models: {
"openai/gpt-5.5": {
params: {
fastMode: true,
fastMode: "auto",
fastAutoOnSeconds: 30,
},
},
},
@@ -193,7 +195,7 @@ troubleshooting, see the main [FAQ](/help/faq).
}
```
For OpenAI, fast mode maps to `service_tier = "priority"` on supported native Responses requests. Session `/fast` overrides beat config defaults.
For OpenAI, fast mode maps to `service_tier = "priority"` on supported native Responses requests. Session `/fast` overrides beat config defaults. Codex app-server turns can only receive the tier at turn start, so `auto` applies on the next OpenClaw-started model turn rather than inside one already-running app-server turn.
See [Thinking and fast mode](/tools/thinking) and [OpenAI fast mode](/providers/openai#fast-mode).

View File

@@ -15,15 +15,18 @@ OpenClaw treats **wake words as a single global list** owned by the **Gateway**.
## Storage (Gateway host)
Wake words are stored on the gateway machine at:
Wake words and routing rules are stored in the gateway state database:
- `~/.openclaw/settings/voicewake.json`
- `~/.openclaw/state/openclaw.sqlite`
Shape:
The active tables are:
```json
{ "triggers": ["openclaw", "claude", "computer"], "updatedAtMs": 1730000000000 }
```
- `voicewake_triggers`
- `voicewake_routing_config`
- `voicewake_routing_routes`
Legacy `settings/voicewake.json` and `settings/voicewake-routing.json` files are
doctor migration inputs only; runtime reads and writes the SQLite tables.
## Protocol

View File

@@ -143,39 +143,12 @@ The native Codex app-server harness supports context engines that require
pre-prompt assembly. Generic CLI backends, including `codex-cli`, do not provide
that host capability.
Codex thread bindings live in OpenClaw's SQLite plugin state and use the stable
agent-scoped OpenClaw session key, or an opaque conversation-binding id, as
their owner. Physical session ids fence delayed cleanup but may rotate without
losing the Codex thread. Context-engine compaction adopts the successor id
before continuing native Codex compaction. The bounded store rejects a new
binding at its safety limit instead of evicting an existing thread's continuity
record.
Conversation binds create or resume their Codex thread on the first bound
message after channel approval; an abandoned approval consumes no thread row.
That first message carries the prepared thread directly into its turn.
Subsequent messages use a metadata-only resume to subscribe the shared client,
then unsubscribe after the turn completes.
The runtime does not poll transcript-adjacent binding files. Upgrades from
releases that used `*.jsonl.codex-app-server.json` sidecars migrate them during
normal startup preflight. `openclaw doctor --fix` can run the same migration
manually.
Successfully matched sidecars are archived before the new runtime resumes their
threads. Migration imports durable thread ownership only; it does not infer
Codex context usage from OpenClaw counters or crawl Codex rollout files. For
agent-session harness bindings, the next resume attempts to restore a cached
native snapshot when Codex has one, and ongoing turns persist the current-context
usage reported by app-server notifications, not the cumulative thread lifetime
total. Conversation bindings
keep metadata-only resumes and leave continuity and compaction with the native
Codex thread. Conflicting or ambiguous sidecars stay in place with a warning for
operator review.
For Codex-backed agents, `/compact` starts native Codex app-server compaction on
the bound thread. OpenClaw bounds the request-acceptance RPC but does not wait
for compaction completion, restart the shared app-server, or fall back to a
context-engine or public OpenAI summarizer. If the native Codex thread binding
is missing or stale, the command fails closed so the operator sees the real
runtime boundary instead of silently switching compaction backends.
the bound thread. OpenClaw does not wait for completion, impose an OpenClaw
timeout, restart the shared app-server, or fall back to a context-engine or
public OpenAI summarizer. If the native Codex thread binding is missing or
stale, the command fails closed so the operator sees the real runtime boundary
instead of silently switching compaction backends.
```json5
{

View File

@@ -33,15 +33,12 @@ For the broader model/provider/runtime split, start with
- A GitHub Copilot subscription that can drive the Copilot CLI (or a
`gitHubToken` env / auth-profile entry for headless / cron runs).
- A writable `copilotHome` directory. The harness defaults to
`~/.openclaw/agents/<agentId>/copilot` for full per-agent isolation. The
platform default (`%APPDATA%\copilot` on Windows, `$XDG_CONFIG_HOME/copilot`
or `~/.config/copilot` elsewhere) is used as the doctor probe fallback when
no explicit home is set.
`<agentDir>/copilot` when OpenClaw provides an agent directory, otherwise
`~/.openclaw/agents/<agentId>/copilot` for full per-agent isolation.
`openclaw doctor` runs the plugin
[doctor contract](#doctor-and-probes) for the extension; failures there are
the canonical way to confirm the environment is ready before opting an agent
in.
[doctor contract](#doctor) for declarative session-state ownership and future
compatibility migrations. It does not run Copilot CLI environment probes.
## Plugin install
@@ -153,10 +150,6 @@ the same directory), or `~/.openclaw/agents/<agentId>/copilot` otherwise.
Override with `copilotHome: <path>` on the attempt input when you need a
custom location (for example, a shared mount for migration).
`probeCopilotAuthShape` (see [Doctor and probes](#doctor-and-probes)) is the
pure shape check that validates which of the modes above will be used.
It does not perform a live SDK handshake.
## Configuration surface
The harness reads its config from per-attempt input
@@ -239,7 +232,7 @@ asserted in
[`extensions/copilot/harness.test.ts`](https://github.com/openclaw/openclaw/blob/main/extensions/copilot/harness.test.ts)
under `describe("runSideQuestion")`.
## Doctor and probes
## Doctor
`extensions/copilot/doctor-contract-api.ts` is auto-loaded by
`src/plugins/doctor-contract-registry.ts`. It contributes:
@@ -251,18 +244,6 @@ under `describe("runSideQuestion")`.
runtime `copilot`; CLI session key `copilot`; auth profile
prefix `github-copilot:`.
`extensions/copilot/src/doctor-probes.ts` exports three imperative probes
that hosts (including `openclaw doctor`) can call to verify the environment:
| Probe | What it checks | Reasons it can fail |
| -------------------------- | --------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
| `probeCopilotCliVersion` | `copilot --version` exits 0 with a non-empty version string | `non-zero-exit`, `empty-version`, `spawn-failed`, `spawn-error`, `probe-timeout` |
| `probeCopilotHomeWritable` | `mkdir -p copilotHome` + write + rm a marker file | `copilothome-not-writable` (with the underlying fs error in `details.rawError`) |
| `probeCopilotAuthShape` | At least one of `useLoggedInUser`, `gitHubToken`, or `profileId`+`profileVersion` | `no-auth-source` |
Each probe accepts a DI seam (`spawnFn`, `fsApi`) so tests do not spawn the
real Copilot CLI or touch the host fs.
## Limitations
- The harness only claims the canonical `github-copilot` provider at MVP.

View File

@@ -146,6 +146,7 @@ observation-only.
- `subagent_delivery_target` - compatibility hook for completion delivery when no core session binding can project a route.
- `subagent_spawning` - deprecated compatibility hook. Core now prepares `thread: true` subagent bindings through channel session-binding adapters before `subagent_spawned` fires.
- `subagent_spawned` includes `resolvedModel` and `resolvedProvider` when OpenClaw has resolved the child session's native model before launch.
- `subagent_ended` carries `targetSessionKey` (identity — this matches `subagent_spawned.childSessionKey`), `targetKind` (`"subagent"` or `"acp"`), `reason`, optional `outcome` (`"ok"`, `"error"`, `"timeout"`, `"killed"`, `"reset"`, or `"deleted"`), optional `error`, `runId`, `endedAt`, `accountId`, and `sendFarewell`. It does **not** include `agentId` or `childSessionKey`; use `targetSessionKey` to correlate with the corresponding `subagent_spawned` event.
**Lifecycle**

View File

@@ -51,7 +51,7 @@ Each entry lists the package, distribution route, and description.
## Core npm package
72 plugins
59 plugins
- **[admin-http-rpc](/plugins/reference/admin-http-rpc)** (`@openclaw/admin-http-rpc`) - included in OpenClaw. OpenClaw admin HTTP RPC endpoint.
@@ -69,8 +69,6 @@ Each entry lists the package, distribution route, and description.
- **[canvas](/plugins/reference/canvas)** (`@openclaw/canvas-plugin`) - included in OpenClaw. Experimental Canvas control and A2UI rendering surfaces for paired nodes.
- **[clickclack](/plugins/reference/clickclack)** (`@openclaw/clickclack`) - included in OpenClaw. Adds the Clickclack channel surface for sending and receiving OpenClaw messages.
- **[codex-supervisor](/plugins/reference/codex-supervisor)** (`@openclaw/codex-supervisor`) - included in OpenClaw. Supervise Codex app-server sessions from OpenClaw.
- **[cohere](/plugins/reference/cohere)** (`@openclaw/cohere-provider`) - included in OpenClaw; npm; ClawHub: `clawhub:@openclaw/cohere-provider`. OpenClaw Cohere provider plugin.
@@ -91,8 +89,6 @@ Each entry lists the package, distribution route, and description.
- **[file-transfer](/plugins/reference/file-transfer)** (`@openclaw/file-transfer`) - included in OpenClaw. Fetch, list, and write files on paired nodes via dedicated node commands. Bypasses bash stdout truncation by using base64 over node.invoke for binaries up to 16 MB.
- **[fireworks](/plugins/reference/fireworks)** (`@openclaw/fireworks-provider`) - included in OpenClaw. Adds Fireworks model provider support to OpenClaw.
- **[github-copilot](/plugins/reference/github-copilot)** (`@openclaw/github-copilot-provider`) - included in OpenClaw. Adds GitHub Copilot model provider support to OpenClaw.
- **[google](/plugins/reference/google)** (`@openclaw/google-plugin`) - included in OpenClaw. Adds Google, Google Gemini CLI, Google Vertex model provider support to OpenClaw.
@@ -101,16 +97,12 @@ Each entry lists the package, distribution route, and description.
- **[imessage](/plugins/reference/imessage)** (`@openclaw/imessage`) - included in OpenClaw. Adds the iMessage channel surface for sending and receiving OpenClaw messages.
- **[irc](/plugins/reference/irc)** (`@openclaw/irc`) - included in OpenClaw. Adds the IRC channel surface for sending and receiving OpenClaw messages.
- **[litellm](/plugins/reference/litellm)** (`@openclaw/litellm-provider`) - included in OpenClaw. Adds LiteLLM model provider support to OpenClaw.
- **[llm-task](/plugins/reference/llm-task)** (`@openclaw/llm-task`) - included in OpenClaw. Generic JSON-only LLM tool for structured tasks callable from workflows.
- **[lmstudio](/plugins/reference/lmstudio)** (`@openclaw/lmstudio-provider`) - included in OpenClaw. Adds LM Studio model provider support to OpenClaw.
- **[mattermost](/plugins/reference/mattermost)** (`@openclaw/mattermost`) - included in OpenClaw. Adds the Mattermost channel surface for sending and receiving OpenClaw messages.
- **[memory-core](/plugins/reference/memory-core)** (`@openclaw/memory-core`) - included in OpenClaw. Adds agent-callable tools.
- **[memory-wiki](/plugins/reference/memory-wiki)** (`@openclaw/memory-wiki`) - included in OpenClaw. Persistent wiki compiler and Obsidian-friendly knowledge vault for OpenClaw.
@@ -127,8 +119,6 @@ Each entry lists the package, distribution route, and description.
- **[mistral](/plugins/reference/mistral)** (`@openclaw/mistral-provider`) - included in OpenClaw. Adds Mistral model provider support to OpenClaw.
- **[moonshot](/plugins/reference/moonshot)** (`@openclaw/moonshot-provider`) - included in OpenClaw. Adds Moonshot model provider support to OpenClaw.
- **[novita](/plugins/reference/novita)** (`@openclaw/novita-provider`) - included in OpenClaw. Adds Novita, Novita AI, Novitaai model provider support to OpenClaw.
- **[nvidia](/plugins/reference/nvidia)** (`@openclaw/nvidia-provider`) - included in OpenClaw. Adds NVIDIA model provider support to OpenClaw.
@@ -151,32 +141,18 @@ Each entry lists the package, distribution route, and description.
- **[runway](/plugins/reference/runway)** (`@openclaw/runway-provider`) - included in OpenClaw. Adds video generation provider support.
- **[searxng](/plugins/reference/searxng)** (`@openclaw/searxng-plugin`) - included in OpenClaw. Adds web search provider support.
- **[senseaudio](/plugins/reference/senseaudio)** (`@openclaw/senseaudio-provider`) - included in OpenClaw. Adds media understanding provider support.
- **[sglang](/plugins/reference/sglang)** (`@openclaw/sglang-provider`) - included in OpenClaw. Adds SGLang model provider support to OpenClaw.
- **[signal](/plugins/reference/signal)** (`@openclaw/signal`) - included in OpenClaw. Adds the Signal channel surface for sending and receiving OpenClaw messages.
- **[sms](/plugins/reference/sms)** (`@openclaw/sms`) - included in OpenClaw. Twilio SMS channel plugin for OpenClaw text messages.
- **[synthetic](/plugins/reference/synthetic)** (`@openclaw/synthetic-provider`) - included in OpenClaw. Adds Synthetic model provider support to OpenClaw.
- **[tavily](/plugins/reference/tavily)** (`@openclaw/tavily-plugin`) - included in OpenClaw. Adds agent-callable tools. Adds web search provider support.
- **[telegram](/plugins/reference/telegram)** (`@openclaw/telegram`) - included in OpenClaw. Adds the Telegram channel surface for sending and receiving OpenClaw messages.
- **[tencent](/plugins/reference/tencent)** (`@openclaw/tencent-provider`) - included in OpenClaw. Adds Tencent TokenHub model provider support to OpenClaw.
- **[together](/plugins/reference/together)** (`@openclaw/together-provider`) - included in OpenClaw. Adds Together model provider support to OpenClaw.
- **[tts-local-cli](/plugins/reference/tts-local-cli)** (`@openclaw/tts-local-cli`) - included in OpenClaw. Adds text-to-speech provider support.
- **[venice](/plugins/reference/venice)** (`@openclaw/venice-provider`) - included in OpenClaw. Adds Venice model provider support to OpenClaw.
- **[vercel-ai-gateway](/plugins/reference/vercel-ai-gateway)** (`@openclaw/vercel-ai-gateway-provider`) - included in OpenClaw. Adds Vercel AI Gateway model provider support to OpenClaw.
- **[vllm](/plugins/reference/vllm)** (`@openclaw/vllm-provider`) - included in OpenClaw. Adds vLLM model provider support to OpenClaw.
- **[volcengine](/plugins/reference/volcengine)** (`@openclaw/volcengine-provider`) - included in OpenClaw. Adds Volcengine, Volcengine Plan model provider support to OpenClaw.
@@ -195,11 +171,9 @@ Each entry lists the package, distribution route, and description.
- **[xiaomi](/plugins/reference/xiaomi)** (`@openclaw/xiaomi-provider`) - included in OpenClaw. Adds Xiaomi, Xiaomi Token Plan model provider support to OpenClaw.
- **[zai](/plugins/reference/zai)** (`@openclaw/zai-provider`) - included in OpenClaw. Adds Z.AI model provider support to OpenClaw.
## Official external packages
54 plugins
68 plugins
- **[acpx](/plugins/reference/acpx)** (`@openclaw/acpx`) - npm; ClawHub. OpenClaw ACP runtime backend with plugin-owned session and transport management.
@@ -217,6 +191,8 @@ Each entry lists the package, distribution route, and description.
- **[chutes](/plugins/reference/chutes)** (`@openclaw/chutes-provider`) - npm; ClawHub: `clawhub:@openclaw/chutes-provider`. Adds Chutes model provider support to OpenClaw.
- **[clickclack](/plugins/reference/clickclack)** (`@openclaw/clickclack`) - npm; ClawHub: `clawhub:@openclaw/clickclack`. Adds the Clickclack channel surface for sending and receiving OpenClaw messages.
- **[cloudflare-ai-gateway](/plugins/reference/cloudflare-ai-gateway)** (`@openclaw/cloudflare-ai-gateway-provider`) - npm; ClawHub: `clawhub:@openclaw/cloudflare-ai-gateway-provider`. Adds Cloudflare AI Gateway model provider support to OpenClaw.
- **[codex](/plugins/reference/codex)** (`@openclaw/codex`) - npm; ClawHub. OpenClaw Codex app-server harness and model provider plugin with a Codex-managed GPT catalog.
@@ -243,6 +219,8 @@ Each entry lists the package, distribution route, and description.
- **[firecrawl](/plugins/reference/firecrawl)** (`@openclaw/firecrawl-plugin`) - npm; ClawHub: `clawhub:@openclaw/firecrawl-plugin`. Adds agent-callable tools. Adds web fetch provider support. Adds web search provider support.
- **[fireworks](/plugins/reference/fireworks)** (`@openclaw/fireworks-provider`) - npm; ClawHub: `clawhub:@openclaw/fireworks-provider`. Adds Fireworks model provider support to OpenClaw.
- **[gmi](/plugins/reference/gmi)** (`@openclaw/gmi-provider`) - npm; ClawHub: `clawhub:@openclaw/gmi-provider`. OpenClaw GMI Cloud provider plugin.
- **[google-meet](/plugins/reference/google-meet)** (`@openclaw/google-meet`) - npm; ClawHub. OpenClaw Google Meet participant plugin for joining calls through Chrome or Twilio transports.
@@ -255,6 +233,8 @@ Each entry lists the package, distribution route, and description.
- **[inworld](/plugins/reference/inworld)** (`@openclaw/inworld-speech`) - npm; ClawHub: `clawhub:@openclaw/inworld-speech`. Inworld streaming text-to-speech (MP3, OGG_OPUS, PCM telephony).
- **[irc](/plugins/reference/irc)** (`@openclaw/irc`) - npm; ClawHub: `clawhub:@openclaw/irc`. Adds the IRC channel surface for sending and receiving OpenClaw messages.
- **[kilocode](/plugins/reference/kilocode)** (`@openclaw/kilocode-provider`) - npm; ClawHub: `clawhub:@openclaw/kilocode-provider`. Adds Kilocode model provider support to OpenClaw.
- **[kimi](/plugins/reference/kimi)** (`@openclaw/kimi-provider`) - npm; ClawHub: `clawhub:@openclaw/kimi-provider`. Adds Kimi, Kimi Coding model provider support to OpenClaw.
@@ -267,8 +247,12 @@ Each entry lists the package, distribution route, and description.
- **[matrix](/plugins/reference/matrix)** (`@openclaw/matrix`) - ClawHub: `clawhub:@openclaw/matrix`; npm. OpenClaw Matrix channel plugin for rooms and direct messages.
- **[mattermost](/plugins/reference/mattermost)** (`@openclaw/mattermost`) - npm; ClawHub: `clawhub:@openclaw/mattermost`. Adds the Mattermost channel surface for sending and receiving OpenClaw messages.
- **[memory-lancedb](/plugins/reference/memory-lancedb)** (`@openclaw/memory-lancedb`) - npm; ClawHub. OpenClaw LanceDB-backed long-term memory plugin with auto-recall, auto-capture, and vector search.
- **[moonshot](/plugins/reference/moonshot)** (`@openclaw/moonshot-provider`) - npm; ClawHub: `clawhub:@openclaw/moonshot-provider`. Adds Moonshot model provider support to OpenClaw.
- **[msteams](/plugins/reference/msteams)** (`@openclaw/msteams`) - npm; ClawHub. OpenClaw Microsoft Teams channel plugin for bot conversations.
- **[nextcloud-talk](/plugins/reference/nextcloud-talk)** (`@openclaw/nextcloud-talk`) - npm; ClawHub. OpenClaw Nextcloud Talk channel plugin for conversations.
@@ -289,22 +273,40 @@ Each entry lists the package, distribution route, and description.
- **[qwen](/plugins/reference/qwen)** (`@openclaw/qwen-provider`) - npm; ClawHub: `clawhub:@openclaw/qwen-provider`. Adds Qwen, Qwen Cloud, Model Studio, DashScope, Qwen Oauth, Qwen Portal, Qwen CLI model provider support to OpenClaw.
- **[raft](/plugins/reference/raft)** (`@openclaw/raft`) - npm; ClawHub. OpenClaw Raft channel plugin for secure CLI wake bridges.
- **[searxng](/plugins/reference/searxng)** (`@openclaw/searxng-plugin`) - npm; ClawHub: `clawhub:@openclaw/searxng-plugin`. Adds web search provider support.
- **[signal](/plugins/reference/signal)** (`@openclaw/signal`) - npm; ClawHub: `clawhub:@openclaw/signal`. Adds the Signal channel surface for sending and receiving OpenClaw messages.
- **[slack](/plugins/reference/slack)** (`@openclaw/slack`) - npm; ClawHub. OpenClaw Slack channel plugin for channels, DMs, commands, and app events.
- **[stepfun](/plugins/reference/stepfun)** (`@openclaw/stepfun-provider`) - npm. Adds StepFun, StepFun Plan model provider support to OpenClaw.
- **[sms](/plugins/reference/sms)** (`@openclaw/sms`) - npm; ClawHub: `clawhub:@openclaw/sms`. Twilio SMS channel plugin for OpenClaw text messages.
- **[stepfun](/plugins/reference/stepfun)** (`@openclaw/stepfun-provider`) - npm; ClawHub: `clawhub:@openclaw/stepfun-provider`. Adds StepFun, StepFun Plan model provider support to OpenClaw.
- **[synology-chat](/plugins/reference/synology-chat)** (`@openclaw/synology-chat`) - npm; ClawHub. Synology Chat channel plugin for OpenClaw channels and direct messages.
- **[tavily](/plugins/reference/tavily)** (`@openclaw/tavily-plugin`) - npm; ClawHub: `clawhub:@openclaw/tavily-plugin`. Adds agent-callable tools. Adds web search provider support.
- **[tencent](/plugins/reference/tencent)** (`@openclaw/tencent-provider`) - npm; ClawHub: `clawhub:@openclaw/tencent-provider`. Adds Tencent TokenHub model provider support to OpenClaw.
- **[tlon](/plugins/reference/tlon)** (`@openclaw/tlon`) - npm; ClawHub. OpenClaw Tlon/Urbit channel plugin for chat workflows.
- **[tokenjuice](/plugins/reference/tokenjuice)** (`@openclaw/tokenjuice`) - npm; ClawHub: `clawhub:@openclaw/tokenjuice`. Compacts exec and bash tool results with tokenjuice reducers.
- **[twitch](/plugins/reference/twitch)** (`@openclaw/twitch`) - npm; ClawHub. OpenClaw Twitch channel plugin for chat and moderation workflows.
- **[venice](/plugins/reference/venice)** (`@openclaw/venice-provider`) - npm; ClawHub: `clawhub:@openclaw/venice-provider`. Adds Venice model provider support to OpenClaw.
- **[vercel-ai-gateway](/plugins/reference/vercel-ai-gateway)** (`@openclaw/vercel-ai-gateway-provider`) - npm; ClawHub: `clawhub:@openclaw/vercel-ai-gateway-provider`. Adds Vercel AI Gateway model provider support to OpenClaw.
- **[voice-call](/plugins/reference/voice-call)** (`@openclaw/voice-call`) - npm; ClawHub. OpenClaw voice-call plugin for Twilio, Telnyx, and Plivo phone calls.
- **[whatsapp](/plugins/reference/whatsapp)** (`@openclaw/whatsapp`) - ClawHub: `clawhub:@openclaw/whatsapp`; npm. OpenClaw WhatsApp channel plugin for WhatsApp Web chats.
- **[zai](/plugins/reference/zai)** (`@openclaw/zai-provider`) - npm; ClawHub: `clawhub:@openclaw/zai-provider`. Adds Z.AI model provider support to OpenClaw.
- **[zalo](/plugins/reference/zalo)** (`@openclaw/zalo`) - npm; ClawHub. OpenClaw Zalo channel plugin for bot and webhook chats.
- **[zalouser](/plugins/reference/zalouser)** (`@openclaw/zalouser`) - npm; ClawHub. OpenClaw Zalo Personal Account plugin via native zca-js integration.

View File

@@ -15,5 +15,5 @@ This page is generated from `extensions/*/package.json` and
pnpm plugins:inventory:gen
```
Use [Plugin inventory](/plugins/plugin-inventory) to browse all 128
Use [Plugin inventory](/plugins/plugin-inventory) to browse all 129
generated plugin reference pages by distribution, package, and description.

View File

@@ -16,4 +16,4 @@ Experimental Canvas control and A2UI rendering surfaces for paired nodes.
## Surface
contracts: tools
contracts: tools; skills

View File

@@ -12,7 +12,7 @@ Adds the Clickclack channel surface for sending and receiving OpenClaw messages.
## Distribution
- Package: `@openclaw/clickclack`
- Install route: included in OpenClaw
- Install route: npm; ClawHub: `clawhub:@openclaw/clickclack`
## Surface

View File

@@ -16,7 +16,7 @@ OpenClaw Discord channel plugin for channels, DMs, commands, and app events.
## Surface
channels: discord; contracts: transcriptSourceProviders
channels: discord; contracts: transcriptSourceProviders; skills
## Related docs

View File

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

View File

@@ -12,7 +12,7 @@ Adds the IRC channel surface for sending and receiving OpenClaw messages.
## Distribution
- Package: `@openclaw/irc`
- Install route: included in OpenClaw
- Install route: npm; ClawHub: `clawhub:@openclaw/irc`
## Surface

View File

@@ -12,7 +12,7 @@ Adds the Mattermost channel surface for sending and receiving OpenClaw messages.
## Distribution
- Package: `@openclaw/mattermost`
- Install route: included in OpenClaw
- Install route: npm; ClawHub: `clawhub:@openclaw/mattermost`
## Surface

View File

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

View File

@@ -0,0 +1,23 @@
---
summary: "OpenClaw Raft channel plugin for secure CLI wake bridges."
read_when:
- You are installing, configuring, or auditing the raft plugin
title: "Raft plugin"
---
# Raft plugin
OpenClaw Raft channel plugin for secure CLI wake bridges.
## Distribution
- Package: `@openclaw/raft`
- Install route: npm; ClawHub
## Surface
channels: raft
## Related docs
- [raft](/channels/raft)

View File

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

View File

@@ -12,7 +12,7 @@ Adds the Signal channel surface for sending and receiving OpenClaw messages.
## Distribution
- Package: `@openclaw/signal`
- Install route: included in OpenClaw
- Install route: npm; ClawHub: `clawhub:@openclaw/signal`
## Surface

View File

@@ -16,7 +16,7 @@ OpenClaw Slack channel plugin for channels, DMs, commands, and app events.
## Surface
channels: slack
channels: slack; skills
## Related docs

View File

@@ -12,7 +12,7 @@ Twilio SMS channel plugin for OpenClaw text messages.
## Distribution
- Package: `@openclaw/sms`
- Install route: included in OpenClaw
- Install route: npm; ClawHub: `clawhub:@openclaw/sms`
## Surface

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -16,7 +16,7 @@ OpenClaw voice-call plugin for Twilio, Telnyx, and Plivo phone calls.
## Surface
contracts: tools
contracts: tools; skills
## Related docs

View File

@@ -16,7 +16,7 @@ OpenClaw WhatsApp channel plugin for WhatsApp Web chats.
## Surface
channels: whatsapp
channels: whatsapp; skills
## Related docs

View File

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

View File

@@ -247,7 +247,7 @@ usage endpoint failed or returned no usable usage data.
| `plugin-sdk/reply-history` | Shared short-window reply-history helpers. New message-turn code should use `createChannelHistoryWindow`; lower-level map helpers remain deprecated compatibility exports only |
| `plugin-sdk/reply-reference` | `createReplyReferencePlanner` |
| `plugin-sdk/reply-chunking` | Narrow text/markdown chunking helpers |
| `plugin-sdk/session-store-runtime` | Session workflow helpers (`getSessionEntry`, `listSessionEntries`, `patchSessionEntry`, `upsertSessionEntry`), legacy session store path/session-key helpers, updated-at reads, and transition-only whole-store/file-path compatibility helpers |
| `plugin-sdk/session-store-runtime` | Session workflow helpers (`getSessionEntry`, `listSessionEntries`, `patchSessionEntry`, `upsertSessionEntry`), bounded recent user/assistant transcript text reads by session identity, legacy session store path/session-key helpers, updated-at reads, and transition-only whole-store/file-path compatibility helpers |
| `plugin-sdk/session-transcript-runtime` | Transcript identity, scoped target/read/write helpers, update publishing, write locks, and transcript memory hit keys |
| `plugin-sdk/sqlite-runtime` | Focused SQLite agent-schema, path, and transaction helpers for first-party runtime |
| `plugin-sdk/cron-store-runtime` | Cron store path/load/save helpers |

View File

@@ -759,7 +759,7 @@ Tool name: `voice_call`.
| `end_call` | `callId` |
| `get_status` | `callId` |
This repo ships a matching skill doc at `skills/voice-call/SKILL.md`.
The voice-call plugin ships a matching agent skill.
## Gateway RPC

View File

@@ -7,12 +7,12 @@ read_when:
- You are debugging Kimi thinking-off behavior on Fireworks
---
[Fireworks](https://fireworks.ai) exposes open-weight and routed models through an OpenAI-compatible API. OpenClaw includes a bundled Fireworks provider plugin that ships with two pre-cataloged Kimi models and accepts any Fireworks model or router id at runtime.
[Fireworks](https://fireworks.ai) exposes open-weight and routed models through an OpenAI-compatible API. Install the official Fireworks provider plugin to use two pre-cataloged Kimi models and any Fireworks model or router id at runtime.
| Property | Value |
| --------------- | ------------------------------------------------------ |
| Provider id | `fireworks` (alias: `fireworks-ai`) |
| Plugin | bundled, `enabledByDefault: true` |
| Package | `@openclaw/fireworks-provider` |
| Auth env var | `FIREWORKS_API_KEY` |
| Onboarding flag | `--auth-choice fireworks-api-key` |
| Direct CLI flag | `--fireworks-api-key <key>` |
@@ -24,6 +24,11 @@ read_when:
## Getting started
<Steps>
<Step title="Install the plugin">
```bash
openclaw plugins install @openclaw/fireworks-provider
```
</Step>
<Step title="Set the Fireworks API key">
<CodeGroup>
@@ -108,7 +113,7 @@ OpenClaw accepts any Fireworks model or router id at runtime. Use the exact id s
</Accordion>
<Accordion title="Why thinking is forced off for Kimi">
Fireworks K2.6 returns a 400 if the request carries `reasoning_*` parameters even though Kimi supports thinking through Moonshot's own API. The bundled policy (`extensions/fireworks/thinking-policy.ts`) advertises only the `off` thinking level for Kimi model ids, so manual `/think` switches and provider-policy surfaces stay aligned with the runtime contract.
Fireworks K2.6 returns a 400 if the request carries `reasoning_*` parameters even though Kimi supports thinking through Moonshot's own API. The provider policy (`extensions/fireworks/thinking-policy.ts`) advertises only the `off` thinking level for Kimi model ids, so manual `/think` switches and provider-policy surfaces stay aligned with the runtime contract.
To use Kimi reasoning end-to-end, configure the [Moonshot provider](/providers/moonshot) and route the same model through it.

View File

@@ -30,7 +30,7 @@ Moonshot and Kimi Coding are **separate providers**. Keys are not interchangeabl
[//]: # "moonshot-kimi-k2-ids:end"
Bundled cost estimates for current Moonshot-hosted K2 models use Moonshot's
Catalog cost estimates for current Moonshot-hosted K2 models use Moonshot's
published pay-as-you-go rates: Kimi K2.7 Code is $0.19/MTok cache hit,
$0.95/MTok input, and $4.00/MTok output; Kimi K2.6 is $0.16/MTok cache hit,
$0.95/MTok input, and $4.00/MTok output; Kimi K2.5 is $0.10/MTok cache hit,
@@ -213,6 +213,11 @@ Choose your provider and follow the setup steps.
</Note>
<Steps>
<Step title="Install the plugin">
```bash
openclaw plugins install @openclaw/kimi-provider
```
</Step>
<Step title="Run onboarding">
```bash
openclaw onboard --auth-choice kimi-code-api-key
@@ -257,8 +262,7 @@ Choose your provider and follow the setup steps.
## Kimi web search
OpenClaw also ships **Kimi** as a `web_search` provider, backed by Moonshot web
search.
The Moonshot plugin also registers **Kimi** as a `web_search` provider, backed by Moonshot web search.
<Steps>
<Step title="Run interactive web search setup">
@@ -405,7 +409,7 @@ Config lives under `plugins.entries.moonshot.config.webSearch`:
capabilities, so compatible custom provider ids targeting the same native
Moonshot hosts inherit the same streaming-usage behavior.
With the bundled K2.6 pricing, streamed usage that includes input, output,
With the catalog K2.6 pricing, streamed usage that includes input, output,
and cache-read tokens is also converted into local estimated USD cost for
`/status`, `/usage full`, `/usage cost`, and transcript-backed session
accounting.

View File

@@ -915,17 +915,17 @@ the Server-side compaction accordion below.
<Accordion title="Fast mode">
OpenClaw exposes a shared fast-mode toggle for `openai/*`:
- **Chat/UI:** `/fast status|on|off`
- **Chat/UI:** `/fast status|auto|on|off`
- **Config:** `agents.defaults.models["<provider>/<model>"].params.fastMode`
When enabled, OpenClaw maps fast mode to OpenAI priority processing (`service_tier = "priority"`). Existing `service_tier` values are preserved, and fast mode does not rewrite `reasoning` or `text.verbosity`.
When enabled, OpenClaw maps fast mode to OpenAI priority processing (`service_tier = "priority"`). Existing `service_tier` values are preserved, and fast mode does not rewrite `reasoning` or `text.verbosity`. `fastMode: "auto"` starts new model calls fast until the auto cutoff, then starts later retry, fallback, tool-result, or continuation calls without fast mode. The cutoff defaults to 60 seconds; set `params.fastAutoOnSeconds` on the active model to change it.
```json5
{
agents: {
defaults: {
models: {
"openai/gpt-5.5": { params: { fastMode: true } },
"openai/gpt-5.5": { params: { fastMode: "auto", fastAutoOnSeconds: 30 } },
},
},
},

View File

@@ -28,8 +28,10 @@ The provider includes:
| ------------------------------- | --------------------- |
| `opencode-go/glm-5` | GLM-5 |
| `opencode-go/glm-5.1` | GLM-5.1 |
| `opencode-go/glm-5.2` | GLM-5.2 |
| `opencode-go/kimi-k2.5` | Kimi K2.5 |
| `opencode-go/kimi-k2.6` | Kimi K2.6 (3x limits) |
| `opencode-go/kimi-k2.7-code` | Kimi K2.7 Code |
| `opencode-go/deepseek-v4-pro` | DeepSeek V4 Pro |
| `opencode-go/deepseek-v4-flash` | DeepSeek V4 Flash |
| `opencode-go/mimo-v2-omni` | MiMo V2 Omni |
@@ -39,6 +41,8 @@ The provider includes:
| `opencode-go/qwen3.5-plus` | Qwen3.5 Plus |
| `opencode-go/qwen3.6-plus` | Qwen3.6 Plus |
GLM-5.2 uses a 1M-token context window and supports up to 131K output tokens.
## Getting started
<Tabs>

View File

@@ -6,12 +6,12 @@ read_when:
- You need the TokenHub API key setup
---
Tencent Cloud ships as a bundled provider plugin in OpenClaw. It gives access to Tencent Hy3 preview through the TokenHub endpoint (`tencent-tokenhub`) using an OpenAI-compatible API.
Install the official Tencent Cloud provider plugin to access Tencent Hy3 preview through the TokenHub endpoint (`tencent-tokenhub`) using an OpenAI-compatible API.
| Property | Value |
| ---------------- | ----------------------------------------------------- |
| Provider id | `tencent-tokenhub` |
| Plugin | bundled, `enabledByDefault: true` |
| Package | `@openclaw/tencent-provider` |
| Auth env var | `TOKENHUB_API_KEY` |
| Onboarding flag | `--auth-choice tokenhub-api-key` |
| Direct CLI flag | `--tokenhub-api-key <key>` |
@@ -23,6 +23,11 @@ Tencent Cloud ships as a bundled provider plugin in OpenClaw. It gives access to
## Quick start
<Steps>
<Step title="Install the plugin">
```bash
openclaw plugins install @openclaw/tencent-provider
```
</Step>
<Step title="Create a TokenHub API key">
Create an API key in Tencent Cloud TokenHub. If you choose a limited access scope for the key, include **Hy3 preview** in the allowed models.
</Step>
@@ -78,7 +83,7 @@ Hy3 preview is Tencent Hunyuan's large MoE language model for reasoning, long-co
## Tiered pricing
The bundled catalog ships tiered cost metadata that scales with input window length, so cost estimates are populated without manual overrides.
The provider catalog ships tiered cost metadata that scales with input window length, so cost estimates are populated without manual overrides.
| Input tokens range | Input rate | Output rate | Cache read |
| ------------------ | ---------- | ----------- | ---------- |

View File

@@ -42,6 +42,11 @@ Anonymized models are **not** fully private. Venice strips metadata before forwa
## Getting started
<Steps>
<Step title="Install the plugin">
```bash
openclaw plugins install @openclaw/venice-provider
```
</Step>
<Step title="Get your API key">
1. Sign up at [venice.ai](https://venice.ai)
2. Go to **Settings > API Keys > Create new key**

View File

@@ -9,12 +9,13 @@ read_when:
The [Vercel AI Gateway](https://vercel.com/ai-gateway) provides a unified API to
access hundreds of models through a single endpoint.
| Property | Value |
| ------------- | -------------------------------- |
| Provider | `vercel-ai-gateway` |
| Auth | `AI_GATEWAY_API_KEY` |
| API | Anthropic Messages compatible |
| Model catalog | Auto-discovered via `/v1/models` |
| Property | Value |
| ------------- | -------------------------------------- |
| Provider | `vercel-ai-gateway` |
| Package | `@openclaw/vercel-ai-gateway-provider` |
| Auth | `AI_GATEWAY_API_KEY` |
| API | Anthropic Messages compatible |
| Model catalog | Auto-discovered via `/v1/models` |
<Tip>
OpenClaw auto-discovers the Gateway `/v1/models` catalog, so
@@ -26,6 +27,11 @@ OpenClaw auto-discovers the Gateway `/v1/models` catalog, so
## Getting started
<Steps>
<Step title="Install the plugin">
```bash
openclaw plugins install @openclaw/vercel-ai-gateway-provider
```
</Step>
<Step title="Set the API key">
Run onboarding and choose the AI Gateway auth option:

View File

@@ -13,6 +13,7 @@ OpenClaw uses the `zai` provider with a Z.AI API key.
| Property | Value |
| -------- | -------------------------------------------- |
| Provider | `zai` |
| Package | `@openclaw/zai-provider` |
| Auth | `ZAI_API_KEY` (legacy alias: `Z_AI_API_KEY`) |
| API | Z.AI Chat Completions (Bearer auth) |
@@ -23,6 +24,12 @@ refs such as `zai/glm-5.2`: provider `zai`, model id `glm-5.2`.
## Getting started
Install the provider plugin first:
```bash
openclaw plugins install @openclaw/zai-provider
```
<Tabs>
<Tab title="Auto-detect endpoint">
**Best for:** most users. OpenClaw probes supported Z.AI endpoints with your API key and applies the correct base URL automatically.
@@ -96,7 +103,7 @@ you want to force a specific Coding Plan or general API surface.
## Built-in catalog
OpenClaw ships the bundled `zai` provider catalog in the plugin manifest, so read-only
The `zai` provider plugin ships its catalog in the plugin manifest, so read-only
listing can show known GLM rows without loading provider runtime:
```bash
@@ -126,6 +133,11 @@ The manifest-backed catalog currently includes:
GLM models are available as `zai/<model>` (example: `zai/glm-5`).
</Tip>
<Tip>
GLM-5.2 supports `off`, `low`, `high`, and `max` thinking levels. OpenClaw maps
`low` and `high` to Z.AI high reasoning effort, and `max` to max effort.
</Tip>
<Note>
Coding Plan setup defaults to `zai/glm-5.2`; general API setup keeps
`zai/glm-5.1`. Endpoint auto-detection falls back to `glm-5.1` or `glm-4.7`
@@ -138,7 +150,7 @@ known to your installed version.
<AccordionGroup>
<Accordion title="Forward-resolving unknown GLM-5 models">
Unknown `glm-5*` ids still forward-resolve on the bundled provider path by
Unknown `glm-5*` ids still forward-resolve on the provider path by
synthesizing provider-owned metadata from the `glm-4.7` template when the id
matches the current GLM-5 family shape.
</Accordion>
@@ -195,7 +207,7 @@ known to your installed version.
</Accordion>
<Accordion title="Image understanding">
The bundled Z.AI plugin registers image understanding.
The Z.AI plugin registers image understanding.
| Property | Value |
| ------------- | ----------- |

View File

@@ -228,9 +228,9 @@ release state.
`OpenClaw Release Checks` for install smoke, package acceptance, cross-OS
package checks, QA Lab parity, Matrix, and Telegram lanes. Stable and full
runs always include exhaustive live/E2E and Docker release-path soak;
`run_release_soak=true` is retained for an explicit beta soak. With
`release_profile=full` and `rerun_group=all`, it also runs package Telegram
E2E against the `release-package-under-test` artifact from release checks.
`run_release_soak=true` is retained for an explicit beta soak. Package
Acceptance provides the canonical package Telegram E2E during candidate
validation, avoiding a second concurrent live poller.
Provide `release_package_spec` after publishing a beta to reuse the shipped
npm package across release checks, Package Acceptance, and package Telegram
E2E without rebuilding the release tarball. Provide
@@ -460,20 +460,16 @@ gh workflow run full-release-validation.yml \
```
The workflow resolves the target ref, dispatches manual `CI` with
`target_ref=<release-ref>`, dispatches `OpenClaw Release Checks`, prepares a
parent `release-package-under-test` artifact for package-facing checks, and
dispatches standalone package Telegram E2E when `release_profile=full` with
`rerun_group=all` or when `release_package_spec` or
`npm_telegram_package_spec` is set. `OpenClaw Release
Checks` then fans out install smoke, cross-OS release checks, live/E2E Docker
release-path coverage when soak is enabled, Package Acceptance with Telegram
package QA, QA Lab parity, live Matrix, and live Telegram. A full/all run is
only acceptable when the `Full Release Validation` summary shows `normal_ci`,
`plugin_prerelease`, and `release_checks` as successful, unless a focused rerun
intentionally skipped the separate `Plugin Prerelease` child. In full/all mode,
the `npm_telegram` child must also be successful; outside full/all it is skipped
unless a published `release_package_spec` or `npm_telegram_package_spec` was
provided. The final
`target_ref=<release-ref>`, then dispatches `OpenClaw Release Checks`.
`OpenClaw Release Checks` fans out install smoke, cross-OS release checks,
live/E2E Docker release-path coverage when soak is enabled, Package Acceptance
with the canonical Telegram package E2E, QA Lab parity, live Matrix, and live
Telegram. A full/all run is only acceptable when the `Full Release Validation`
summary shows `normal_ci`, `plugin_prerelease`, and `release_checks` as
successful, unless a focused rerun intentionally skipped the separate `Plugin
Prerelease` child. Use the standalone `npm-telegram` child only for a focused
published-package rerun with `release_package_spec` or
`npm_telegram_package_spec`. The final
verifier summary includes slowest-job tables for each child run, so the release
manager can see the current critical path without downloading logs.
See [Full release validation](/reference/full-release-validation) for the
@@ -558,8 +554,8 @@ runs only the release-only plugin child, `release-checks` runs every release
box, and the narrower release groups are `install-smoke`, `cross-os`,
`live-e2e`, `package`, `qa`, `qa-parity`, `qa-live`, and `npm-telegram`.
Focused `npm-telegram` reruns require `release_package_spec` or
`npm_telegram_package_spec`; full/all runs with `release_profile=full` use the
release-checks package artifact. Focused
`npm_telegram_package_spec`; full/all runs use the canonical package Telegram
E2E inside Package Acceptance. Focused
cross-OS reruns can add `cross_os_suite_filter=windows/packaged-upgrade` or
another OS/suite filter. QA release-check failures block normal release
validation, including required OpenClaw dynamic tool drift in the standard tier.

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