Compare commits

..

1926 Commits

Author SHA1 Message Date
joshp123
5083fbb62f docs: improve plugin inventory layout 2026-06-06 15:12:19 +02:00
Nimrod Gutman
a547010a95 fix(talk): resolve realtime provider secret refs (#90914)
Merged via squash.

Prepared head SHA: c5a52049be
Reviewed-by: @ngutman
2026-06-06 15:47:13 +03:00
Onur Solmaz
0aea58ab66 fix(memory): fail fast when embeddings provider is unavailable
Fixes #89691.

Memory search now treats explicitly configured non-local embedding providers as required. When that provider is unavailable, search and sync surface an unavailable memory-search result instead of silently returning FTS-only recall.

Unset/default/local/none-style paths keep FTS fallback so existing workflows do not lose keyword recall entirely. The fallback state is now surfaced in diagnostics/status instead of being hidden.

Maintainer merge note: current CI still has unrelated baseline boundary failures in extensions/google/google.live.test.ts and extensions/minimax/minimax.live.test.ts. This PR does not touch those files; the PR-specific memory, docs, lint, type, security, and ClawSweeper checks were reviewed before merge.
2026-06-06 20:39:38 +08:00
Vincent Koc
6b2af6c1ee fix(agents): keep safe tool images without native backend 2026-06-06 05:11:55 -07:00
Vincent Koc
0a08625d79 fix(agents): emit terminal abort lifecycle metadata
Carry terminal abort state into embedded agent lifecycle events before agent_end emits, and include terminal stopReason from the last assistant message when runner metadata is not available yet.

Fixes #66534
2026-06-06 04:54:11 -07:00
Vincent Koc
74331f632b test(live): tolerate ARM provider drift 2026-06-06 03:47:24 -07:00
joshavant
f4a5e5762e feat(android): brand onboarding welcome screen 2026-06-06 05:03:37 -05:00
joshavant
1098063783 fix(android): clarify nearby gateway discovery state 2026-06-06 05:03:37 -05:00
joshavant
b80893f30d chore(android): simplify onboarding entry actions 2026-06-06 05:03:37 -05:00
joshavant
72b387ad48 fix(android): show configured provider readiness 2026-06-06 05:03:37 -05:00
joshavant
44a72cde58 chore(android): remove provider setup footer 2026-06-06 05:03:37 -05:00
joshavant
81312e7aa3 chore(android): remove model catalog section 2026-06-06 05:03:37 -05:00
joshavant
53e50ec127 fix(android): reconnect saved gateway after disconnect 2026-06-06 05:03:37 -05:00
joshavant
485446af8c fix(android): keep sent chat messages in history 2026-06-06 05:03:37 -05:00
joshavant
81f4fe6c11 fix(android): pause gateway pairing retries 2026-06-06 05:03:37 -05:00
joshavant
a2455fcc09 fix(android): keep gateway pairing off main thread 2026-06-06 05:03:37 -05:00
joshavant
e4583b4f57 fix(android): show flavor channel in about 2026-06-06 05:03:37 -05:00
joshavant
9413a5aba5 fix(android): defer runtime startup after first draw 2026-06-06 05:03:36 -05:00
joshavant
b7cafb56fa fix(android): surface voice provider attention 2026-06-06 05:03:36 -05:00
joshavant
efea9ca0f5 chore(android): fix ktlint formatting 2026-06-06 05:03:36 -05:00
Vincent Koc
98f52dcc00 test(live): skip DeepInfra V4 Flash tool sentinel drift 2026-06-06 03:00:12 -07:00
joshavant
32b0b58868 style(ios): use app logo on onboarding intro 2026-06-06 04:41:33 -05:00
joshavant
9942428df0 fix(ios): disable chat composer while offline 2026-06-06 04:41:33 -05:00
joshavant
f40680c826 style(ios): align command section header padding 2026-06-06 04:41:33 -05:00
joshavant
a6582f787c fix(ios): remove extra root tab bottom insets 2026-06-06 04:41:33 -05:00
joshavant
a9a2c34293 fix(ios): stop marking scheduled agents busy 2026-06-06 04:41:33 -05:00
joshavant
2ef0d274fa fix(ios): hide agent sessions from recent sessions 2026-06-06 04:41:33 -05:00
joshavant
dc5c24fbe6 fix(ios): keep chat messages above composer 2026-06-06 04:41:33 -05:00
joshavant
0b87990328 fix(ios): remove command live activity section 2026-06-06 04:41:33 -05:00
joshavant
14f018e794 fix(ios): move approvals to settings 2026-06-06 04:41:33 -05:00
joshavant
81d099f0e9 fix(ios): remove command start work button 2026-06-06 04:41:33 -05:00
joshavant
e8c0d92015 fix(ios): clarify agent chat session 2026-06-06 04:41:32 -05:00
joshavant
67dc71983c fix(ios): show focused session agent 2026-06-06 04:41:32 -05:00
joshavant
be537060ce fix(ios): show recent sessions preview 2026-06-06 04:41:32 -05:00
joshavant
ea7e214bd4 Fix chat history races across agent switches 2026-06-06 04:41:32 -05:00
joshavant
7478e6e485 Fix chat session sync ownership 2026-06-06 04:41:32 -05:00
joshavant
83a6bce835 Fix iOS chat background presentation 2026-06-06 04:41:32 -05:00
joshavant
5c07f7ccf0 Fix iOS selected agent chat routing 2026-06-06 04:41:32 -05:00
joshavant
af50a5959d fix ios onboarding success screen 2026-06-06 04:41:32 -05:00
joshavant
472a30bd3f fix ios skill editor toggle hit target 2026-06-06 04:41:32 -05:00
joshavant
8f6f18b6e7 fix ios operator recovery live activity 2026-06-06 04:41:32 -05:00
joshavant
1746319db5 fix ios operator scope upgrade state 2026-06-06 04:41:32 -05:00
joshavant
19e827c969 fix ios operator admin scope requests 2026-06-06 04:41:32 -05:00
joshavant
f1cf898460 fix ios onboarding tls toggle hit targets 2026-06-06 04:41:32 -05:00
joshavant
7e6134cb12 fix ios onboarding developer toggle hit target 2026-06-06 04:41:32 -05:00
joshavant
2fb5ff3034 fix ios settings bottom scroll inset 2026-06-06 04:41:32 -05:00
joshavant
fbaa5a6f0a fix ios gateway settings control hit targets 2026-06-06 04:41:32 -05:00
joshavant
33cb1c18ac fix ios diagnostics toggle hit targets 2026-06-06 04:41:31 -05:00
joshavant
0ee7cf970c fix ios quick setup suppression toggle 2026-06-06 04:41:31 -05:00
joshavant
762540aa04 fix ios talk controls hit targets 2026-06-06 04:41:31 -05:00
joshavant
73f056a0a4 fix ios chat error banner overlap 2026-06-06 04:41:31 -05:00
joshavant
88f6857c2e fix ios onboarding mode row hit targets 2026-06-06 04:41:31 -05:00
joshavant
c29cc7f82f fix(ios): use safe area inset for settings scroll 2026-06-06 04:41:31 -05:00
Vincent Koc
d4b4a65809 fix(plugins): preserve core embedding providers 2026-06-06 00:30:48 -07:00
Vincent Koc
f94e4f85f0 test(pairing): isolate store state tests 2026-06-05 23:46:11 -07:00
Vincent Koc
c72c82726f fix(installer): print npm debug logs on Windows install failure 2026-06-05 23:16:39 -07:00
Vincent Koc
92242f4f68 fix(test): route extension tests through scoped paths 2026-06-05 22:59:54 -07:00
xydigit-sj
743051d400 fix(uninstall): refuse to remove current working directory during cleanup (#90813)
* fix(uninstall): refuse to remove current working directory during cleanup

* fix(uninstall): guard cleanup ancestors of cwd

---------

Co-authored-by: sallyom <somalley@redhat.com>
2026-06-06 01:51:16 -04:00
Vincent Koc
153a2badb0 fix(release): extend live Docker image pull timeout 2026-06-05 22:34:22 -07:00
Omar Shahine
37aaa5cc2b fix(imessage): frame rpc stdout on LF only (#90845)
Merged via squash.

Prepared head SHA: c62a2dcbf1
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-05 22:31:50 -07:00
Kevin Lin
ab7c922825 fix(codex): report completion timeout diagnostics
Surface Codex-specific completion-timeout outcomes and structural diagnostics while preserving the existing replay-safe retry behavior.\n\nVerified with focused Vitest coverage, live forced-timeout Showboat proof, and green PR CI.
2026-06-05 22:00:38 -07:00
Vincent Koc
2fc4511eeb fix(release): retry provider-throttled cross-os agent turns 2026-06-05 21:58:46 -07:00
Vincent Koc
9313471fa5 fix(plugins): strengthen registry root memo fingerprint 2026-06-05 21:23:55 -07:00
brokemac79
2f46a27b40 fix(codex): preserve completed replies after client close (#90790)
Merged via squash.

Prepared head SHA: d948b3543c
Co-authored-by: brokemac79 <255583030+brokemac79@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman
2026-06-05 21:22:10 -07:00
Vincent Koc
8e9c377971 test(gateway): stabilize channel restart fake timers 2026-06-05 21:16:41 -07:00
Glucksberg
6f909f6454 Fix OpenAI audio auth to use API keys (#90793)
* fix(media): require api key auth for OpenAI audio

* fix(media): narrow OpenAI audio auth API scope

* fix(media): align OpenAI audio auth selection

Signed-off-by: sallyom <somalley@redhat.com>

---------

Signed-off-by: sallyom <somalley@redhat.com>
Co-authored-by: sallyom <somalley@redhat.com>
2026-06-06 00:11:05 -04:00
Vincent Koc
092075534e docs(changelog): complete 2026.6.5 release refs 2026-06-05 20:36:28 -07:00
Vincent Koc
04ecc1aae9 docs(changelog): complete 2026.6.5 notes 2026-06-05 20:23:13 -07:00
Vincent Koc
af4ba6221b docs(changelog): refresh 2026.6.5 notes 2026-06-05 20:04:04 -07:00
clawsweeper[bot]
9cbf18293b fix #90668: [Bug]: macOS node mode can silently self-reconnect in a healthy direct gateway session (#90815)
Summary:
- Adds a macOS node-mode TLS session cache keyed by gateway URL and TLS pin parameters, with Swift tests for reuse and rebuild behavior.
- PR surface: Other +78. Total +78 across 2 files.
- Reproducibility: yes. The source path is clear: current main supplies a fresh TLS session identity into `Gat ... inked macOS WSS proof demonstrates repeated connected callbacks before the cache and one callback after it.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(macos): make TLS session cache lint-safe
- PR branch already contained follow-up commit before automerge: fix #90668: [Bug]: macOS node mode can silently self-reconnect in a h…

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

Prepared head SHA: 1496eac8c1
Review: https://github.com/openclaw/openclaw/pull/90815#issuecomment-4637057530

Co-authored-by: 张贵萍0668001030 <zhang.guiping@xydigit.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: takhoffman
Co-authored-by: takhoffman <781889+takhoffman@users.noreply.github.com>
2026-06-06 02:44:59 +00:00
clawsweeper[bot]
50aaf1f9b6 fix(memory): resolve adapter default model in plain status identity check (#90816)
Summary:
- This PR updates memory-core index identity resolution to treat an empty configured model as the embedding adapter default and adds a regression test for plain memory status.
- PR surface: Source +5, Tests +33. Total +38 across 2 files.
- Reproducibility: yes. from source and inherited proof: current main compares identity against an unresolved empty model in the plain status path, and the source PR shows the before/after CLI behavior on the same index.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(memory): resolve adapter default model in plain status identity c…

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

Prepared head SHA: 9741437564
Review: https://github.com/openclaw/openclaw/pull/90816#issuecomment-4637058847

Co-authored-by: 宇宙熊Yzx <53250620+849261680@users.noreply.github.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: takhoffman
Co-authored-by: takhoffman <781889+takhoffman@users.noreply.github.com>
2026-06-06 02:34:29 +00:00
Chunyue Wang
aa8070a76f fix(llm): defer Anthropic stream start event until after message_start (#90697)
Summary:
- The branch moves Anthropic `start` emission into `message_start` handling for the provider and transport stream paths and adds focused ordering/error tests.
- PR surface: Source +5, Tests +149. Total +154 across 4 files.
- Reproducibility: Do we have a high-confidence way to reproduce the issue? Yes from source: current main emit ... ecovery intentionally refuses to retry after any non-error output; no live expired-cache run was performed.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(agents): defer Anthropic transport stream start event until after…

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

Prepared head SHA: 399a243c64
Review: https://github.com/openclaw/openclaw/pull/90697#issuecomment-4632866448

Co-authored-by: openperf <16864032@qq.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: takhoffman
Co-authored-by: takhoffman <781889+takhoffman@users.noreply.github.com>
2026-06-06 02:17:54 +00:00
Yzx
b1e4b6b65e fix(agents): coerce non-text/image MCP tool-result blocks to text (fixes #90710) (#90728)
Summary:
- The PR converts wider MCP CallToolResult content blocks into text/image AgentToolResult blocks at the bundle-MCP materialization boundary and adds regression tests.
- PR surface: Source +36, Tests +66. Total +102 across 2 files.
- Reproducibility: yes. Source inspection shows current main lets MCP resource/audio blocks cross into a text/ ...  a spawned stdio MCP server; I did not run a live hosted Anthropic API round trip in this read-only review.

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

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

Prepared head SHA: f70dccf33e
Review: https://github.com/openclaw/openclaw/pull/90728#issuecomment-4634126025

Co-authored-by: 宇宙熊Yzx <53250620+849261680@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: takhoffman
Co-authored-by: takhoffman <781889+takhoffman@users.noreply.github.com>
2026-06-06 02:16:10 +00:00
Sahibzada
9e29375cec fix(voice-call): track Twilio streams after connect (#90607)
Summary:
- The PR moves Twilio inbound active-stream tracking from TwiML generation to `registerCallStream` and updates provider tests for connected-stream and no-stream cases.
- PR surface: Source -3, Tests +23. Total +20 across 2 files.
- Reproducibility: yes. from source inspection and supplied before/after output: on current main, one inbound  ... nd inbound parse queues even when no media stream registered. I did not run tests in this read-only review.

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

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

Prepared head SHA: 22575a9f27
Review: https://github.com/openclaw/openclaw/pull/90607#issuecomment-4630012870

Co-authored-by: Sahibzada Allahyar <sahibzada@fastino.ai>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: takhoffman
Co-authored-by: takhoffman <781889+takhoffman@users.noreply.github.com>
2026-06-06 02:15:00 +00:00
keshavbotagent
3a2f54e6a8 fix(telegram): suppress post-final tool error noise
Suppress non-actionable text-only tool/progress noise after Telegram final delivery while preserving terminal final warnings, media payloads, and exec approval prompts.

Use the core nonTerminalToolErrorWarning marker for recovered final tool warnings, and cover suppression plus preservation cases with regression tests.
2026-06-05 18:24:09 -07:00
Harjoth Khara
e5d1fadea7 test(codex): cover thread abandonment after completion-idle timeout (#90027)
Regression coverage for #89974. Confirms that after a
turn_completion_idle_timeout, OpenClaw clears the timed-out Codex
app-server thread binding and the next turn starts a fresh thread instead
of resuming the thread that may hold Codex's generic <turn_aborted> /
user-interrupted marker. No runtime behavior changes.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 17:41:52 -07:00
Josh Lehman
bbfe8ccaf6 fix: refresh prompt fence after compaction writes
Fix embedded attempts falsely reporting session takeover after OpenClaw-owned auto-compaction writes a compaction entry while the prompt fence is released.

The compaction append path now publishes an owned session-file fence only when the guarded SessionManager append produced the expected compaction entry. External or interleaved session-file edits remain takeover errors.

Closes #90729
2026-06-05 17:05:35 -07:00
Yzx
a4f7e4cbb9 fix(google): preserve Vertex ADC catalog auth (#90609)
* fix: preserve Google Vertex ADC catalog auth

* fix: register Google Vertex ADC config marker

* fix: fill Vertex ADC static catalog auth
2026-06-05 18:16:34 -04:00
Yzx
6da3b1f6a3 fix(agents): re-probe single-provider primary during cooldown (#90717)
Fixes #90702.

Allow a single-provider primary to periodically probe through the existing cooldown throttle even when no fallback chain is configured. This lets WHAM/subscription-limit cooldown state recover without waiting for a far-future provider reset timestamp.

Verified:
- node scripts/run-vitest.mjs src/agents/model-fallback.probe.test.ts
- git diff --check
- cherry-pick onto current origin/main and rerun focused regression
2026-06-05 14:20:57 -07:00
dependabot[bot]
2ab4eaa2b1 build(deps): bump docker/login-action from 3.6.0 to 4.1.0 (#74980)
Bumps [docker/login-action](https://github.com/docker/login-action) from 3.6.0 to 4.1.0.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v3.6.0...4907a6ddec9925e35a0a9e82d7399ccc52663121)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-version: 4.1.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-05 14:12:53 -07:00
dependabot[bot]
c965141d67 chore(deps): bump the android-deps group across 1 directory with 9 updates (#86481)
Bumps the android-deps group with 9 updates in the /apps/android directory:

| Package | From | To |
| --- | --- | --- |
| [gradle-wrapper](https://github.com/gradle/gradle) | `9.4.1` | `9.5.1` |
| androidx.compose:compose-bom | `2026.04.01` | `2026.05.01` |
| [dnsjava:dnsjava](https://github.com/dnsjava/dnsjava) | `3.6.4` | `3.6.5` |
| [org.junit.vintage:junit-vintage-engine](https://github.com/junit-team/junit-framework) | `6.0.3` | `6.1.0` |
| [org.jetbrains.kotlinx:kotlinx-coroutines-android](https://github.com/Kotlin/kotlinx.coroutines) | `1.10.2` | `1.11.0` |
| [org.jetbrains.kotlinx:kotlinx-coroutines-test](https://github.com/Kotlin/kotlinx.coroutines) | `1.10.2` | `1.11.0` |
| [com.google.android.material:material](https://github.com/material-components/material-components-android) | `1.13.0` | `1.14.0` |
| com.android.application | `9.2.0` | `9.2.1` |
| com.android.test | `9.2.0` | `9.2.1` |



Updates `gradle-wrapper` from 9.4.1 to 9.5.1
- [Release notes](https://github.com/gradle/gradle/releases)
- [Commits](https://github.com/gradle/gradle/compare/v9.4.1...v9.5.1)

Updates `androidx.compose:compose-bom` from 2026.04.01 to 2026.05.01

Updates `dnsjava:dnsjava` from 3.6.4 to 3.6.5
- [Release notes](https://github.com/dnsjava/dnsjava/releases)
- [Changelog](https://github.com/dnsjava/dnsjava/blob/master/Changelog)
- [Commits](https://github.com/dnsjava/dnsjava/commits)

Updates `org.junit.vintage:junit-vintage-engine` from 6.0.3 to 6.1.0
- [Release notes](https://github.com/junit-team/junit-framework/releases)
- [Commits](https://github.com/junit-team/junit-framework/compare/r6.0.3...r6.1.0)

Updates `org.jetbrains.kotlinx:kotlinx-coroutines-android` from 1.10.2 to 1.11.0
- [Release notes](https://github.com/Kotlin/kotlinx.coroutines/releases)
- [Changelog](https://github.com/Kotlin/kotlinx.coroutines/blob/master/CHANGES.md)
- [Commits](https://github.com/Kotlin/kotlinx.coroutines/compare/1.10.2...1.11.0)

Updates `org.jetbrains.kotlinx:kotlinx-coroutines-test` from 1.10.2 to 1.11.0
- [Release notes](https://github.com/Kotlin/kotlinx.coroutines/releases)
- [Changelog](https://github.com/Kotlin/kotlinx.coroutines/blob/master/CHANGES.md)
- [Commits](https://github.com/Kotlin/kotlinx.coroutines/compare/1.10.2...1.11.0)

Updates `org.jetbrains.kotlinx:kotlinx-coroutines-test` from 1.10.2 to 1.11.0
- [Release notes](https://github.com/Kotlin/kotlinx.coroutines/releases)
- [Changelog](https://github.com/Kotlin/kotlinx.coroutines/blob/master/CHANGES.md)
- [Commits](https://github.com/Kotlin/kotlinx.coroutines/compare/1.10.2...1.11.0)

Updates `com.google.android.material:material` from 1.13.0 to 1.14.0
- [Release notes](https://github.com/material-components/material-components-android/releases)
- [Commits](https://github.com/material-components/material-components-android/compare/1.13.0...1.14.0)

Updates `com.android.application` from 9.2.0 to 9.2.1

Updates `com.android.test` from 9.2.0 to 9.2.1

Updates `com.android.test` from 9.2.0 to 9.2.1

---
updated-dependencies:
- dependency-name: androidx.compose:compose-bom
  dependency-version: 2026.05.01
  dependency-type: direct:production
  dependency-group: android-deps
- dependency-name: com.android.application
  dependency-version: 9.2.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: android-deps
- dependency-name: com.android.test
  dependency-version: 9.2.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: android-deps
- dependency-name: com.android.test
  dependency-version: 9.2.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: android-deps
- dependency-name: com.google.android.material:material
  dependency-version: 1.14.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: android-deps
- dependency-name: dnsjava:dnsjava
  dependency-version: 3.6.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: android-deps
- dependency-name: gradle-wrapper
  dependency-version: 9.5.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: android-deps
- dependency-name: org.jetbrains.kotlinx:kotlinx-coroutines-android
  dependency-version: 1.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: android-deps
- dependency-name: org.jetbrains.kotlinx:kotlinx-coroutines-test
  dependency-version: 1.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: android-deps
- dependency-name: org.jetbrains.kotlinx:kotlinx-coroutines-test
  dependency-version: 1.11.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: android-deps
- dependency-name: org.junit.vintage:junit-vintage-engine
  dependency-version: 6.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: android-deps
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-05 14:07:08 -07:00
dependabot[bot]
c6972a0664 chore(deps): bump github.com/apple/swift-testing (#81757)
Bumps the swift-deps group with 1 update in the /apps/swabble directory: [github.com/apple/swift-testing](https://github.com/apple/swift-testing).


Updates `github.com/apple/swift-testing` from 6.3.1 to 6.3.2
- [Release notes](https://github.com/apple/swift-testing/releases)
- [Commits](https://github.com/apple/swift-testing/compare/6.3.1...6.3.2)

---
updated-dependencies:
- dependency-name: github.com/apple/swift-testing
  dependency-version: 6.3.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swift-deps
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-05 14:05:15 -07:00
dependabot[bot]
662d366f01 chore(deps): bump the actions group across 1 directory with 4 updates (#90601)
Bumps the actions group with 4 updates in the / directory: [github/codeql-action](https://github.com/github/codeql-action), [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action), [docker/build-push-action](https://github.com/docker/build-push-action) and [openai/codex-action](https://github.com/openai/codex-action).


Updates `github/codeql-action` from 4 to 4.36.1
- [Release notes](https://github.com/github/codeql-action/releases)
- [Commits](https://github.com/github/codeql-action/compare/v4...v4.36.1)

Updates `docker/setup-buildx-action` from 4.0.0 to 4.1.0
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](4d04d5d948...d7f5e7f509)

Updates `docker/build-push-action` from 7.1.0 to 7.2.0
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](bcafcacb16...f9f3042f7e)

Updates `openai/codex-action` from 1.7 to 1.8
- [Changelog](https://github.com/openai/codex-action/blob/main/CHANGELOG.md)
- [Commits](5c3f4ccdb2...e0fdf01220)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 4.36.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
- dependency-name: docker/setup-buildx-action
  dependency-version: 4.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
- dependency-name: docker/build-push-action
  dependency-version: 7.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
- dependency-name: openai/codex-action
  dependency-version: '1.8'
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-05 13:37:40 -07:00
dependabot[bot]
aee45f5f73 chore(deps): bump the swift-deps group across 1 directory with 3 updates (#86483)
Bumps the swift-deps group with 3 updates in the /apps/macos directory: [github.com/apple/swift-log](https://github.com/apple/swift-log), [github.com/sparkle-project/sparkle](https://github.com/sparkle-project/Sparkle) and [github.com/steipete/peekaboo](https://github.com/steipete/Peekaboo).


Updates `github.com/apple/swift-log` from 1.12.0 to 1.13.1
- [Release notes](https://github.com/apple/swift-log/releases)
- [Commits](https://github.com/apple/swift-log/compare/1.12.0...1.13.1)

Updates `github.com/sparkle-project/sparkle` from 2.9.1 to 2.9.2
- [Release notes](https://github.com/sparkle-project/Sparkle/releases)
- [Commits](https://github.com/sparkle-project/Sparkle/compare/2.9.1...2.9.2)

Updates `github.com/steipete/peekaboo` from 3.2.1 to 3.3.0
- [Release notes](https://github.com/steipete/Peekaboo/releases)
- [Commits](https://github.com/steipete/Peekaboo/compare/v3.2.1...v3.3.0)

---
updated-dependencies:
- dependency-name: github.com/apple/swift-log
  dependency-version: 1.12.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swift-deps
- dependency-name: github.com/sparkle-project/sparkle
  dependency-version: 2.9.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swift-deps
- dependency-name: github.com/steipete/peekaboo
  dependency-version: 3.2.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: swift-deps
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-05 13:10:45 -07:00
Dallin Romney
ac9a219692 fix(tui): stabilize optimistic user messages across history reloads, runId reassignment, and abort (#86205)
* fix(tui): preserve optimistic user messages

* refactor(tui): drop unused pending-user chat-log helpers

* fix(tui): reconcile optimistic user row across runId reassignment and abort

* refactor(tui): reuse asDateTimestampMs for history timestamp coercion

* test(tui): fix event-handler chatLog render mock arity
2026-06-05 12:09:24 -07:00
Matt H
db7d70ae4d feat(parallel): add Parallel as a bundled web_search provider (#85158)
- New extensions/parallel package modeled on extensions/exa
- Wires Parallel's POST /v1/search through the generic web_search contract,
  exposing Parallel's recommended {objective, search_queries} shape (plus
  optional count, session_id, client_model) so the model can supply both the
  natural-language goal and 2-3 short keyword queries as Parallel docs advise
- client_model lets the model report its own slug so Parallel can tailor
  optimizations for the consuming model's capabilities; partitions the cache
  by client_model so different models do not silently share ranked excerpts
- Honors top-level tools.web.search.{maxResults,timeoutSeconds,cacheTtlMinutes}
  via the shared SDK helpers (mergeScopedSearchConfig, withTrustedWebSearchEndpoint,
  buildSearchCacheKey, read/writeCachedSearchPayload)
- Auto-detect order 75; auth via PARALLEL_API_KEY or
  plugins.entries.parallel.config.webSearch.apiKey
- Optional baseUrl override for proxies (e.g. Cloudflare AI Gateway)
- Threads caller-supplied session_id through follow-up calls; strips
  auto-generated session_id from the shared cache to avoid cross-task leaks
- Always sends advanced_settings.max_results so result volume matches the
  OpenClaw web_search default (5) instead of Parallel's default (10)
- Identifies the plugin via User-Agent header built from package version
- Runtime accepts the generic `query` arg as a fallback so the operator
  CLI (openclaw capability web.search) keeps working when Parallel is the
  active provider: it is promoted into the lone `search_queries` entry.
  `objective` stays optional and is never synthesized from a keyword
  query (Parallel documents it as natural-language intent). Agent callers
  using the native objective+search_queries shape take precedence; the
  schema still advertises only the native keys
- Updates the agent tool-display extractor (src/agents/tool-display-common.ts)
  to recognize Parallel's objective+search_queries shape so calls render with
  query context in CLI progress and Codex activity metadata
- Adds /tools/parallel-search docs page, web.md provider listing, docs nav,
  labeler entry, per-plugin registration contract test, and minimal core
  touch-points (legacy migrate, registration cases, providers contract list,
  runtime bundled list, vitest extension paths)
2026-06-05 12:01:58 -07:00
Jason (Json)
36d9241cf7 docs: prefer web_fetch in weather skill (#90250)
* docs: prefer web_fetch in weather skill

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

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

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

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

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

* test(matrix): satisfy ci guards

* fix(matrix): preserve thread relations on edits

* chore: annotate deprecated compatibility aliases

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

* test(matrix): enable audio preflight qa config

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

---------

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

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

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

Supersedes #89500.

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

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

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

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

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

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

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

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

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

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

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

## Linked context

Which issue does this close?

Closes #

Which issues, PRs, or discussions are related?

Related Spec 24.8: Google Chat native approval cards.

Was this requested by a maintainer or owner?

Requested by maintainer in the Codex task thread.

## Real behavior proof (required for external PRs)

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

## Tests and validation

Which commands did you run?

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

What regression coverage was added or updated?

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

What failed before this fix, if known?

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

If no test was added, why not?

Tests were added for the changed runtime and webhook behavior.

## Risk checklist

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

Yes.

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

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

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

Yes.

What is the highest-risk area?

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

How is that risk mitigated?

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

## Current review state

What is the next action?

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

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

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

Which bot or reviewer comments were addressed?

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

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

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

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

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

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

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

* fix: satisfy ClawHub install type checks

* fix: harden github-backed skill installs

* fix: keep heartbeat template non-actionable

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

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

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

* test: add coverage for waitForWaConnection timeout path

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

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

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

* fix: bound whatsapp connection startup waits

* fix: align web channel wait contract

* fix: retry whatsapp setup timeouts

* fix: satisfy whatsapp status lint

* fix: preserve whatsapp wait compatibility

---------

Co-authored-by: MMMMSSSS8899 <praelovk@gmail.com>
2026-06-04 21:45:43 -03:00
Peter Steinberger
29f5e9d35c docs: document test helpers 2026-06-04 20:42:26 -04:00
Peter Steinberger
25211167e8 docs: document vitest config files 2026-06-04 20:40:11 -04:00
Peter Steinberger
ecb6779a16 docs: document root test files 2026-06-04 20:37:28 -04:00
Peter Steinberger
edb920b857 docs: document remaining src helpers 2026-06-04 20:34:26 -04:00
Peter Steinberger
b2e320dfb1 docs: document support test files 2026-06-04 20:31:55 -04:00
Peter Steinberger
1bdf210b43 docs: document rescue and trajectory tests 2026-06-04 20:29:59 -04:00
Peter Steinberger
d8326f13c3 docs: document proxy and mcp helpers 2026-06-04 20:27:46 -04:00
Peter Steinberger
9b30ff181c docs: document routing helpers 2026-06-04 20:25:55 -04:00
Peter Steinberger
4f79f2419c docs: document video generation helpers 2026-06-04 20:24:49 -04:00
Peter Steinberger
65546f0158 docs: document tui components 2026-06-04 20:23:23 -04:00
Peter Steinberger
6d58ff3562 docs: document session helpers 2026-06-04 20:22:09 -04:00
Peter Steinberger
47bae66415 docs: document session config tests 2026-06-04 20:20:20 -04:00
Peter Steinberger
f5b6a977d7 docs: document tts helpers 2026-06-04 20:19:15 -04:00
Peter Steinberger
85e6940202 docs: document talk helpers 2026-06-04 20:17:42 -04:00
Peter Steinberger
5ba4eeceac docs: document daemon tests 2026-06-04 20:16:21 -04:00
Peter Steinberger
a628a66e4d docs: document process helpers 2026-06-04 20:14:34 -04:00
Peter Steinberger
ef08c83e17 docs: document utility helpers 2026-06-04 20:12:49 -04:00
Peter Steinberger
b6ce59d367 docs: document wizard helpers 2026-06-04 20:11:22 -04:00
Peter Steinberger
c8665c66ba docs: document flow helpers 2026-06-04 20:10:01 -04:00
Peter Steinberger
4c3b4f8ad8 docs: document hook helpers 2026-06-04 20:08:40 -04:00
Peter Steinberger
e6f85453dc docs: document llm helpers 2026-06-04 20:07:13 -04:00
Peter Steinberger
f1bdc91b64 docs: document media helpers 2026-06-04 20:05:16 -04:00
Peter Steinberger
add135d238 docs: document logging helpers 2026-06-04 20:04:06 -04:00
Vincent Koc
563dac5989 test(core): remove stale unused test bindings 2026-06-04 17:03:40 -07:00
Vincent Koc
5bc300a1df test(agents): align pdf default model expectation 2026-06-04 17:03:40 -07:00
Vincent Koc
1d19d7ec46 fix(auto-reply): skip commented heartbeat scaffolding 2026-06-04 17:03:40 -07:00
Peter Steinberger
87d053c0cb docs: document shared helpers 2026-06-04 20:02:33 -04:00
Peter Steinberger
5b53cddc75 docs: document cron test files 2026-06-04 20:01:05 -04:00
Peter Steinberger
6c48a12562 docs: document skill runtime files 2026-06-04 19:58:44 -04:00
Peter Steinberger
43cee29f70 docs: document skill loading files 2026-06-04 19:57:11 -04:00
Peter Steinberger
725ddd11cc docs: document remaining plugin runtime files 2026-06-04 19:54:07 -04:00
Peter Steinberger
d2d14d5793 docs: document plugin contract tests 2026-06-04 19:52:26 -04:00
Peter Steinberger
f25c246f6b docs: document plugin runtime helpers 2026-06-04 19:48:26 -04:00
Peter Steinberger
6486fc1c0d docs: document model command tests 2026-06-04 19:46:17 -04:00
Peter Steinberger
81eee47045 docs: document doctor command tests 2026-06-04 19:44:23 -04:00
Peter Steinberger
4499b24781 docs: document cli program tests 2026-06-04 19:41:55 -04:00
Peter Steinberger
b59b34f9d5 docs: document cli service tests 2026-06-04 19:39:51 -04:00
Shakker
912e70acbd test: scope system run helper env 2026-06-05 00:38:28 +01:00
Shakker
16147e16e3 test: isolate approval path token cases 2026-06-05 00:38:28 +01:00
Shakker
638be00f4b test: scope fake runtime path setup 2026-06-05 00:38:28 +01:00
Shakker
695e09d360 test: scope proof temp dir env 2026-06-05 00:38:28 +01:00
Shakker
69ddcc00e6 test: scope invoke prepare path env 2026-06-05 00:38:28 +01:00
Shakker
a18c60e141 test: scope sandbox audit home env 2026-06-05 00:38:28 +01:00
Shakker
ec048ae693 test: restore cron state dir through helper 2026-06-05 00:38:28 +01:00
Shakker
7675b10223 test: capture usage format env setup 2026-06-05 00:38:28 +01:00
Shakker
25a1b0c240 test: cover allowlist tilde expansion 2026-06-05 00:38:28 +01:00
Shakker
c006ed5e16 test: restore reply harness temp home 2026-06-05 00:38:27 +01:00
Shakker
b4e048e60a test: reset gateway token env per case 2026-06-05 00:38:27 +01:00
Shakker
f365568f1b test: shorten tool metadata home paths 2026-06-05 00:38:27 +01:00
Shakker
e2c23d8a5e test: verify model status agent-dir env 2026-06-05 00:38:27 +01:00
Peter Steinberger
408ba4c8a0 docs: document remaining cli tests 2026-06-04 19:37:38 -04:00
Peter Steinberger
4995907541 docs: document cli support tests 2026-06-04 19:35:08 -04:00
Peter Steinberger
8cb093e7a9 docs: document cli test batch 2026-06-04 19:32:43 -04:00
Peter Steinberger
3e29885c83 docs: document channel subdir tests 2026-06-04 19:30:09 -04:00
Peter Steinberger
867d7898df docs: document channel plugin contracts 2026-06-04 19:28:31 -04:00
Peter Steinberger
fa46138047 docs: document channel plugin tests 2026-06-04 19:24:55 -04:00
Peter Steinberger
c135624c69 docs: document root channel tests 2026-06-04 19:23:04 -04:00
Peter Steinberger
048f307695 docs: document remaining plugin sdk files 2026-06-04 19:21:04 -04:00
Peter Steinberger
feffb6d02f docs: document plugin sdk runtime helpers 2026-06-04 19:16:24 -04:00
Peter Steinberger
a16c6ca94b docs: document plugin sdk public helpers 2026-06-04 19:14:41 -04:00
Vincent Koc
7fb748462e fix(ci): classify live installer docker lanes 2026-06-04 16:13:27 -07:00
Peter Steinberger
50dcaad71a docs: document remaining command tests 2026-06-04 19:11:43 -04:00
Peter Steinberger
7a7ca15776 docs: document command setup batch 2026-06-04 19:07:41 -04:00
Peter Steinberger
bf19d198d9 docs: document command cleanup batch 2026-06-04 19:05:16 -04:00
Peter Steinberger
eaad487c42 docs: document command report batch 2026-06-04 19:03:19 -04:00
Peter Steinberger
12ade5c5e8 docs: document command scan batch 2026-06-04 19:01:12 -04:00
Peter Steinberger
076bf2a361 docs: document command status batch 2026-06-04 18:59:04 -04:00
Peter Steinberger
0156de5c34 docs: document command onboarding batch 2026-06-04 18:57:02 -04:00
Shakker
646eb00112 test: pin acp prompt home redaction 2026-06-04 23:55:53 +01:00
Shakker
06f95f9a65 test: anchor auth sqlite agent dirs 2026-06-04 23:55:53 +01:00
Shakker
9a78886c78 test: localize logger env overrides 2026-06-04 23:55:53 +01:00
Shakker
66212260ef test: seal crestodian rescue stores 2026-06-04 23:55:53 +01:00
Shakker
dda0a98b76 test: bracket agent directory fixtures 2026-06-04 23:55:53 +01:00
Shakker
c71d3e45a1 test: isolate tts status fixture homes 2026-06-04 23:55:53 +01:00
Shakker
986025afe4 test: guard fs-safe tilde fixtures 2026-06-04 23:55:53 +01:00
Peter Steinberger
0d393ba6b4 docs: document command diagnostics batch 2026-06-04 18:54:31 -04:00
Peter Steinberger
0de924b35c docs: document command support batch 2026-06-04 18:52:07 -04:00
Peter Steinberger
4a47a9db98 docs: document command test batch 2026-06-04 18:50:37 -04:00
Peter Steinberger
5fa55d93f7 docs: document command helper tests 2026-06-04 18:48:22 -04:00
Peter Steinberger
64008398d1 docs: document noninteractive onboarding tests 2026-06-04 18:47:21 -04:00
Peter Steinberger
5c362884f3 docs: document channel setup tests 2026-06-04 18:46:23 -04:00
Peter Steinberger
71b09b99f8 docs: document migrate command tests 2026-06-04 18:45:28 -04:00
Peter Steinberger
3f31b62cd4 docs: document channels capability tests 2026-06-04 18:44:22 -04:00
Peter Steinberger
4927388580 docs: document gateway status tests 2026-06-04 18:43:35 -04:00
Peter Steinberger
19da9d8832 docs: document status-all command tests 2026-06-04 18:42:37 -04:00
Peter Steinberger
bea27678b4 docs: document link understanding 2026-06-04 18:41:10 -04:00
Peter Steinberger
ba28f7b018 docs: document model catalog planners 2026-06-04 18:40:10 -04:00
Peter Steinberger
571179c80b docs: document gateway live tests 2026-06-04 18:38:44 -04:00
Peter Steinberger
f0e5fdc064 docs: document gateway connection helpers 2026-06-04 18:36:52 -04:00
Peter Steinberger
f24ae91842 docs: document gateway server runtime 2026-06-04 18:35:26 -04:00
Peter Steinberger
ec22756340 docs: document gateway method descriptors 2026-06-04 18:34:05 -04:00
Peter Steinberger
e1b5fd2716 docs: document gateway test helpers 2026-06-04 18:33:25 -04:00
Peter Steinberger
437a5a71ae docs: document gateway runtime helpers 2026-06-04 18:30:41 -04:00
Peter Steinberger
a8154f425c docs: document gateway tool methods 2026-06-04 18:28:04 -04:00
Peter Steinberger
a6ecc4bd89 docs: document gateway session methods 2026-06-04 18:25:03 -04:00
Peter Steinberger
8c94131c0d docs: document gateway send methods 2026-06-04 18:22:31 -04:00
Peter Steinberger
e825301393 docs: document gateway node methods 2026-06-04 18:21:03 -04:00
Peter Steinberger
a84910be91 docs: document gateway diagnostics methods 2026-06-04 18:18:51 -04:00
Peter Steinberger
390a35d441 docs: document gateway chat methods 2026-06-04 18:17:28 -04:00
Peter Steinberger
f4c448f65b docs: document gateway agents methods 2026-06-04 18:15:10 -04:00
Peter Steinberger
c5d6764f56 docs: document gateway agent methods 2026-06-04 18:13:20 -04:00
Peter Steinberger
7f6af117f2 docs: document gateway tool test suites 2026-06-04 18:11:47 -04:00
Peter Steinberger
f0cb1a93e5 docs: document gateway session test suites 2026-06-04 18:10:05 -04:00
Peter Steinberger
d89d352971 docs: document gateway auth test suites 2026-06-04 18:08:07 -04:00
Shakker
3f6268ebd7 test: scope utility home env 2026-06-04 23:06:33 +01:00
Shakker
f3128f92d0 test: scope qqbot path env 2026-06-04 23:06:33 +01:00
Shakker
ec56a94ba3 test: scope sandbox env checks 2026-06-04 23:06:33 +01:00
Shakker
f0256be48d test: fence oauth manager env 2026-06-04 23:06:33 +01:00
Shakker
9de73ab6d2 test: scope agent auth copy state 2026-06-04 23:06:33 +01:00
Shakker
6bb91b2971 test: fence embedding provider secrets env 2026-06-04 23:06:33 +01:00
Shakker
840eaf9c19 test: centralize task flow state env 2026-06-04 23:06:33 +01:00
Shakker
2f6d4b811c test: isolate cleanup plan env 2026-06-04 23:06:33 +01:00
Shakker
509fa621de test: contain completion cache env 2026-06-04 23:06:33 +01:00
Peter Steinberger
6a95c8724a docs: document gateway chat test suites 2026-06-04 18:05:57 -04:00
Peter Steinberger
82de5903d7 docs: document gateway server test suites 2026-06-04 18:04:23 -04:00
Peter Steinberger
64b288be64 docs: document gateway probe test suites 2026-06-04 18:02:27 -04:00
Peter Steinberger
768143af06 docs: document gateway http test suites 2026-06-04 18:01:05 -04:00
Peter Steinberger
1e311058bc docs: document gateway config test suites 2026-06-04 17:58:55 -04:00
Peter Steinberger
7d216c2945 docs: document gateway client test suites 2026-06-04 17:57:26 -04:00
Peter Steinberger
fd8c789d42 docs: document gateway root test suites 2026-06-04 17:56:00 -04:00
Vincent Koc
deb9f11897 test(docker): harden live acp bind probes 2026-06-04 14:55:05 -07:00
Peter Steinberger
41d5e685ef docs: document gateway probe test helpers 2026-06-04 17:52:50 -04:00
Peter Steinberger
ca72d2706e docs: document gateway live runtime helpers 2026-06-04 17:50:37 -04:00
Shakker
81d9c2f41f test: scope session history state 2026-06-04 22:49:01 +01:00
Shakker
a0a115d466 test: wrap managed image attachment env 2026-06-04 22:49:01 +01:00
Shakker
501adb2524 test: isolate command secret env values 2026-06-04 22:49:01 +01:00
Shakker
1a4732410a test: scope host hook contract state 2026-06-04 22:49:01 +01:00
Peter Steinberger
8779bc49e0 docs: document gateway test helper fixtures 2026-06-04 17:47:12 -04:00
Peter Steinberger
15afc1d34c docs: document gateway auth ui runtime helpers 2026-06-04 17:45:49 -04:00
Peter Steinberger
8b4d12e161 docs: document gateway node startup helpers 2026-06-04 17:43:37 -04:00
Peter Steinberger
4c5b423fb8 docs: document gateway history runtime helpers 2026-06-04 17:41:47 -04:00
Peter Steinberger
bd76296c21 docs: document gateway runtime helpers 2026-06-04 17:38:20 -04:00
Peter Steinberger
360b2c9699 docs: document gateway session utility helpers 2026-06-04 17:36:25 -04:00
Peter Steinberger
aa9cc80060 docs: document gateway shutdown session helpers 2026-06-04 17:34:36 -04:00
Peter Steinberger
861bf541c2 docs: document gateway client auth helpers 2026-06-04 17:32:38 -04:00
Peter Steinberger
c8ac4c8aea docs: document gateway reload helpers 2026-06-04 17:30:08 -04:00
Peter Steinberger
8e371cfea1 docs: document gateway channel helpers 2026-06-04 17:27:42 -04:00
Peter Steinberger
dc23e924ef docs: document gateway runtime startup 2026-06-04 17:26:02 -04:00
Peter Steinberger
a3f495eb09 docs: document gateway node helpers 2026-06-04 17:23:48 -04:00
Peter Steinberger
1e438739bc docs: document gateway mcp helpers 2026-06-04 17:22:27 -04:00
Peter Steinberger
4d8502804d docs: document gateway state helpers 2026-06-04 17:20:55 -04:00
Peter Steinberger
d72184d3e0 docs: document gateway auth helpers 2026-06-04 17:18:36 -04:00
Peter Steinberger
7e0ee6d5c8 docs: document gateway utility policies 2026-06-04 17:16:36 -04:00
Peter Steinberger
2da49ef4ac docs: document gateway node policies 2026-06-04 17:14:38 -04:00
Peter Steinberger
fba99cddc1 docs: document gateway session utilities 2026-06-04 17:13:14 -04:00
Peter Steinberger
d76301e0ab docs: document gateway http helpers 2026-06-04 17:10:56 -04:00
Peter Steinberger
043929e76d docs: document gateway auth helpers 2026-06-04 17:09:08 -04:00
Peter Steinberger
c9c8125941 docs: document time formatting helpers 2026-06-04 17:06:59 -04:00
Peter Steinberger
b4a63886af docs: document outbound action runner 2026-06-04 17:06:05 -04:00
Peter Steinberger
5a6eddf5d0 docs: document outbound delivery queue 2026-06-04 17:04:07 -04:00
Peter Steinberger
ba72fb5b43 docs: document outbound message policy 2026-06-04 17:02:49 -04:00
Peter Steinberger
fc1848a28b docs: document outbound channel resolution 2026-06-04 17:01:45 -04:00
Peter Steinberger
aa12e7cda9 docs: document outbound action helpers 2026-06-04 17:00:31 -04:00
Peter Steinberger
9093556647 docs: document outbound session bindings 2026-06-04 16:59:17 -04:00
Peter Steinberger
ffc6bc0be0 docs: document outbound target helpers 2026-06-04 16:58:07 -04:00
Peter Steinberger
1f52854c0d docs: document command analysis infra 2026-06-04 16:56:05 -04:00
Peter Steinberger
170df6612e docs: document managed proxy net helpers 2026-06-04 16:54:14 -04:00
Peter Steinberger
eb6be3cf62 docs: document proxy network helpers 2026-06-04 16:52:20 -04:00
Peter Steinberger
53aa5232bc docs: document guarded fetch net helpers 2026-06-04 16:50:29 -04:00
Peter Steinberger
ba82257e37 docs: document media runner scenario tests 2026-06-04 16:48:42 -04:00
Peter Steinberger
5f7095f8be docs: document media runner core 2026-06-04 16:47:26 -04:00
Peter Steinberger
d6e4c879e8 docs: document media audio helpers 2026-06-04 16:46:02 -04:00
Peter Steinberger
ef6f4c1544 docs: document media attachment runtime 2026-06-04 16:45:01 -04:00
Peter Steinberger
9b42f399a1 docs: document media understanding defaults 2026-06-04 16:43:48 -04:00
Peter Steinberger
347ed87a96 docs: document agent instruction files 2026-06-04 16:42:36 -04:00
Peter Steinberger
3d168074b4 docs: document sandbox ssh tests 2026-06-04 16:41:43 -04:00
Peter Steinberger
cc296f3a46 docs: document subagent tests 2026-06-04 16:40:43 -04:00
Peter Steinberger
f39aff1558 docs: document remaining agent tool tests 2026-06-04 16:39:32 -04:00
Peter Steinberger
ea9f791a68 docs: document image and pdf tool tests 2026-06-04 16:38:27 -04:00
Peter Steinberger
d5ce1edf7e docs: document core agent tool tests 2026-06-04 16:37:10 -04:00
Peter Steinberger
5c71f2190b docs: document media cron tool tests 2026-06-04 16:35:06 -04:00
Peter Steinberger
c70e2bd2b3 docs: document sandbox helper tests 2026-06-04 16:33:10 -04:00
Peter Steinberger
ba02f12464 docs: document sandbox policy tests 2026-06-04 16:31:29 -04:00
Peter Steinberger
4e6fbf73a2 docs: document sandbox filesystem tests 2026-06-04 16:30:09 -04:00
Peter Steinberger
a0fa579cdc docs: document docker sandbox tests 2026-06-04 16:28:39 -04:00
Peter Steinberger
a98f292a11 docs: document status tool tests 2026-06-04 16:26:49 -04:00
Peter Steinberger
56ae6d3c1a docs: document common agent tool tests 2026-06-04 16:25:47 -04:00
Peter Steinberger
0e427e6cdc docs: document web runtime tests 2026-06-04 16:24:37 -04:00
Peter Steinberger
caa9078e70 docs: document web search fetch tests 2026-06-04 16:23:34 -04:00
Peter Steinberger
099584676b docs: document extension loader tests 2026-06-04 16:22:24 -04:00
Peter Steinberger
1ad9109a6c docs: document agent session infra tests 2026-06-04 16:21:30 -04:00
Peter Steinberger
1e8609af5d docs: document pdf web tool tests 2026-06-04 16:20:29 -04:00
Peter Steinberger
a3f21d03e8 docs: document sandbox config tests 2026-06-04 16:18:47 -04:00
Peter Steinberger
d045deb79d docs: document session tool tests 2026-06-04 16:17:22 -04:00
Peter Steinberger
c65eacae17 docs: document agent session tests 2026-06-04 16:16:25 -04:00
Chunyue Wang
6c259af759 fix(agents): strip stale compaction thinking signatures before Anthropic replay (#90163)
Pre-compaction assistant messages carry thinkingSignature values bound to the
original conversation prefix. After compaction the prefix changes (summarized
content is replaced by the compaction summary), so Anthropic rejects those
signatures with "Invalid signature in thinking block", permanently stalling the
session through gateway restarts.

stripInvalidThinkingSignatures only catches absent/blank signatures; this adds
stripStaleThinkingSignaturesForCompactionReplay (thinking.ts) which identifies
pre-compaction assistant messages by timestamp comparison against the latest
compaction summary and strips their signature fields. Called in
sanitizeSessionHistory (replay-history.ts) before stripInvalidThinkingSignatures
for all signed-thinking providers (Anthropic, Bedrock, Vertex). Also fixes
buildSuccessorEntries (compaction-successor-transcript.ts) to strip only
pre-compaction kept entries when writing the rotation successor JSONL; uses
strict < timestamp boundary so same-instant post-compaction messages are not
affected.

Docs: update transcript-hygiene.md Anthropic and Bedrock sections.
Tests: 8 new cases for stripStaleThinkingSignaturesForCompactionReplay; 1 new
case for buildSuccessorEntries verifying pre/post-compaction signature boundary.

Fixes #90108
2026-06-04 16:15:44 -04:00
Peter Steinberger
266dcf33f2 docs: document gateway web tool tests 2026-06-04 16:15:21 -04:00
Peter Steinberger
b380cdc84e docs: document media generation tests 2026-06-04 16:13:06 -04:00
Peter Steinberger
0f73e09769 docs: document sessions tools tests 2026-06-04 16:11:19 -04:00
Peter Steinberger
2d3b378876 docs: document tool guard tests 2026-06-04 16:09:36 -04:00
Peter Steinberger
3ac506a887 docs: document agent utility tests 2026-06-04 16:08:01 -04:00
Peter Steinberger
22f21ed7e6 docs: document runtime plan type tests 2026-06-04 16:06:12 -04:00
Shakker
bca7d18c60 test: scope chat directive transcript state 2026-06-04 21:06:04 +01:00
Shakker
9932ba7359 test: wrap session projection stores 2026-06-04 21:06:04 +01:00
Shakker
53978b358a test: isolate auth profile state dirs 2026-06-04 21:06:04 +01:00
Shakker
3f16f2e9a5 test: contain config write env state 2026-06-04 21:06:04 +01:00
Shakker
4a6dc1b830 test: scope task registry env setup 2026-06-04 21:06:04 +01:00
Peter Steinberger
7766d2b65b docs: document runtime plan tests 2026-06-04 16:04:45 -04:00
Peter Steinberger
01eefa7f96 docs: document transcript tool tests 2026-06-04 16:03:01 -04:00
Peter Steinberger
ff254a44c9 docs: document workspace transport tests 2026-06-04 16:01:19 -04:00
Peter Steinberger
22f2a91c2d docs: document system prompt tests 2026-06-04 15:59:59 -04:00
Peter Steinberger
1f57a946ca docs: document tool policy tests 2026-06-04 15:58:11 -04:00
Peter Steinberger
a09594b4ac docs: document subagent spawn tests 2026-06-04 15:56:34 -04:00
Peter Steinberger
e7de27f8b0 docs: document subagent registry persistence tests 2026-06-04 15:55:31 -04:00
Peter Steinberger
ec4a871f91 docs: document subagent registry lifecycle tests 2026-06-04 15:54:32 -04:00
Peter Steinberger
7af2673965 docs: document subagent registry helpers 2026-06-04 15:53:22 -04:00
Peter Steinberger
a9224f6f5d docs: document subagent control tests 2026-06-04 15:52:23 -04:00
Peter Steinberger
ca24dd7793 docs: document subagent announce tests 2026-06-04 15:51:15 -04:00
Peter Steinberger
f4ac968577 docs: document simple completion tests 2026-06-04 15:50:27 -04:00
Peter Steinberger
939fe702a6 docs: document session shell tests 2026-06-04 15:48:20 -04:00
Peter Steinberger
dca200ade5 docs: document session guard tests 2026-06-04 15:46:45 -04:00
Peter Steinberger
58f00707ed docs: document sandbox path tests 2026-06-04 15:45:12 -04:00
Peter Steinberger
3da0803ab4 docs: document sandbox runtime tests 2026-06-04 15:43:40 -04:00
Vincent Koc
40661e9d19 fix(test): use API-key auth for Codex live Docker lanes 2026-06-04 12:43:30 -07:00
Peter Steinberger
57ec0b236f docs: document runtime utility tests 2026-06-04 15:42:07 -04:00
Peter Steinberger
88a0fc69f0 docs: document provider transport tests 2026-06-04 15:40:23 -04:00
Peter Steinberger
4a93974a90 docs: document provider policy tests 2026-06-04 15:39:01 -04:00
Peter Steinberger
d3e5959669 docs: document subagent tool harness tests 2026-06-04 15:37:17 -04:00
Peter Steinberger
f8f7ba8f01 docs: document subagent spawn tests 2026-06-04 15:35:43 -04:00
Peter Steinberger
1a8d237369 docs: document openclaw session tool tests 2026-06-04 15:34:42 -04:00
Peter Steinberger
b491058e88 docs: document openclaw tool tests 2026-06-04 15:33:39 -04:00
Peter Steinberger
1df9bca8e2 docs: document openai transport tests 2026-06-04 15:32:37 -04:00
Peter Steinberger
48d67e88d0 docs: document openai live compat tests 2026-06-04 15:31:22 -04:00
Peter Steinberger
8605076a6f docs: document models config serialization tests 2026-06-04 15:30:20 -04:00
Peter Steinberger
c1b54fe01e docs: document provider policy tests 2026-06-04 15:29:14 -04:00
Peter Steinberger
117bb3c61c docs: document provider config tests 2026-06-04 15:28:05 -04:00
Peter Steinberger
c31877464c docs: document models config tests 2026-06-04 15:25:52 -04:00
Peter Steinberger
4653454c91 docs: document model selection tests 2026-06-04 15:24:14 -04:00
Peter Steinberger
287a62c2fd docs: document model runtime tests 2026-06-04 15:22:18 -04:00
Peter Steinberger
a0cdd4e305 docs: document model fallback tests 2026-06-04 15:20:42 -04:00
Peter Steinberger
3140bb695d docs: document model auth tests 2026-06-04 15:18:53 -04:00
Josh Lehman
ab0a633ab9 fix: tolerate missing streamed response content type
Fixes the OpenAI-compatible stream transport regression where a valid ChatGPT Codex HTTP 200 stream could arrive without a `content-type` header and be rejected before the OpenAI SDK consumed it.

Prepared head SHA: 0d7f8abb17

Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
2026-06-04 12:17:22 -07:00
Peter Steinberger
d4523cba74 docs: document model transport tests 2026-06-04 15:16:44 -04:00
Peter Steinberger
0d2a9073f5 docs: document mcp session tests 2026-06-04 15:14:28 -04:00
Peter Steinberger
4c5b2cf2e2 docs: document live model tests 2026-06-04 15:13:12 -04:00
Jacob Tomlinson
829847292e feat(nvidia): default to nemotron ultra
Add NVIDIA Nemotron 3 Ultra to the bundled NVIDIA provider catalog and make it the bundled fallback default. Keep successful NVIDIA featured-model feeds authoritative, while treating the first live feed row as the setup default option. Update NVIDIA provider docs and focused provider/model-picker tests for the new Ultra behavior.

Verification:
- node scripts/run-vitest.mjs src/commands/model-picker.test.ts extensions/nvidia/provider-catalog.test.ts extensions/nvidia/index.test.ts extensions/nvidia/onboard.test.ts
- pnpm exec oxfmt --check src/flows/model-picker.ts src/commands/model-picker.test.ts
- pnpm format:docs:check
- pnpm docs:check-mdx
- git diff --check upstream/main...HEAD
- .agents/skills/autoreview/scripts/autoreview --mode branch --base upstream/main --parallel-tests "node scripts/run-vitest.mjs src/commands/model-picker.test.ts extensions/nvidia/provider-catalog.test.ts extensions/nvidia/index.test.ts extensions/nvidia/onboard.test.ts"
2026-06-04 20:13:06 +01:00
Peter Steinberger
8048ceca71 docs: document agent identity tests 2026-06-04 15:10:14 -04:00
Peter Steinberger
a1c6a6e36f docs: document harness lifecycle tests 2026-06-04 15:08:47 -04:00
Peter Steinberger
bc2294b413 docs: document agent harness tests 2026-06-04 15:07:36 -04:00
Shakker
b4e47ae395 docs: document env mutation report 2026-06-04 20:06:24 +01:00
Shakker
51f9082873 test: add env mutation report 2026-06-04 20:06:24 +01:00
Peter Steinberger
85b4bd6c7b docs: document agent policy tests 2026-06-04 15:06:09 -04:00
Vincent Koc
023427b1d5 test(docker): cap npm scheduler concurrency 2026-06-04 12:04:45 -07:00
Peter Steinberger
5864669b3b docs: document embedded subscribe tools 2026-06-04 15:04:14 -04:00
Peter Steinberger
e0fe08ccce docs: document embedded subscribe lifecycle 2026-06-04 15:03:07 -04:00
Peter Steinberger
a5880a3747 docs: document embedded subscribe chunking 2026-06-04 15:02:04 -04:00
Peter Steinberger
60cb5d633f docs: document embedded subscribe text streaming 2026-06-04 15:00:33 -04:00
Peter Steinberger
fc7f96c826 docs: document embedded subscribe reply regressions 2026-06-04 14:59:30 -04:00
Peter Steinberger
6cde30a77c docs: document embedded subscribe handlers 2026-06-04 14:58:34 -04:00
Peter Steinberger
82d4d989d0 docs: document embedded runner helper tests 2026-06-04 14:57:12 -04:00
Peter Steinberger
69df4c9136 docs: document embedded runner registry tests 2026-06-04 14:54:59 -04:00
Peter Steinberger
689bafd16f docs: document embedded payload prompt tests 2026-06-04 14:53:23 -04:00
Peter Steinberger
d91f645d28 docs: document embedded failover image tests 2026-06-04 14:51:49 -04:00
Peter Steinberger
c7c67fc790 docs: document embedded attempt auth tests 2026-06-04 14:49:17 -04:00
Peter Steinberger
9dcf42472b docs: document embedded attempt spawn tests 2026-06-04 14:46:45 -04:00
Peter Steinberger
0f53d0000c docs: document embedded attempt workspace tests 2026-06-04 14:44:56 -04:00
Peter Steinberger
6365951160 docs: document embedded attempt helper tests 2026-06-04 14:43:03 -04:00
Peter Steinberger
838bc724ec docs: document embedded attempt tests 2026-06-04 14:41:17 -04:00
Peter Steinberger
ff39de4806 docs: document embedded runner overflow tests 2026-06-04 14:39:46 -04:00
Shakker
dfde0ce1a6 test: explain skipped changed vitest targets 2026-06-04 19:38:06 +01:00
Shakker
dd8f491040 test: expose changed fallback skip metadata 2026-06-04 19:38:06 +01:00
Peter Steinberger
a31d3355cd docs: document embedded runner run tests 2026-06-04 14:38:00 -04:00
Peter Steinberger
cd26595d6f docs: document embedded runner cache tests 2026-06-04 14:36:52 -04:00
Peter Steinberger
810f29b5f6 docs: document embedded runner model tests 2026-06-04 14:35:43 -04:00
Peter Steinberger
0b6aad58f2 docs: document embedded runner routing tests 2026-06-04 14:33:55 -04:00
Peter Steinberger
e78ef6fbad docs: document embedded runner extra params tests 2026-06-04 14:32:48 -04:00
Peter Steinberger
315cdd42fb docs: document embedded runner compaction tests 2026-06-04 14:31:27 -04:00
Peter Steinberger
85df2e1f85 docs: document embedded runner history tests 2026-06-04 14:29:25 -04:00
Peter Steinberger
94555a5898 docs: document embedded runner guard tests 2026-06-04 14:26:32 -04:00
Peter Steinberger
998adc707f docs: document embedded runner extra params tests 2026-06-04 14:24:17 -04:00
Peter Steinberger
77d0792e02 docs: document embedded helper classifier tests 2026-06-04 14:22:41 -04:00
Peter Steinberger
0241665795 docs: document embedded helper tests 2026-06-04 14:19:48 -04:00
Peter Steinberger
100be0e55a docs: document context lookup tests 2026-06-04 14:18:08 -04:00
Peter Steinberger
31b4575172 docs: document compaction tests 2026-06-04 14:16:34 -04:00
Peter Steinberger
17fc1c430f docs: document command session tests 2026-06-04 14:14:07 -04:00
Peter Steinberger
a767c6d1df docs: document agent command tests 2026-06-04 14:12:48 -04:00
Peter Steinberger
8fb70a90bd docs: document cli runner preparation tests 2026-06-04 14:11:04 -04:00
Peter Steinberger
429bf9fe84 docs: document cli runner bundle mcp tests 2026-06-04 14:07:58 -04:00
Peter Steinberger
a44c5ee3f7 docs: document cli runner tests 2026-06-04 14:06:45 -04:00
Peter Steinberger
bb6c3ce262 docs: document agent cli tests 2026-06-04 14:04:32 -04:00
Peter Steinberger
ddc832ead1 docs: document agent cache auth tests 2026-06-04 14:03:39 -04:00
Peter Steinberger
d216322640 docs: document agent bootstrap tests 2026-06-04 14:02:28 -04:00
Peter Steinberger
2761e8cc3b docs: document daemon systemd helpers 2026-06-04 14:00:34 -04:00
Peter Steinberger
407f4777d2 docs: document daemon service env helpers 2026-06-04 13:58:33 -04:00
Peter Steinberger
a3c44d53d1 docs: document daemon audit helpers 2026-06-04 13:57:21 -04:00
Peter Steinberger
feeaff20ab docs: document daemon runtime helpers 2026-06-04 13:55:33 -04:00
Peter Steinberger
975d40d474 docs: document launchd daemon helpers 2026-06-04 13:54:21 -04:00
Peter Steinberger
0d35da9cc4 docs: document daemon command helpers 2026-06-04 13:51:23 -04:00
Peter Steinberger
d1bf769dbd docs: document cron store helpers 2026-06-04 13:50:06 -04:00
Peter Steinberger
77f09f2575 docs: document cron service state and timer 2026-06-04 13:47:51 -04:00
Peter Steinberger
f51126f0fa docs: document cron service operations 2026-06-04 13:45:55 -04:00
Peter Steinberger
31ce6dfc4c docs: document cron schedule helpers 2026-06-04 13:44:43 -04:00
Peter Steinberger
875c9fd96d docs: document cron normalization and run logs 2026-06-04 13:42:58 -04:00
Peter Steinberger
03a2f6f89d docs: document isolated cron run helpers 2026-06-04 13:41:33 -04:00
Peter Steinberger
93e75f646f docs: document isolated cron delivery helpers 2026-06-04 13:38:54 -04:00
Peter Steinberger
9d2c7bcb66 docs: document cron delivery helpers 2026-06-04 13:37:20 -04:00
Peter Steinberger
a10dfb7185 docs: document crestodian helpers 2026-06-04 13:35:15 -04:00
Peter Steinberger
a0e19507e3 docs: document context engine helpers 2026-06-04 13:33:05 -04:00
Peter Steinberger
c74fd6f015 docs: document session transcript helpers 2026-06-04 13:31:05 -04:00
Peter Steinberger
fbac4a2ec7 docs: document session store facade 2026-06-04 13:29:18 -04:00
Peter Steinberger
3f16b96ddc docs: document session store helpers 2026-06-04 13:27:40 -04:00
Peter Steinberger
0e3f7a82fd docs: document session path helpers 2026-06-04 13:26:14 -04:00
Mason Huang
8b29ff5f16 fix(ci): scope PR merge diff checks to first parent (#90287)
Summary:
- This PR adds opt-in first-parent merge-head diff-base handling for CI changed-scope, changed-lanes, and OpenGrep PR scans, plus synthetic merge coverage and small lint/type cleanups.
- PR surface: Source +6, Tests +204, Config +1, Other +179. Total +390 across 15 files.
- Reproducibility: yes. The synthetic merge tests and PR body live-ref proof show the stale payload-base path can include main-only files, and first-parent mode narrows it to PR-owned paths.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(ci): update workflow guard expectations
- PR branch already contained follow-up commit before automerge: fix(ci): resolve plugin guardrail lint failures
- PR branch already contained follow-up commit before automerge: fix(ci): preserve plugin run context typing
- PR branch already contained follow-up commit before automerge: fix(ci): scope PR merge diff checks to first parent

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

Prepared head SHA: 40235e8c3d
Review: https://github.com/openclaw/openclaw/pull/90287#issuecomment-4621155576

Co-authored-by: Mason Huang <masonxhuang@tencent.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: hxy91819
Co-authored-by: hxy91819 <8814856+hxy91819@users.noreply.github.com>
2026-06-04 17:24:03 +00:00
Peter Steinberger
fff04af46d docs: document session metadata helpers 2026-06-04 13:23:36 -04:00
Peter Steinberger
c87c1569d5 docs: document session cleanup helpers 2026-06-04 13:21:00 -04:00
Peter Steinberger
d00d10f172 docs: document command test helpers 2026-06-04 13:18:37 -04:00
Peter Steinberger
8d65e78a07 docs: document task command helpers 2026-06-04 13:17:07 -04:00
Peter Steinberger
726bc2b6c7 docs: document status summary helpers 2026-06-04 13:15:41 -04:00
joshavant
cb4f6af504 fix ios canvas presentation 2026-06-04 10:15:32 -07:00
joshavant
81c8f525eb fix ios gateway forced reconnect 2026-06-04 10:15:32 -07:00
Peter Steinberger
1794efbba1 docs: document status scan core 2026-06-04 13:13:46 -04:00
Peter Steinberger
9b7ad2441f docs: document status scan helpers 2026-06-04 13:12:00 -04:00
Peter Steinberger
976ea3ff50 docs: document status command wiring 2026-06-04 13:10:25 -04:00
Peter Steinberger
f0b3c4164f docs: document status runtime helpers 2026-06-04 13:09:09 -04:00
Peter Steinberger
ea6d3232ca docs: document status overview helpers 2026-06-04 13:07:17 -04:00
Peter Steinberger
abb09b93cb docs: document status json helpers 2026-06-04 13:05:43 -04:00
Peter Steinberger
e11e4e8935 docs: document status report helpers 2026-06-04 13:04:21 -04:00
Peter Steinberger
2939ac6b72 docs: document status channel helpers 2026-06-04 13:02:30 -04:00
Peter Steinberger
ae948fa429 docs: document setup command helpers 2026-06-04 13:00:10 -04:00
Peter Steinberger
d09e0740e5 docs: document session display commands 2026-06-04 12:58:22 -04:00
Peter Steinberger
09467b1b87 docs: document sandbox session utilities 2026-06-04 12:55:08 -04:00
Peter Steinberger
97cdf8e7ac docs: document plugin install utilities 2026-06-04 12:52:51 -04:00
Vincent Koc
7dead6537a test(e2e): keep tui pty smoke off arm gateway runs 2026-06-04 09:50:57 -07:00
Peter Steinberger
d3cabb0fc6 docs: document onboard command surface 2026-06-04 12:50:31 -04:00
Peter Steinberger
c100ae1f36 docs: document noninteractive onboarding entrypoints 2026-06-04 12:48:25 -04:00
Peter Steinberger
3bae50af7f docs: document local onboarding helpers 2026-06-04 12:46:22 -04:00
Peter Steinberger
fe70a2f5a6 docs: document noninteractive auth choice helpers 2026-06-04 12:44:26 -04:00
Peter Steinberger
6c89ef9c3a docs: document noninteractive onboarding helpers 2026-06-04 12:42:33 -04:00
Peter Steinberger
0812b7e3a8 docs: document custom onboarding comments 2026-06-04 12:40:58 -04:00
Peter Steinberger
d4ed8964d3 docs: document onboarding helper comments 2026-06-04 12:37:56 -04:00
Peter Steinberger
12efbcaa7e docs: document daemon oauth command seams 2026-06-04 12:36:05 -04:00
Peter Steinberger
47f0af0d2d docs: document model command comments 2026-06-04 12:33:53 -04:00
Peter Steinberger
9d7f83b175 docs: document model list source comments 2026-06-04 12:32:05 -04:00
Peter Steinberger
73752f07f2 docs: document model list runtime comments 2026-06-04 12:29:16 -04:00
Peter Steinberger
feb6dc6bb6 docs: document model list comments 2026-06-04 12:27:15 -04:00
Peter Steinberger
865bd10bda docs: document model auth fallback comments 2026-06-04 12:25:03 -04:00
Peter Steinberger
945a7fdb36 docs: document migrate picker comments 2026-06-04 12:22:26 -04:00
Peter Steinberger
c6a6f56699 docs: document message migrate helpers 2026-06-04 12:20:26 -04:00
Peter Steinberger
09df56ee1f docs: document gateway status health comments 2026-06-04 12:18:52 -04:00
Peter Steinberger
473f651e09 docs: document gateway helper comments 2026-06-04 12:17:02 -04:00
Peter Steinberger
4912342dd7 docs: document doctor command entrypoints 2026-06-04 12:13:46 -04:00
Peter Steinberger
d4ac91d8f0 docs: document doctor state checks 2026-06-04 12:10:56 -04:00
Peter Steinberger
c0b3c8cdb9 docs: document doctor session checks 2026-06-04 12:08:24 -04:00
Peter Steinberger
0913b6989c docs: document doctor repair policy helpers 2026-06-04 12:06:06 -04:00
Peter Steinberger
9d6e8b872a docs: document doctor plugin checks 2026-06-04 12:04:28 -04:00
Peter Steinberger
f1e6177331 docs: document doctor heartbeat checks 2026-06-04 12:02:48 -04:00
Peter Steinberger
3b914ca40b docs: document doctor gateway checks 2026-06-04 12:01:00 -04:00
Peter Steinberger
688777ca79 docs: document doctor config preflight checks 2026-06-04 11:59:11 -04:00
Peter Steinberger
d2ff1c31d6 docs: document doctor configuration checks 2026-06-04 11:57:47 -04:00
Peter Steinberger
c0e9797644 docs: document doctor auth repair helpers 2026-06-04 11:56:19 -04:00
Peter Steinberger
3e2d56469b docs: document doctor stale warning helpers 2026-06-04 11:53:40 -04:00
Peter Steinberger
3e6978770a docs: document doctor plugin repair helpers 2026-06-04 11:51:25 -04:00
Peter Steinberger
51f7844c43 docs: document legacy web and auth helpers 2026-06-04 11:48:55 -04:00
Peter Steinberger
2016d32187 docs: document legacy migration registry 2026-06-04 11:46:48 -04:00
Peter Steinberger
90f9f2c2e4 docs: document runtime legacy migrations 2026-06-04 11:44:36 -04:00
Peter Steinberger
8dc5b9afcd docs: document legacy config normalizers 2026-06-04 11:43:03 -04:00
Peter Steinberger
4fcc7537ff docs: document doctor legacy repair helpers 2026-06-04 11:41:21 -04:00
Peter Steinberger
7beeedbe73 docs: document doctor warning helpers 2026-06-04 11:39:28 -04:00
Peter Steinberger
6a6da54062 docs: document codex doctor helpers 2026-06-04 11:37:55 -04:00
Peter Steinberger
efda5918b5 docs: document channel doctor helpers 2026-06-04 11:36:22 -04:00
Peter Steinberger
5e8190b779 docs: document doctor allowlist repairs 2026-06-04 11:34:47 -04:00
Peter Steinberger
98df83079d docs: document doctor allowlist warnings 2026-06-04 11:33:51 -04:00
Peter Steinberger
0c4ea11b06 docs: document doctor repair orchestration 2026-06-04 11:32:42 -04:00
Peter Steinberger
695e181179 docs: document doctor cron repair helpers 2026-06-04 11:31:17 -04:00
Peter Steinberger
4eb3d1fae9 docs: document daemon install helpers 2026-06-04 11:27:53 -04:00
Peter Steinberger
118060157d docs: document configure wizard helpers 2026-06-04 11:25:31 -04:00
Peter Steinberger
14962b2825 docs: document cleanup and configure helpers 2026-06-04 11:22:51 -04:00
Peter Steinberger
7385c611fc docs: document channel command helpers 2026-06-04 11:20:59 -04:00
Peter Steinberger
b9aade4b12 refactor: move MS Teams state migration to doctor 2026-06-04 08:20:39 -07:00
Peter Steinberger
3a335c6df1 refactor: remove Feishu runtime dedupe JSON fallback 2026-06-04 08:20:28 -07:00
Peter Steinberger
0a351cdf7f docs: document channel setup commands 2026-06-04 11:18:50 -04:00
Vincent Koc
52b07b4a46 test(ci): stabilize ARM changed-test guards 2026-06-04 08:18:15 -07:00
Jesse Merhi
fa3901e665 fix(message-tool): stabilize send idempotency keys 2026-06-04 20:47:41 +05:30
Peter Steinberger
4c32553875 docs: document backup and channel setup helpers 2026-06-04 11:16:37 -04:00
Peter Steinberger
63dfa848a6 docs: document auth choice command helpers 2026-06-04 11:14:36 -04:00
Peter Steinberger
03490ba1b9 docs: document agent management commands 2026-06-04 11:12:55 -04:00
Vincent Koc
367be94676 test(agents): stabilize compaction worker timing 2026-06-04 08:11:39 -07:00
Peter Steinberger
4af066b013 docs: document update and agent cli helpers 2026-06-04 11:11:07 -04:00
Peter Steinberger
20c4e9475a docs: document cli startup helpers 2026-06-04 11:07:54 -04:00
Peter Steinberger
98187f3277 docs: document cli program helpers 2026-06-04 11:05:42 -04:00
Peter Steinberger
f7e54acec1 docs: document plugin program cli helpers 2026-06-04 11:03:24 -04:00
Peter Steinberger
169a4159de docs: document plugin cli helpers 2026-06-04 11:01:13 -04:00
Peter Steinberger
a94a939626 docs: document cli command runtimes 2026-06-04 10:58:50 -04:00
Peter Steinberger
69c27677f6 docs: document gateway cli helpers 2026-06-04 10:56:59 -04:00
Peter Steinberger
cb5d43ba95 docs: document cli utility helpers 2026-06-04 10:55:01 -04:00
Peter Steinberger
8946648ace docs: document daemon cli helpers 2026-06-04 10:53:30 -04:00
Peter Steinberger
09cee22249 docs: document cli config and cron helpers 2026-06-04 10:52:10 -04:00
Vincent Koc
5522268b24 test(ci): align lint suppression guard 2026-06-04 07:51:28 -07:00
Peter Steinberger
e2a5823c83 docs: document cli startup policy helpers 2026-06-04 10:50:04 -04:00
Peter Steinberger
dd0dd662a1 docs: document cli command helpers 2026-06-04 10:48:30 -04:00
Peter Steinberger
ca9249e357 docs: document channel core helpers 2026-06-04 10:47:00 -04:00
Peter Steinberger
cc73ef8ba5 docs: document channel plugin setup types 2026-06-04 10:42:48 -04:00
Peter Steinberger
67ddda2a21 docs: document channel plugin runtime helpers 2026-06-04 10:39:46 -04:00
Peter Steinberger
15f3903b6f docs: document channel plugin contract helpers 2026-06-04 10:36:02 -04:00
Vincent Koc
ecb30fece4 fix(ci): stabilize include permission checks 2026-06-04 07:35:25 -07:00
Vincent Koc
71bda851d1 test(ui): harden control ui vitest setup 2026-06-04 07:35:02 -07:00
Peter Steinberger
08fd123906 docs: document channel plugin binding helpers 2026-06-04 10:31:28 -04:00
Peter Steinberger
7d3f1963d3 docs: document channel message access helpers 2026-06-04 10:28:38 -04:00
Peter Steinberger
6aed185ccb docs: document channel utility helpers 2026-06-04 10:26:27 -04:00
Peter Steinberger
041fab7b72 docs: document session web tools 2026-06-04 10:22:14 -04:00
Peter Steinberger
1ce11fbf42 docs: document media session tools 2026-06-04 10:17:38 -04:00
Peter Steinberger
2cbaacda43 docs: document built-in tool helpers 2026-06-04 10:14:36 -04:00
Peter Steinberger
bf1634b17a docs: document tool utility helpers 2026-06-04 10:11:57 -04:00
Peter Steinberger
3894fe11ca docs: document subagent registry helpers 2026-06-04 10:08:39 -04:00
Peter Steinberger
589b1f6aec docs: document subagent helpers 2026-06-04 10:05:53 -04:00
Peter Steinberger
600a57e60f docs: document session tools 2026-06-04 10:03:05 -04:00
Peter Steinberger
f84460e625 docs: document session services 2026-06-04 10:01:18 -04:00
Ayaan Zaidi
735587dde0 docs(acp): document parent commentary default 2026-06-04 19:30:46 +05:30
Ayaan Zaidi
8fdfb2d7e3 fix(acp): default parent commentary in progress mode 2026-06-04 19:30:46 +05:30
Peter Steinberger
88f78190ee docs: document sandbox helpers 2026-06-04 09:58:14 -04:00
Peter Steinberger
15361bfe07 docs: document provider tool helpers 2026-06-04 09:55:11 -04:00
Peter Steinberger
e8895f0a99 docs: document model selection helpers 2026-06-04 09:51:42 -04:00
Vincent Koc
8e9a4e99f5 test(gateway): extend vitest idle watchdog 2026-06-04 06:49:43 -07:00
Peter Steinberger
b031913031 docs: document agent runtime helpers 2026-06-04 09:48:36 -04:00
Peter Steinberger
585e89adbe docs: document exec and harness helpers 2026-06-04 09:46:22 -04:00
Peter Steinberger
46b826944c docs: document embedded subscribe helpers 2026-06-04 09:43:42 -04:00
Peter Steinberger
33bda2629a docs: document runner root helpers 2026-06-04 09:41:21 -04:00
Peter Steinberger
e95e51a24f docs: document runner message helpers 2026-06-04 09:38:52 -04:00
Peter Steinberger
fa6be505ef docs: document runner failover helpers 2026-06-04 09:36:19 -04:00
Peter Steinberger
20577f0b3b docs: document runner attempt runtime helpers 2026-06-04 09:34:34 -04:00
Peter Steinberger
f5ccfb7319 docs: document runner attempt helpers 2026-06-04 09:32:25 -04:00
Peter Steinberger
6719528316 docs: document runner model helpers 2026-06-04 09:30:31 -04:00
Peter Steinberger
dea0be4f11 docs: document runner utility helpers 2026-06-04 09:28:31 -04:00
Ayaan Zaidi
e59a7680e6 test(acp): keep streaming off regression typed (#89505) (thanks @100yenadmin) 2026-06-04 18:55:55 +05:30
Ayaan Zaidi
448e67bd8b fix(config): accept shared progress commentary (#89505) (thanks @100yenadmin) 2026-06-04 18:55:55 +05:30
Ayaan Zaidi
1f4b08ad2a fix(acp): preserve parent streaming off overrides (#89505) (thanks @100yenadmin) 2026-06-04 18:55:55 +05:30
Ayaan Zaidi
afb8d80ce7 fix(acp): satisfy relay lint checks (#89505) (thanks @100yenadmin) 2026-06-04 18:55:55 +05:30
Ayaan Zaidi
bd065c1154 fix(acp): harden parent commentary progress (#89505) (thanks @100yenadmin) 2026-06-04 18:55:55 +05:30
Ayaan Zaidi
4c9b724987 fix(acp): relay codex parent commentary 2026-06-04 18:55:55 +05:30
Ayaan Zaidi
2bf886b7dd fix(acp): reuse progress commentary config 2026-06-04 18:55:55 +05:30
Eva
9ac94568f3 fix(acp): honor tag visibility for status progress 2026-06-04 18:55:55 +05:30
Eva
ca0789ee8f fix(acp): type status progress relay metadata 2026-06-04 18:55:55 +05:30
Eva
0d44d970a9 Handle ACP status progress commentary 2026-06-04 18:55:55 +05:30
Eva
0c272958cf Add opt-in ACP commentary relay 2026-06-04 18:55:55 +05:30
Peter Steinberger
d07cce7bd1 docs: document runner compaction helpers 2026-06-04 09:25:41 -04:00
Peter Steinberger
dbfe5a252c docs: document embedded agent helpers 2026-06-04 09:22:40 -04:00
Peter Steinberger
550f707565 docs: document agent config helpers 2026-06-04 09:19:21 -04:00
Peter Steinberger
2173d1bf47 docs: document compaction planning helpers 2026-06-04 09:17:00 -04:00
Peter Steinberger
e12776037f docs: document agent command helpers 2026-06-04 09:15:00 -04:00
Peter Steinberger
a4550c5769 docs: document agent attempt helpers 2026-06-04 09:12:59 -04:00
Peter Steinberger
e996956c29 docs: document codex search helpers 2026-06-04 09:11:36 -04:00
Peter Steinberger
31a1034cb5 docs: document code mode helpers 2026-06-04 09:10:13 -04:00
Peter Steinberger
087fcf4085 docs: document cli runner history helpers 2026-06-04 09:08:46 -04:00
Peter Steinberger
1d7d8a1658 docs: document cli runner execution helpers 2026-06-04 09:06:53 -04:00
Peter Steinberger
f178c31305 docs: document cli runner shared helpers 2026-06-04 09:05:29 -04:00
Peter Steinberger
e6ec78ede4 docs: document cli runner mcp helpers 2026-06-04 09:04:02 -04:00
Peter Steinberger
34f7d78449 docs: document cli agent helpers 2026-06-04 09:02:44 -04:00
Peter Steinberger
258373717a docs: document chutes oauth helpers 2026-06-04 09:01:15 -04:00
Vincent Koc
383d214c7c test(plugin-sdk): restore testing facade guard 2026-06-04 15:00:21 +02:00
Vincent Koc
59777971d2 fix(plugins): remove stale run context generic 2026-06-04 15:00:21 +02:00
Vincent Koc
f187bec815 fix(cli): skip plugin loader cache clear on short-lived commands 2026-06-04 15:00:21 +02:00
Peter Steinberger
0dea7eab37 docs: document agent cache helpers 2026-06-04 09:00:00 -04:00
Peter Steinberger
b53c6eae62 docs: document bundle mcp helpers 2026-06-04 08:59:21 -04:00
Peter Steinberger
67ff2f8c95 docs: document btw side-question helpers 2026-06-04 08:58:19 -04:00
Peter Steinberger
1e1a966651 docs: document agent bootstrap helpers 2026-06-04 08:57:18 -04:00
Peter Steinberger
9b9d4883c3 docs: document codex command helpers 2026-06-04 08:55:39 -04:00
Peter Steinberger
796ed1b501 docs: document codex approval roundtrip 2026-06-04 08:53:19 -04:00
Peter Steinberger
ff867fcb7f docs: document codex protocol validators 2026-06-04 08:51:38 -04:00
Peter Steinberger
e72447de40 docs: document codex app-server support helpers 2026-06-04 08:50:13 -04:00
Peter Steinberger
bd94eda53a docs: document codex trajectory progress helpers 2026-06-04 08:48:00 -04:00
Peter Steinberger
d99268ae51 docs: document codex plugin app config 2026-06-04 08:46:41 -04:00
Peter Steinberger
22efdfa904 docs: document codex app-server runtime utilities 2026-06-04 08:45:03 -04:00
Peter Steinberger
b91ed087c8 docs: document codex app-server small utilities 2026-06-04 08:42:50 -04:00
Peter Steinberger
e4a775567c docs: document codex sandbox process bridge 2026-06-04 08:41:11 -04:00
Peter Steinberger
e1c7f228d6 docs: document codex sandbox fs policy 2026-06-04 08:40:26 -04:00
Peter Steinberger
226f5ac17f docs: document codex sandbox exec server 2026-06-04 08:39:30 -04:00
Peter Steinberger
29e9625b18 docs: document codex sandbox exec fs http 2026-06-04 08:37:41 -04:00
Peter Steinberger
b1c47dabd9 docs: document codex sandbox exec protocol 2026-06-04 08:36:28 -04:00
Peter Steinberger
2ff83d3023 docs: document codex app-server utilities 2026-06-04 08:35:15 -04:00
Peter Steinberger
121ee3f555 docs: document codex native subagent helpers 2026-06-04 08:33:42 -04:00
Peter Steinberger
7a2aa68960 docs: document codex app-server helpers 2026-06-04 08:32:30 -04:00
Peter Steinberger
c67491cbaf docs: document codex dynamic tool build 2026-06-04 08:31:20 -04:00
Peter Steinberger
7139f47333 docs: document codex dynamic tool bridge 2026-06-04 08:29:21 -04:00
Peter Steinberger
381a51b2d4 docs: document codex compaction projection 2026-06-04 08:27:05 -04:00
Peter Steinberger
0dc1d6a989 docs: document codex app-server client helpers 2026-06-04 08:25:40 -04:00
Peter Steinberger
0b5298d24e docs: document codex attempt context 2026-06-04 08:22:45 -04:00
Peter Steinberger
d249e25a64 docs: document codex attempt lifecycle helpers 2026-06-04 08:21:04 -04:00
Peter Steinberger
0050f6b165 docs: document codex notification helpers 2026-06-04 08:19:31 -04:00
Peter Steinberger
23258c86be docs: document codex app-server cleanup modules 2026-06-04 08:18:04 -04:00
Peter Steinberger
f60943717e docs: document codex root plugin modules 2026-06-04 08:16:35 -04:00
Peter Steinberger
8b477d2887 docs: document cloudflare and codex supervisor plugins 2026-06-04 08:14:22 -04:00
Peter Steinberger
802cdc7783 docs: document clickclack plugin 2026-06-04 08:12:22 -04:00
Peter Steinberger
a4a27517ff docs: document cerebras and chutes providers 2026-06-04 08:09:34 -04:00
Peter Steinberger
4726aaa08c docs: document canvas plugin 2026-06-04 08:07:38 -04:00
Peter Steinberger
18ecb82034 docs: document byteplus provider 2026-06-04 08:03:04 -04:00
Peter Steinberger
e900428a47 docs: document repo support scripts 2026-06-04 08:01:15 -04:00
Peter Steinberger
f07ee23d23 docs: document browser root modules 2026-06-04 08:00:12 -04:00
Peter Steinberger
f750029c72 docs: document browser support modules 2026-06-04 07:58:03 -04:00
Peter Steinberger
0d7f8051d0 docs: document browser cli modules 2026-06-04 07:56:15 -04:00
Peter Steinberger
5ab430fa11 docs: document browser server context 2026-06-04 07:53:45 -04:00
Peter Steinberger
29ddb9d926 docs: document browser utility helpers 2026-06-04 07:50:05 -04:00
Peter Steinberger
383531da96 docs: document browser playwright tools 2026-06-04 07:47:51 -04:00
Peter Steinberger
44ceccd2be docs: document browser playwright session 2026-06-04 07:45:58 -04:00
Peter Steinberger
3720ecaf52 docs: document browser config paths 2026-06-04 07:42:57 -04:00
Peter Steinberger
e8e57f9395 docs: document browser config support 2026-06-04 07:40:44 -04:00
Peter Steinberger
3dcdfee1e1 docs: document browser client APIs 2026-06-04 07:37:59 -04:00
Peter Steinberger
b24979cc30 docs: document browser chrome helpers 2026-06-04 07:36:15 -04:00
Peter Steinberger
c32748bc28 docs: document browser cdp runtime 2026-06-04 07:34:30 -04:00
Peter Steinberger
a3af426353 docs: document browser route support 2026-06-04 07:31:50 -04:00
Peter Steinberger
7fe6c16f03 docs: document browser route handlers 2026-06-04 07:29:19 -04:00
Peter Steinberger
ce56fc176a docs: document browser act routes 2026-06-04 07:27:43 -04:00
Peter Steinberger
5dcb072f7f docs: document browser cdp policies 2026-06-04 07:26:23 -04:00
Peter Steinberger
a982f798ca docs: document browser tool runtime 2026-06-04 07:24:31 -04:00
Peter Steinberger
83e4cfba30 docs: document browser plugin entrypoints 2026-06-04 07:22:35 -04:00
Peter Steinberger
2ad6314d72 docs: document small provider plugins 2026-06-04 07:20:32 -04:00
Peter Steinberger
caf930e65e docs: document anthropic runtime provider 2026-06-04 07:17:20 -04:00
Peter Steinberger
d89ad16124 docs: document anthropic cli config helpers 2026-06-04 07:15:05 -04:00
Peter Steinberger
c46610472f docs: document anthropic vertex plugin 2026-06-04 07:13:17 -04:00
Peter Steinberger
8cfc09238f docs: document bedrock provider plugins 2026-06-04 07:11:24 -04:00
Peter Steinberger
8c02521c47 docs: document active memory admin alibaba plugins 2026-06-04 07:07:49 -04:00
Peter Steinberger
bac84c5858 docs: document acpx runtime internals 2026-06-04 07:06:08 -04:00
Peter Steinberger
198d0b36a2 docs: document acpx process runtime helpers 2026-06-04 07:04:59 -04:00
Peter Steinberger
33c284ca0d docs: document acpx entry contracts 2026-06-04 07:03:35 -04:00
Peter Steinberger
1cbbfe8ed2 docs: document workspace policy helpers 2026-06-04 07:02:08 -04:00
Peter Steinberger
7ef836812b docs: document runtime plan contracts 2026-06-04 06:59:48 -04:00
Peter Steinberger
6ca104d129 docs: document runtime config helpers 2026-06-04 06:57:27 -04:00
Peter Steinberger
1a8263c2f5 docs: document auth redaction helpers 2026-06-04 06:55:31 -04:00
Peter Steinberger
18ed27bf5f docs: document provider planning helpers 2026-06-04 06:53:58 -04:00
Peter Steinberger
8edd7e84ad docs: document session output helpers 2026-06-04 06:52:02 -04:00
Peter Steinberger
7fb74310f0 docs: document agent policy helpers 2026-06-04 06:50:26 -04:00
Peter Steinberger
eb48b6bd06 docs: document agent utility contracts 2026-06-04 06:48:45 -04:00
Peter Steinberger
511f114138 docs: document models config runtime 2026-06-04 06:47:40 -04:00
Peter Steinberger
7913b6cd27 docs: document agent control helpers 2026-06-04 06:46:16 -04:00
Peter Steinberger
f3a2488ab0 docs: document agent test helpers 2026-06-04 06:44:16 -04:00
Peter Steinberger
f5f046a736 docs: document provider auth helpers 2026-06-04 06:42:49 -04:00
Peter Steinberger
e533ff4c4a docs: document runtime helper contracts 2026-06-04 06:40:26 -04:00
Peter Steinberger
fbf3e009d4 docs: document failover utility helpers 2026-06-04 06:37:21 -04:00
Peter Steinberger
21031c2243 docs: document model policy helpers 2026-06-04 06:34:39 -04:00
Peter Steinberger
5181a93391 docs: document agent utility helpers 2026-06-04 06:32:31 -04:00
Peter Steinberger
a8f6e7601b docs: document live model helpers 2026-06-04 06:30:11 -04:00
Peter Steinberger
76f2a12ad7 docs: document auth marker helpers 2026-06-04 06:28:22 -04:00
Peter Steinberger
88eb405491 docs: document tool utility helpers 2026-06-04 06:26:19 -04:00
Peter Steinberger
36ae3dd235 docs: document transcript policy helpers 2026-06-04 06:24:59 -04:00
Peter Steinberger
53d08d4aef docs: document agent helper contracts 2026-06-04 06:23:32 -04:00
Peter Steinberger
d2d2dfd9f2 docs: document process tool controls 2026-06-04 06:21:15 -04:00
Peter Steinberger
ac7ef5b8c6 style: restore exec approval e2e formatting 2026-06-04 06:18:55 -04:00
Peter Steinberger
bc88f735cd style: restore exec approval e2e formatting 2026-06-04 06:17:53 -04:00
Peter Steinberger
2feb81249f docs: document exec tool entry 2026-06-04 06:16:34 -04:00
Peter Steinberger
045145c700 docs: document exec runtime 2026-06-04 06:13:45 -04:00
Peter Steinberger
ec6cf6a2ac docs: document node exec host 2026-06-04 06:12:10 -04:00
Peter Steinberger
6537080674 docs: document gateway exec host 2026-06-04 06:09:49 -04:00
Peter Steinberger
8cd4d74d94 docs: document exec approval requests 2026-06-04 06:08:28 -04:00
Peter Steinberger
e5f3bf99cc docs: document exec approval followups 2026-06-04 06:07:16 -04:00
Peter Steinberger
11eb9ac1b9 docs: document bash tool helpers 2026-06-04 06:06:03 -04:00
Peter Steinberger
41cefdff8f docs: document bash process registry 2026-06-04 06:04:28 -04:00
Peter Steinberger
7b8da19302 docs: document auth profile usage 2026-06-04 06:03:18 -04:00
Peter Steinberger
db7a228e6c docs: document auth profile state store 2026-06-04 06:01:47 -04:00
Peter Steinberger
f9613ff01e docs: document auth profile persistence 2026-06-04 05:59:18 -04:00
Ayaan Zaidi
9ed9af4f39 fix(agents): restore Anthropic system cache boundary 2026-06-04 15:27:23 +05:30
Ayaan Zaidi
01cc68ee0d fix(agents): bound Anthropic cache markers 2026-06-04 15:27:23 +05:30
Ayaan Zaidi
2454952544 fix(agents): keep Anthropic tool cache breakpoint advancing 2026-06-04 15:27:23 +05:30
Ayaan Zaidi
77c383d1e0 refactor(agents): distill Anthropic cache marker cleanup 2026-06-04 15:27:23 +05:30
Peter Lindsey
ca9ab97427 fix: stabilize Anthropic cache marker through tool loops 2026-06-04 15:27:23 +05:30
Peter Steinberger
e1da5a36d4 docs: document oauth profile resolution 2026-06-04 05:57:01 -04:00
Peter Steinberger
d581d9d733 docs: document oauth refresh manager 2026-06-04 05:54:47 -04:00
Peter Steinberger
9d20ad261a docs: document oauth identity helpers 2026-06-04 05:52:57 -04:00
Peter Steinberger
81516ca1a4 docs: document external auth overlays 2026-06-04 05:51:25 -04:00
Peter Steinberger
474d6e520a docs: document auth health helpers 2026-06-04 05:49:50 -04:00
Vincent Koc
2cba10a49f test(infra): remove empty skipped builtin placeholder 2026-06-04 11:48:40 +02:00
Peter Steinberger
f7ef52e66d docs: document apply patch helpers 2026-06-04 05:47:45 -04:00
Peter Steinberger
479df18caf docs: document anthropic agent transports 2026-06-04 05:46:46 -04:00
Peter Steinberger
523537a627 docs: document agent tool assembly 2026-06-04 05:45:05 -04:00
Peter Steinberger
c25800ccc1 docs: document agent tool guards 2026-06-04 05:43:42 -04:00
Peter Steinberger
60e0d2a7b9 docs: document agent tool adapters 2026-06-04 05:41:50 -04:00
Peter Steinberger
634174f050 docs: document agent model settings 2026-06-04 05:38:58 -04:00
Peter Steinberger
6c113837b8 docs: document agent command hooks 2026-06-04 05:36:39 -04:00
Peter Steinberger
f2d8facb48 docs: document agent bundle runtimes 2026-06-04 05:34:48 -04:00
Peter Steinberger
b851ba2f98 docs: document agents acp auth helpers 2026-06-04 05:32:57 -04:00
Peter Steinberger
e112fb939a docs: document acp translator bridge 2026-06-04 05:31:15 -04:00
Peter Steinberger
61fdc7bf34 docs: document acp runtime bridge 2026-06-04 05:29:10 -04:00
Peter Steinberger
fc64494b03 docs: document acp events and bindings 2026-06-04 05:27:58 -04:00
Peter Steinberger
05289f1aa0 docs: document acp turn control plane 2026-06-04 05:26:08 -04:00
Peter Steinberger
d88b06cb75 docs: document acp runtime handles 2026-06-04 05:24:49 -04:00
Peter Steinberger
c782e8e44f docs: document acp control plane failover 2026-06-04 05:23:42 -04:00
Peter Steinberger
5a350aeaf5 docs: document acp client helpers 2026-06-04 05:22:29 -04:00
Peter Steinberger
053fbf0209 docs: document web fetch runtime 2026-06-04 05:21:16 -04:00
Peter Steinberger
3c1e9984e0 docs: document secrets surface helpers 2026-06-04 05:20:16 -04:00
Peter Steinberger
bea35d0902 docs: document secrets resolution helpers 2026-06-04 05:17:28 -04:00
Peter Steinberger
d28ac4dbdb docs: document secrets target registry 2026-06-04 05:13:24 -04:00
Peter Steinberger
a720a1f9de docs: document secrets runtime state 2026-06-04 05:09:51 -04:00
Peter Steinberger
5a869eea5a docs: document node host runtime 2026-06-04 05:06:54 -04:00
Peter Steinberger
0135a0a780 docs: document image generation runtime 2026-06-04 05:00:21 -04:00
Peter Steinberger
f4d2748ca5 docs: document plugin metadata utilities 2026-06-04 04:57:48 -04:00
Vincent Koc
72bb5cd692 fix(e2e): bound release journey output assertions 2026-06-04 10:55:37 +02:00
Peter Steinberger
7c1deea5fa docs: document plugin setup state 2026-06-04 04:54:37 -04:00
Peter Steinberger
f875b519e5 docs: document plugin host contracts 2026-06-04 04:51:24 -04:00
Peter Steinberger
040ebadfc5 docs: document plugin runtime contracts 2026-06-04 04:48:42 -04:00
Peter Steinberger
f91fab8b18 docs: document plugin manifest helpers 2026-06-04 04:46:27 -04:00
Peter Steinberger
a77f20a6d6 docs: document plugin registry helpers 2026-06-04 04:44:12 -04:00
Peter Steinberger
4d54d196c9 docs: document plugin discovery helpers 2026-06-04 04:41:47 -04:00
Peter Steinberger
fffd72f36d docs: document plugin compatibility helpers 2026-06-04 04:38:31 -04:00
Peter Steinberger
21572415c8 docs: document plugin lifecycle helpers 2026-06-04 04:36:44 -04:00
Peter Steinberger
f6049db20f docs: document plugin discovery helpers 2026-06-04 04:34:19 -04:00
Vincent Koc
d77d231507 fix(e2e): ignore stale agent output markers 2026-06-04 10:32:31 +02:00
Peter Steinberger
6de517cbcb docs: document plugin registry helpers 2026-06-04 04:31:52 -04:00
Peter Steinberger
507e237d8c docs: document plugin command helpers 2026-06-04 04:29:53 -04:00
Peter Steinberger
6082f01b97 docs: document plugin loader helpers 2026-06-04 04:27:23 -04:00
Peter Steinberger
d33664aef0 docs: document plugin provider helpers 2026-06-04 04:25:18 -04:00
Peter Steinberger
8975f75c8b docs: document plugin public surface helpers 2026-06-04 04:22:45 -04:00
Peter Steinberger
463e9f2704 docs: document plugin install metadata helpers 2026-06-04 04:21:07 -04:00
Vincent Koc
10b9df6d8a fix(release): bound cross-os agent log fallback reads 2026-06-04 10:19:21 +02:00
Peter Steinberger
ee282c6de5 docs: document plugin auth helpers 2026-06-04 04:19:03 -04:00
Peter Steinberger
6d6aba2be5 docs: document plugin runtime helpers 2026-06-04 04:17:40 -04:00
Peter Steinberger
a6d084113a docs: document plugin install helpers 2026-06-04 04:15:33 -04:00
Peter Steinberger
6a2b1b2198 docs: document plugin metadata helpers 2026-06-04 04:14:06 -04:00
Peter Steinberger
e8e6c684bb docs: document tui and plugin helpers 2026-06-04 04:12:38 -04:00
Peter Steinberger
4ed2fb75f2 docs: document tui runtime helpers 2026-06-04 04:10:14 -04:00
Peter Steinberger
bced79b63d docs: document security policy helpers 2026-06-04 04:08:10 -04:00
Mrunal Patel
d522e02fe4 fix(docker): qualify base image refs for podman short-name mode (#90058)
* fix(docker): qualify base image refs for podman short-name mode

Podman with short-name-mode=enforcing (the Fedora/RHEL default) blocked
the build: `FROM oven/bun:1.3.13...` is an ambiguous short name with no
alias, so Podman prompted interactively for a registry (the apparent
"hang") or, headless, failed with "short-name resolution enforced but
cannot prompt without a TTY". `node:*` only resolved because a `node`
short-name alias ships in registries.conf.d.

Fully-qualify the node and bun base images with docker.io/ so registry
resolution is deterministic. Pinned digests are unchanged, so resolved
image content is identical, and Docker/Buildx builds are unaffected.

Also qualify the docker.io/ prefix in the digest-refresh maintenance
comments so the documented update path matches the defaults and does not
reintroduce the same short-name ambiguity for Podman users.

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

* test(docker): expect qualified base image refs

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: sallyom <somalley@redhat.com>
2026-06-04 04:06:20 -04:00
Peter Steinberger
961759c08b docs: document security finding helpers 2026-06-04 04:06:08 -04:00
Peter Steinberger
0e8c5fd85d docs: document security audit helpers 2026-06-04 04:04:43 -04:00
Peter Steinberger
8408c16da4 docs: document config diagnostics helpers 2026-06-04 04:03:16 -04:00
Peter Steinberger
40c8ed0dff docs: document config provider helpers 2026-06-04 04:01:24 -04:00
Peter Steinberger
838644b989 docs: document config policy helpers 2026-06-04 04:00:03 -04:00
Peter Steinberger
0796e992e4 docs: document config schema runtime helpers 2026-06-04 03:58:47 -04:00
mushuiyu_xydt
1f1ce8a1fe fix(feishu): preserve streaming card content (#90181)
* fix(feishu): preserve streaming card content

* fix(feishu): preserve streaming card content (#90181) (thanks @mushuiyu886)

---------

Co-authored-by: sliverp <870080352@qq.com>
2026-06-04 15:57:37 +08:00
Peter Steinberger
9572267f64 docs: document config recovery helpers 2026-06-04 03:57:07 -04:00
Peter Steinberger
edc6042c65 docs: document config validation helpers 2026-06-04 03:55:53 -04:00
Peter Steinberger
186e966483 docs: document config agent helpers 2026-06-04 03:54:37 -04:00
Peter Steinberger
51474b6f15 docs: document config mutation helpers 2026-06-04 03:52:12 -04:00
Peter Steinberger
54fe5dc842 docs: document config runtime helpers 2026-06-04 03:51:01 -04:00
Peter Steinberger
6989d6283a docs: document config io helpers 2026-06-04 03:49:54 -04:00
Peter Steinberger
43190f5248 docs: document config schema helpers 2026-06-04 03:47:57 -04:00
Peter Steinberger
6f358fd8e0 docs: document config and commitments helpers 2026-06-04 03:46:55 -04:00
Peter Steinberger
bff849b874 docs: document tool and model helpers 2026-06-04 03:45:53 -04:00
Peter Steinberger
606e3d7866 docs: document task execution helpers 2026-06-04 03:44:45 -04:00
Peter Steinberger
cca24cc78b docs: document task registry helpers 2026-06-04 03:43:46 -04:00
Peter Steinberger
4930766711 docs: document infra flow helpers 2026-06-04 03:42:18 -04:00
Peter Steinberger
2e8b444da8 docs: document infra storage helpers 2026-06-04 03:40:45 -04:00
Peter Steinberger
d13a431860 docs: document infra install runtime helpers 2026-06-04 03:37:48 -04:00
Peter Steinberger
117aca7f4e docs: document infra approval runtime helpers 2026-06-04 03:34:47 -04:00
Peter Steinberger
ec3aa5def4 docs: document infra transport helpers 2026-06-04 03:32:06 -04:00
Peter Steinberger
73b6de1011 docs: document infra socket helpers 2026-06-04 03:29:58 -04:00
Peter Steinberger
5780aa1cd6 docs: document infra heartbeat helpers 2026-06-04 03:27:16 -04:00
Peter Steinberger
cfe31ca3b2 docs: document infra policy helpers 2026-06-04 03:24:36 -04:00
Peter Steinberger
25eb63885d docs: document infra event helpers 2026-06-04 03:22:42 -04:00
Peter Steinberger
79dc565825 docs: document infra database helpers 2026-06-04 03:20:32 -04:00
Peter Steinberger
5dcb2ab40e docs: document infra push helpers 2026-06-04 03:17:57 -04:00
Peter Steinberger
d3b38311b0 docs: document infra approval helpers 2026-06-04 03:16:06 -04:00
Peter Steinberger
59fca2d738 docs: document infra runtime helpers 2026-06-04 03:14:19 -04:00
Peter Steinberger
1275368151 docs: document infra install helpers 2026-06-04 03:12:47 -04:00
Peter Steinberger
a881181fd8 docs: document infra update helpers 2026-06-04 03:11:12 -04:00
Peter Steinberger
f542e23a2f docs: document infra pairing helpers 2026-06-04 03:09:43 -04:00
Pavan Kumar Gondhi
3c6259ebb7 fix: guard mcp http redirects (#89732) 2026-06-04 12:38:25 +05:30
Peter Steinberger
6851dc9505 docs: document infra config helpers 2026-06-04 03:08:12 -04:00
Vincent Koc
cce1a14795 fix(e2e): bound parallels package progress extraction 2026-06-04 09:06:35 +02:00
Peter Steinberger
2f814c6c92 docs: document infra execution helpers 2026-06-04 03:06:18 -04:00
Peter Steinberger
dc3f2bd1d9 docs: document infra process helpers 2026-06-04 03:03:13 -04:00
Peter Steinberger
d819ef3e32 docs: document infra status helpers 2026-06-04 03:00:57 -04:00
Peter Steinberger
334a1dd716 docs: document infra package helpers 2026-06-04 02:58:15 -04:00
Vincent Koc
5c08fb225a fix(e2e): stream docker stats resource scans 2026-06-04 08:57:47 +02:00
Peter Steinberger
0d109750ae docs: document infra runtime helpers 2026-06-04 02:55:39 -04:00
Peter Steinberger
a9a386dee1 docs: document test utils and infra helpers 2026-06-04 02:53:43 -04:00
Peter Steinberger
4295329ec3 docs: document test utility helpers 2026-06-04 02:51:28 -04:00
Peter Steinberger
b4f16c7bcb docs: document pairing and transcript helpers 2026-06-04 02:49:38 -04:00
Vincent Koc
e17bfc4938 fix(e2e): tighten kitchen sink plugin log allowlist 2026-06-04 08:48:58 +02:00
Peter Steinberger
2db057423b docs: document root and music generation helpers 2026-06-04 02:47:44 -04:00
Peter Steinberger
c1aa424d6b docs: document src root entry helpers 2026-06-04 02:45:34 -04:00
Peter Steinberger
b18a05ae3e docs: document auto reply queue and acp helpers 2026-06-04 02:43:05 -04:00
Peter Steinberger
29f057b242 docs: document auto reply admission helpers 2026-06-04 02:41:04 -04:00
Peter Steinberger
119bb57627 docs: document auto reply command gates 2026-06-04 02:39:12 -04:00
Vincent Koc
21c3d6993b fix(e2e): tighten kitchen sink error log allowlist 2026-06-04 08:37:59 +02:00
Peter Steinberger
e71e585969 docs: document auto reply fast path helpers 2026-06-04 02:37:27 -04:00
Peter Steinberger
ea6d3a35ff docs: document auto reply dispatch helpers 2026-06-04 02:35:34 -04:00
Peter Steinberger
26355cc35d docs: document auto reply delivery runtime 2026-06-04 02:33:09 -04:00
Peter Steinberger
25e9097af0 docs: document auto reply session routing 2026-06-04 02:31:18 -04:00
Vincent Koc
d0f05d98d2 fix(e2e): share gateway websocket request handling 2026-06-04 08:29:33 +02:00
Peter Steinberger
88b27c378d docs: document auto reply directive helpers 2026-06-04 02:29:18 -04:00
Peter Steinberger
a66462b583 docs: document auto reply runner support 2026-06-04 02:28:00 -04:00
Peter Steinberger
e61fb145fc docs: document auto reply runtime helpers 2026-06-04 02:26:38 -04:00
Peter Steinberger
afeab32780 docs: document auto reply routing helpers 2026-06-04 02:25:21 -04:00
Peter Steinberger
4e5752631c docs: document auto reply behavior tests 2026-06-04 02:23:56 -04:00
Peter Steinberger
08b1b06aab docs: document auto reply reply tests 2026-06-04 02:21:46 -04:00
Peter Steinberger
0289b046da docs: document auto reply command tests 2026-06-04 02:18:51 -04:00
Peter Steinberger
1053a76dd8 docs: document auto reply top-level tests 2026-06-04 02:16:34 -04:00
Peter Steinberger
82e5dd4da7 docs: document auto reply runner internals 2026-06-04 02:13:57 -04:00
Peter Steinberger
a70e618b20 docs: document auto reply routing helpers 2026-06-04 02:11:29 -04:00
Peter Steinberger
20d7c7ae02 docs: document auto reply run helpers 2026-06-04 02:09:32 -04:00
Peter Steinberger
1c0fb5768b docs: document auto reply command helpers 2026-06-04 02:06:38 -04:00
Peter Steinberger
25c0699fe9 docs: document auto reply delivery helpers 2026-06-04 02:04:44 -04:00
Vincent Koc
ce0d5117bf fix(e2e): fail codex app server log errors 2026-06-04 08:03:24 +02:00
Peter Steinberger
826cdd884c docs: document auto reply command contracts 2026-06-04 02:01:00 -04:00
Peter Steinberger
4503560084 docs: document auto reply top-level helpers 2026-06-04 01:58:27 -04:00
Peter Steinberger
bf1056c554 docs: document gateway helper contracts 2026-06-04 01:54:01 -04:00
Peter Steinberger
344417c0de docs: document media and sdk package facades 2026-06-04 01:51:12 -04:00
Peter Steinberger
86150a3e51 docs: document shared test helpers 2026-06-04 01:48:32 -04:00
Peter Steinberger
d8b5e22e8b docs: document voice call runtime surfaces 2026-06-04 01:45:11 -04:00
Peter Steinberger
5dd026f3f7 docs: document voice call manager helpers 2026-06-04 01:42:32 -04:00
Peter Steinberger
ae5376a599 docs: document voice call helper APIs 2026-06-04 01:41:07 -04:00
Vincent Koc
cc122956df fix(qa): bound malformed otlp receiver requests 2026-06-04 07:39:30 +02:00
Peter Steinberger
eaf803b223 docs: document shared package contracts 2026-06-04 01:39:12 -04:00
Peter Steinberger
d14fe163b5 docs: document terminal core helpers 2026-06-04 01:36:23 -04:00
Peter Steinberger
5b98f03c64 docs: document memory host runtime helpers 2026-06-04 01:34:57 -04:00
Peter Steinberger
eecec7495f docs: document memory remote helpers 2026-06-04 01:32:51 -04:00
Peter Steinberger
c40dd6ff5c docs: document memory batch helpers 2026-06-04 01:31:52 -04:00
Peter Steinberger
8b6bed9c9c docs: document memory host sdk helpers 2026-06-04 01:30:42 -04:00
Peter Steinberger
5a10f46c56 docs: document sdk package facades 2026-06-04 01:26:12 -04:00
Peter Steinberger
bdfeece562 docs: document agent core package 2026-06-04 01:23:43 -04:00
Peter Steinberger
aafdf67d39 docs: document speech core facades 2026-06-04 01:22:31 -04:00
Peter Steinberger
55bde6750f docs: document embedded agent barrels 2026-06-04 01:21:09 -04:00
Peter Steinberger
546f44f395 docs: document ssh sandbox helpers 2026-06-04 01:20:33 -04:00
Peter Steinberger
9877f31fdd docs: document code mode bridge 2026-06-04 01:19:45 -04:00
Peter Steinberger
9bc7712c40 docs: document model selection reconciliation 2026-06-04 01:18:43 -04:00
Peter Steinberger
ba445e0e3f docs: document tool search surfaces 2026-06-04 01:17:16 -04:00
Peter Steinberger
f1ec2605b7 docs: document subscribe helper seams 2026-06-04 01:16:17 -04:00
Peter Steinberger
81f359ec5b docs: document transcript state harness 2026-06-04 01:15:25 -04:00
Peter Steinberger
d8a67ef39a docs: document sandbox backend bridges 2026-06-04 01:14:14 -04:00
Peter Steinberger
cf36b9456d docs: document subagent display seams 2026-06-04 01:12:08 -04:00
Peter Steinberger
376bf65d8e docs: document session list web search tools 2026-06-04 01:10:37 -04:00
Peter Steinberger
b7b069c4d6 docs: document Claude CLI runner helpers 2026-06-04 01:08:59 -04:00
Peter Steinberger
340fca0a45 docs: document embedded compaction helpers 2026-06-04 01:07:28 -04:00
Peter Steinberger
c768a9e6ca docs: document e2e helper mocks 2026-06-04 01:05:37 -04:00
Peter Steinberger
ce1ef04efe docs: document schema media planning helpers 2026-06-04 01:04:21 -04:00
Peter Steinberger
79f6c5a8ad docs: document media generation actions 2026-06-04 01:02:54 -04:00
Peter Steinberger
3a5baf1229 docs: document embedded attempt helpers 2026-06-04 01:01:38 -04:00
Peter Steinberger
fe52654d2e docs: document agent test helpers 2026-06-04 01:00:05 -04:00
Peter Steinberger
45144ce2e8 docs: document model helper normalization 2026-06-04 00:58:43 -04:00
Peter Steinberger
5b36bbf83e docs: document sandbox lifecycle registry 2026-06-04 00:57:14 -04:00
Peter Steinberger
9b4e2fa8a8 docs: document session tool bridges 2026-06-04 00:55:22 -04:00
Peter Steinberger
cc191e8021 docs: document sandbox support types 2026-06-04 00:53:43 -04:00
Peter Steinberger
64b9b60d94 docs: document runner guard helpers 2026-06-04 00:52:32 -04:00
Vincent Koc
68307afb5b fix(e2e): fail parallels host log write errors 2026-06-04 06:51:27 +02:00
Peter Steinberger
5a557b5e10 docs: document embedded runner state helpers 2026-06-04 00:51:06 -04:00
Peter Steinberger
7e85ba6139 docs: document sandbox helper utilities 2026-06-04 00:49:00 -04:00
Peter Steinberger
edd3870d53 docs: document session helper metadata 2026-06-04 00:47:48 -04:00
Peter Steinberger
a590fd24a9 docs: document agent harness helpers 2026-06-04 00:46:44 -04:00
Peter Steinberger
8f14a1c59a docs: document embedded runner helpers 2026-06-04 00:45:31 -04:00
Peter Steinberger
e1db0f01fe docs: document image pdf model helpers 2026-06-04 00:42:21 -04:00
Peter Steinberger
b1053ef9e9 docs: document session reply sentinels 2026-06-04 00:41:04 -04:00
Peter Steinberger
35d801a1e5 docs: document goal planning tools 2026-06-04 00:40:08 -04:00
Peter Steinberger
d901f85abb docs: document sandbox backend contracts 2026-06-04 00:38:23 -04:00
Vincent Koc
61d16dd173 fix(e2e): fail mock openai request log errors 2026-06-04 06:37:24 +02:00
Peter Steinberger
bb8e0ab5dc docs: document sandbox hash helpers 2026-06-04 00:37:11 -04:00
Peter Steinberger
1c640622dd docs: document sandbox safety helpers 2026-06-04 00:36:17 -04:00
Peter Steinberger
918d5afd67 docs: document embedded gateway helpers 2026-06-04 00:34:26 -04:00
Peter Steinberger
5820d105c9 docs: document media generation task helpers 2026-06-04 00:33:39 -04:00
Pavan Kumar Gondhi
3f1e0ebb86 Rate limit node pairing requests [AI] (#90147)
* fix: rate limit node pairing requests

* fix: preserve paired node reconnects
2026-06-04 10:02:55 +05:30
Peter Steinberger
52f96fab51 docs: document subagent tool step helpers 2026-06-04 00:31:53 -04:00
Peter Steinberger
9c10ef2ffa docs: document web fetch helpers 2026-06-04 00:30:14 -04:00
Peter Steinberger
4cd8b5eb78 docs: document tool runtime availability helpers 2026-06-04 00:29:25 -04:00
Peter Steinberger
07676fbb44 docs: document subagent registry read helpers 2026-06-04 00:28:29 -04:00
Peter Steinberger
bb1f3e8eaf docs: document session tool render helpers 2026-06-04 00:26:52 -04:00
Peter Steinberger
fd3cc7d224 docs: document session support helpers 2026-06-04 00:26:08 -04:00
Peter Steinberger
5a62a896b2 docs: document subagent announce runtime helpers 2026-06-04 00:25:03 -04:00
Pavan Kumar Gondhi
85b26bd206 fix: protect global agent config defaults (#90145) 2026-06-04 09:53:56 +05:30
Peter Steinberger
6f08a1a3dd docs: document session tool helpers 2026-06-04 00:23:38 -04:00
Peter Steinberger
20c3736dae docs: document web search helper config 2026-06-04 00:22:49 -04:00
Peter Steinberger
3c21fdad3c docs: document subagent session helpers 2026-06-04 00:22:08 -04:00
Peter Steinberger
5960549816 docs: document session keybinding helpers 2026-06-04 00:20:46 -04:00
Vincent Koc
2baa9d550e fix(e2e): fail pty transcript log errors 2026-06-04 06:20:02 +02:00
Peter Steinberger
b90fb1ef71 docs: document tool inventory helpers 2026-06-04 00:19:46 -04:00
Peter Steinberger
301c6d0043 docs: document exec utility helpers 2026-06-04 00:18:22 -04:00
Peter Steinberger
ed4c133c2c docs: document tool policy helpers 2026-06-04 00:17:34 -04:00
Peter Steinberger
f4369d225a docs: document model auth helpers 2026-06-04 00:15:57 -04:00
Peter Steinberger
b77c272fb9 docs: document tool model helpers 2026-06-04 00:13:55 -04:00
Peter Steinberger
46f3efe7ce docs: document harness hook helpers 2026-06-04 00:12:10 -04:00
Peter Steinberger
87b5796649 docs: document agent test fixtures 2026-06-04 00:10:47 -04:00
Peter Steinberger
2bb3132a5c docs: document harness classification helpers 2026-06-04 00:09:28 -04:00
Vincent Koc
54c3f53de5 fix(e2e): fail vanished crabbox sync checkouts 2026-06-04 06:08:04 +02:00
Peter Steinberger
73a81d1d6a docs: document harness helpers 2026-06-04 00:07:49 -04:00
Peter Steinberger
7b02080fa1 docs: document runtime utility helpers 2026-06-04 00:05:49 -04:00
Peter Steinberger
c90f42dbae docs: document dispatch report helpers 2026-06-04 00:04:19 -04:00
Peter Steinberger
32282418eb docs: document schema status helpers 2026-06-03 23:47:47 -04:00
Peter Steinberger
3eaab8632e docs: document mcp plugin helpers 2026-06-03 23:46:22 -04:00
Peter Steinberger
ff43ede887 docs: document cli compat helpers 2026-06-03 23:45:12 -04:00
Peter Steinberger
e4f6dd3440 docs: document agent utility helpers 2026-06-03 23:43:45 -04:00
Peter Steinberger
48557cecff docs: document model tool helpers 2026-06-03 23:41:43 -04:00
Peter Steinberger
6439b64c90 docs: document shared runtime helpers 2026-06-03 23:40:46 -04:00
Peter Steinberger
2fb968a425 docs: document runtime system helpers 2026-06-03 23:39:30 -04:00
Peter Steinberger
ddaa2c5dc8 docs: document status capability helpers 2026-06-03 23:38:32 -04:00
Peter Steinberger
fba1e49083 docs: document live provider probes 2026-06-03 23:37:21 -04:00
Peter Steinberger
059277f83b docs: document provider error helpers 2026-06-03 23:35:55 -04:00
Peter Steinberger
ae8b868342 docs: document oauth test helpers 2026-06-03 23:32:59 -04:00
Peter Steinberger
48d6c75111 docs: document text transform helpers 2026-06-03 23:30:53 -04:00
Mukunda Rao Katta
d966486242 fix(whatsapp): restart channel when a per-account config field changes so disabled accounts are torn down (#87965)
Merged via squash.

Prepared head SHA: 4142d5562e
Co-authored-by: MukundaKatta <99349238+MukundaKatta@users.noreply.github.com>
Co-authored-by: mcaxtr <7562095+mcaxtr@users.noreply.github.com>
Reviewed-by: @mcaxtr
2026-06-04 00:30:29 -03:00
Peter Steinberger
e98b864752 docs: document agent contract helpers 2026-06-03 23:28:53 -04:00
Marcus Castro
88dc177afc fix(auto-reply): count message tool sends as delivery (#90123) 2026-06-04 00:26:45 -03:00
Peter Steinberger
06fee678e1 docs: document runtime timeout helpers 2026-06-03 23:26:32 -04:00
Peter Steinberger
1d2e91e20d docs: document mcp workspace helpers 2026-06-03 23:24:30 -04:00
Peter Steinberger
5169d19ce8 docs: document agent test helpers 2026-06-03 23:21:21 -04:00
Peter Steinberger
86c071035d docs: document provider config helpers 2026-06-03 23:19:46 -04:00
Peter Steinberger
c635716297 docs: document model normalization helpers 2026-06-03 23:17:44 -04:00
Peter Steinberger
0df6292ab3 docs: document live provider helpers 2026-06-03 23:16:10 -04:00
Peter Steinberger
dd555073d0 docs: document provider tool helpers 2026-06-03 23:13:43 -04:00
Vincent Koc
8c74fd4e23 fix(e2e): keep parallels json output parseable 2026-06-04 05:12:13 +02:00
Peter Steinberger
59768909ba docs: document runtime test helpers 2026-06-03 23:11:45 -04:00
Peter Steinberger
1b35d46257 docs: document prompt routing helpers 2026-06-03 23:10:06 -04:00
Peter Steinberger
20e443b965 docs: document model tool utilities 2026-06-03 23:09:20 -04:00
Peter Steinberger
d714803e6d docs: document subagent session helpers 2026-06-03 23:07:58 -04:00
Peter Steinberger
18d036326c docs: document codex context helpers 2026-06-03 23:05:41 -04:00
Peter Steinberger
8b47fa5a76 docs: document runtime utility helpers 2026-06-03 23:02:33 -04:00
Peter Steinberger
e168a82367 docs: document cli runner mcp helpers 2026-06-03 22:59:39 -04:00
Peter Steinberger
83eab79d15 docs: document bootstrap cache helpers 2026-06-03 22:56:27 -04:00
Peter Steinberger
c7a8114f54 docs: document bash process helpers 2026-06-03 22:54:12 -04:00
Peter Steinberger
ef17cecca9 docs: document auth profile store helpers 2026-06-03 22:51:20 -04:00
Peter Steinberger
8835787ed6 docs: document tool hook helpers 2026-06-03 22:47:45 -04:00
Vincent Koc
b12114e45c fix(e2e): abort kitchen sink readiness on gateway exit 2026-06-04 04:46:34 +02:00
Peter Steinberger
32e51f250f docs: document auth redaction helpers 2026-06-03 22:43:51 -04:00
Peter Steinberger
8f7808d1e6 docs: document agent diagnostics helpers 2026-06-03 22:42:05 -04:00
Peter Steinberger
3788a2fd3d docs: document agent steering helpers 2026-06-03 22:40:24 -04:00
Peter Steinberger
b6d6ed34ed docs: document runtime tool helpers 2026-06-03 22:37:59 -04:00
Peter Steinberger
44bcaf00b7 docs: document model auth helpers 2026-06-03 22:36:19 -04:00
Vincent Koc
546aa5770a fix(e2e): report gauntlet log write failures 2026-06-04 04:34:50 +02:00
Peter Steinberger
658f90f845 docs: document subagent model helpers 2026-06-03 22:34:29 -04:00
Peter Steinberger
155260eb04 docs: document model path helpers 2026-06-03 22:33:13 -04:00
Peter Steinberger
7ce1487f33 docs: document auth profile oauth helpers 2026-06-03 22:30:03 -04:00
Peter Steinberger
ac2dbfcfca docs: document auth profile persistence helpers 2026-06-03 22:28:03 -04:00
Vincent Koc
d6ab1fdfe4 test: read codex on-demand auth store from sqlite 2026-06-04 04:26:51 +02:00
Vincent Koc
50c3995894 fix(e2e): fail secret provider startup exits fast 2026-06-04 04:25:53 +02:00
Peter Steinberger
ad958fd97a docs: document auth profile selection helpers 2026-06-03 22:25:34 -04:00
Vincent Koc
0451dcdc56 test(codex): isolate app-server auth fixtures 2026-06-03 19:23:57 -07:00
Peter Steinberger
003bb8546d docs: document auth profile discovery helpers 2026-06-03 22:23:37 -04:00
Peter Steinberger
a2a4924679 docs: document auth profile path state helpers 2026-06-03 22:21:50 -04:00
Peter Steinberger
2ff2ed4099 docs: document agent tool policy helpers 2026-06-03 22:19:38 -04:00
Peter Steinberger
fc5cb461c9 docs: document agent runtime preset helpers 2026-06-03 22:15:52 -04:00
Peter Steinberger
c86eb20dc5 docs: document agent routing state helpers 2026-06-03 22:13:26 -04:00
Peter Steinberger
0d0632d88d docs: document agent runtime utility helpers 2026-06-03 22:11:33 -04:00
Peter Steinberger
2e89655a03 docs: document agent command exec helpers 2026-06-03 22:09:31 -04:00
Peter Steinberger
233666366f docs: document agent command shared helpers 2026-06-03 22:07:59 -04:00
Vincent Koc
474be452a7 test: align dependency override guard with workspace metadata 2026-06-04 04:07:15 +02:00
Peter Steinberger
076178adc6 docs: document agent provider request helpers 2026-06-03 22:06:48 -04:00
Peter Steinberger
eda170f328 docs: document agent inventory recovery helpers 2026-06-03 22:04:42 -04:00
Peter Steinberger
d4867ec20d docs: document agent runtime tool policies 2026-06-03 22:03:35 -04:00
Vincent Koc
d26cef4249 fix(ci): preserve crabbox hydrate pnpm store 2026-06-04 03:59:51 +02:00
Peter Steinberger
e1e095d020 docs: document agent catalog helpers 2026-06-03 21:59:30 -04:00
Vincent Koc
7e5ea598c5 fix(e2e): fail gateway frame waits on socket close 2026-06-04 03:52:52 +02:00
Peter Steinberger
0328f29707 docs: document subagent sqlite registry 2026-06-03 21:51:50 -04:00
Peter Steinberger
9dce23f295 docs: document agent workspace helpers 2026-06-03 21:50:11 -04:00
Peter Steinberger
caa6102144 docs: document agent provider auth helpers 2026-06-03 21:48:41 -04:00
Peter Steinberger
ca3250a3c1 docs: document agent schema and outcome helpers 2026-06-03 21:46:11 -04:00
Vincent Koc
dcfd033746 test: seed auto-reply auth profiles through store 2026-06-04 03:44:04 +02:00
Peter Steinberger
45f4875613 docs: document agent model auth helpers 2026-06-03 21:43:58 -04:00
Peter Steinberger
51e279153f docs: document agent safety helpers 2026-06-03 21:42:04 -04:00
Peter Steinberger
d54addcd28 docs: document agent helper barrels 2026-06-03 21:40:38 -04:00
Peter Steinberger
9eb525de0e docs: document fetch header normalization 2026-06-03 21:38:41 -04:00
Peter Steinberger
d0bf656a3f docs: document session helper APIs 2026-06-03 21:37:49 -04:00
Peter Steinberger
604597d825 docs: document daemon process helpers 2026-06-03 21:35:27 -04:00
Vincent Koc
c286f56167 test: align e2e fixtures with current runtime stores 2026-06-04 03:29:37 +02:00
Vincent Koc
036b730321 fix(scripts): run deadcode knip through pnpm runner 2026-06-04 03:23:45 +02:00
Vincent Koc
deea78da72 perf(gateway): bypass config facade for config handlers 2026-06-04 03:23:45 +02:00
Vincent Koc
eb5d6c7294 perf(gateway): delay provider auth warmup 2026-06-04 03:23:45 +02:00
Vincent Koc
009d7335b5 fix(scripts): run RPC RTT probe without pnpm 2026-06-04 03:23:45 +02:00
Vincent Koc
25f3d2d714 perf(gateway): avoid heavy chat imports in history tests 2026-06-04 03:23:45 +02:00
Peter Steinberger
0416117168 docs: document channel helper APIs 2026-06-03 21:21:02 -04:00
Shakker
4cb34f3999 fix: refresh generated gateway protocol 2026-06-04 02:10:38 +01:00
Shakker
0059f5c24a fix: suppress commands for revision handoff sends 2026-06-04 02:10:38 +01:00
Shakker
4bcae169e2 refactor: centralize chat command interpretation 2026-06-04 02:10:38 +01:00
Shakker
3da05d01a7 fix: namespace chat dedupe by system context 2026-06-04 02:10:38 +01:00
Shakker
f7e44ac6b5 fix: treat Skill Workshop slash drafts as revisions 2026-06-04 02:10:38 +01:00
Shakker
25e3162cce fix: route Skill Workshop revisions through request RPC 2026-06-04 02:10:38 +01:00
Shakker
7150c3c957 fix: separate Skill Workshop revision target agent 2026-06-04 02:10:38 +01:00
Shakker
bf08234ee3 feat: add Skill Workshop revision request 2026-06-04 02:10:38 +01:00
Peter Steinberger
179ff9b423 docs: document plugin registry helper APIs 2026-06-03 21:03:43 -04:00
Peter Steinberger
9b6cd2ea75 docs: document plugin security channel helpers 2026-06-03 21:01:33 -04:00
Vincent Koc
4fbc318e30 ci: stabilize live e2e resource lanes 2026-06-04 02:59:52 +02:00
Brian
7b5f75eb98 Pin official npm plugin install records (#88585)
* fix(plugins): pin official npm install records

* fix(infra): tolerate equivalent plugin install migrations

* fix(plugins): preserve manual exact plugin pins

* fix(infra): remove stale migration imports

* chore: unblock ci guards

* fix: preserve official sync integrity checks

* fix: avoid prerelease integrity carryover

* fix: preserve manual official npm specs

* fix: preserve beta fallback integrity checks

* fix: preserve trusted prerelease fallback integrity

* fix: preserve prerelease-only integrity checks

* fix: pin unchanged official npm records

* fix: allow official compatible fallback updates

* fix: preserve fallback integrity after prerelease resolution

* fix: skip incompatible fallback integrity pins

* fix: preserve pin-only install provenance

* fix: check integrity when repairing missing official pins

---------

Co-authored-by: Lilac <lilac@Lilacs-iMac.local>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-06-03 17:58:09 -07:00
Peter Steinberger
d1fef1d50d docs: document plugin runtime config helpers 2026-06-03 20:54:08 -04:00
Vincent Koc
392af2e612 fix(deps): keep managed overrides in workspace metadata 2026-06-03 17:43:09 -07:00
Vincent Koc
b4234d4028 test: preserve version exports in partial mocks 2026-06-03 17:43:09 -07:00
Peter Steinberger
69c8097dd1 docs: document plugin overlay memory helpers 2026-06-03 20:41:35 -04:00
Val Alexander
60104fe254 fix(workboard): isolate stale lifecycle bulk patches
Follow-up to #89600 for #88592.

- Keep stale lifecycle bulk updates from mutating shared Workboard patch objects.
- Preserve non-status updates while suppressing stale lifecycle status/provenance writes.
- Tighten current-main migrate-hermes test assertions against the canonical auth-profile store shape.

Verification:
- node scripts/run-vitest.mjs ui/src/ui/controllers/workboard.test.ts ui/src/ui/views/workboard.test.ts extensions/workboard/src/store.test.ts extensions/workboard/src/gateway.test.ts --reporter=verbose
- node scripts/run-vitest.mjs --config test/vitest/vitest.ui-e2e.config.ts --configLoader runner ui/src/ui/e2e/workboard-status-persistence.e2e.test.ts ui/src/ui/e2e/workboard.e2e.test.ts --reporter=verbose
- node scripts/run-tsgo.mjs -p test/tsconfig/tsconfig.core.test.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/core-test.tsbuildinfo
- node scripts/run-tsgo.mjs -p test/tsconfig/tsconfig.extensions.test.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/extensions-test.tsbuildinfo
- node scripts/run-vitest.mjs extensions/migrate-hermes/files-and-skills.test.ts extensions/migrate-hermes/secrets.test.ts --reporter=verbose
- corepack pnpm deadcode:unused-files
- git diff --name-only origin/main...HEAD | xargs node scripts/run-oxlint.mjs
- git diff --check origin/main...HEAD
- .agents/skills/autoreview/scripts/autoreview --mode branch --base origin/main --no-web-search
- GitHub CI for 5ee8b3dd5f
2026-06-03 17:40:07 -07:00
Peter Steinberger
fd5dc5bb3a docs: document plugin catalog install helpers 2026-06-03 20:39:16 -04:00
Peter Steinberger
f6aa2c02d1 docs: document plugin runtime load context 2026-06-03 20:36:52 -04:00
Peter Steinberger
7b4d14f786 docs: document plugin runtime helper APIs 2026-06-03 20:34:17 -04:00
Vincent Koc
822ebb4c94 test(e2e): read onboard auth profiles from sqlite 2026-06-04 02:33:56 +02:00
Peter Steinberger
58f7d7e5f8 docs: document plugin scope state helpers 2026-06-03 20:30:38 -04:00
Peter Steinberger
0ad13b714e docs: document plugin manifest install helpers 2026-06-03 20:28:45 -04:00
Peter Steinberger
bb8192ff7c docs: document plugin hook provider helpers 2026-06-03 20:26:18 -04:00
Peter Steinberger
c5d52bf2a7 docs: document plugin runtime utilities 2026-06-03 20:22:56 -04:00
Peter Steinberger
06ad1d0d74 docs: document plugin public artifact helpers 2026-06-03 20:21:16 -04:00
Peter Steinberger
27b1d05a1d docs: document plugin runtime status helpers 2026-06-03 20:19:17 -04:00
Peter Steinberger
02c6630f11 docs: document plugin install helpers 2026-06-03 20:17:55 -04:00
Peter Steinberger
c821ef274b docs: document plugin test helpers 2026-06-03 20:16:51 -04:00
Vincent Koc
6d84fb35c7 test(plugins): read migrated auth profiles from sqlite store 2026-06-04 02:15:52 +02:00
Peter Steinberger
335f045393 docs: document plugin manifest helpers 2026-06-03 20:15:37 -04:00
Peter Steinberger
371777ad14 docs: document plugin runtime loaders 2026-06-03 20:13:51 -04:00
Peter Steinberger
ca7c2714f6 docs: document plugin auth runtime helpers 2026-06-03 20:12:42 -04:00
Peter Steinberger
b1d434b666 docs: document plugin provider helpers 2026-06-03 20:11:25 -04:00
Vincent Koc
a7f442ffd8 test(infra): follow active npm override pins 2026-06-04 02:07:49 +02:00
Peter Steinberger
bbff951880 docs: document plugin index policy helpers 2026-06-03 20:04:38 -04:00
Peter Steinberger
9a4d28695b docs: document plugin host helpers 2026-06-03 20:01:47 -04:00
Vincent Koc
96136e6d71 fix(plugins): align auth profile tests with sqlite store 2026-06-04 02:00:35 +02:00
Peter Steinberger
e993e1c334 docs: document auto-reply final helpers 2026-06-03 19:57:58 -04:00
Vincent Koc
99e627b283 fix(deps): align root override metadata 2026-06-03 16:54:52 -07:00
Vincent Koc
edc9be1b7f test(release): read auth refs from sqlite store 2026-06-03 16:54:52 -07:00
Peter Steinberger
01d69041a2 docs: document auto-reply session runtime helpers 2026-06-03 19:54:16 -04:00
Peter Steinberger
6baa5ca5b1 docs: document auto-reply runtime facades 2026-06-03 19:51:54 -04:00
joshavant
d5d3e9983e fix: harden mobile a2ui bridge trust 2026-06-03 16:50:08 -07:00
Peter Steinberger
0b6fff44f5 docs: document auto-reply policy helpers 2026-06-03 19:48:48 -04:00
Val Alexander
e07dbb27d9 Fix Workboard status persistence
Summary:
- Persist Workboard lifecycle status provenance so stale linked session/task lifecycle updates cannot overwrite newer manual or non-default creation status.
- Add focused Workboard store/controller regressions for lifecycle-vs-manual precedence and creation-status precedence.
- Add mocked Control UI browser E2E proof for create/edit/reopen, running move, lifecycle sync, reload persistence, and read-only operator behavior.

Verification:
- `node scripts/run-vitest.mjs extensions/workboard/src/store.test.ts extensions/workboard/src/gateway.test.ts --reporter=verbose`
- `node scripts/run-vitest.mjs ui/src/ui/controllers/workboard.test.ts ui/src/ui/views/workboard.test.ts --reporter=verbose`
- `node scripts/run-vitest.mjs --config test/vitest/vitest.ui-e2e.config.ts --configLoader runner ui/src/ui/e2e/workboard-status-persistence.e2e.test.ts ui/src/ui/e2e/workboard.e2e.test.ts --reporter=verbose`
- `corepack pnpm tsgo:core:test`
- `corepack pnpm tsgo:extensions:test`
- `node scripts/run-oxlint.mjs extensions/workboard/src/sqlite-store.ts extensions/workboard/src/store.test.ts extensions/workboard/src/store.ts extensions/workboard/src/types.ts ui/src/ui/controllers/workboard.test.ts ui/src/ui/controllers/workboard.ts ui/src/ui/e2e/workboard-status-persistence.e2e.test.ts ui/src/ui/e2e/workboard.e2e.test.ts ui/src/ui/views/workboard.test.ts ui/src/ui/views/workboard.ts`
- `git diff --check`
- `.agents/skills/autoreview/scripts/autoreview --mode branch --base origin/main` clean
- GitHub PR checks green on head `6d05d6edd5ca6cbb2e625f3e478e973feba5e4cf`

Proof:
- E2E manifest: `/Users/buns/.codex/worktrees/74e7/openclaw/.artifacts/control-ui-e2e/workboard/manifest.json`
- Live Gateway success proof: `/Users/buns/.codex/worktrees/74e7/openclaw/.artifacts/live-workboard/proof/12-live-review-success.png`
- Remaining gap: read-only operator behavior is covered by mocked browser E2E, not live Gateway.
2026-06-03 16:46:14 -07:00
Peter Steinberger
d9d4514c00 docs: document auto-reply directive helpers 2026-06-03 19:31:07 -04:00
Peter Steinberger
05d92d8761 docs: document auto-reply queue exec helpers 2026-06-03 19:29:12 -04:00
Peter Steinberger
90b1ab1c70 docs: document auto-reply block helpers 2026-06-03 19:25:58 -04:00
Peter Steinberger
93917413de docs: document auto-reply dispatch helpers 2026-06-03 19:23:08 -04:00
Peter Steinberger
9a1e896c96 docs: document auto-reply queue helpers 2026-06-03 19:21:12 -04:00
Josh Lehman
208fec6ddc docs: clarify legacy openai-codex auth (#90028) 2026-06-03 16:18:51 -07:00
Peter Steinberger
6d4d313d44 docs: document auto-reply runtime helpers 2026-06-03 19:16:37 -04:00
Peter Steinberger
8129fc0f3a docs: document auto-reply top-level helpers 2026-06-03 19:14:43 -04:00
Peter Steinberger
e16ac04330 refactor(auth): store auth profiles in sqlite (#89102) 2026-06-03 16:14:15 -07:00
Peter Steinberger
116bc2a0f0 docs: surface Windows Hub across docs 2026-06-03 16:09:24 -07:00
Peter Steinberger
1878ca0820 chore(release): prepare 2026.6.2 beta 2026-06-04 00:06:52 +01:00
B.K.
c96a12d3c8 fix(update): surface plugin channel fallbacks (#81422)
* fix: surface plugin update channel fallbacks

* fix: clarify dry-run plugin fallback output

* fix: preserve failed plugin fallback metadata

* chore: mark compatibility aliases deprecated

* chore: fix channel runtime lint directive

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-06-03 16:00:40 -07:00
Vincent Koc
28a2e795da test(canvas): pin a2ui compatibility assets 2026-06-04 00:58:33 +02:00
Peter Steinberger
46f015a627 docs: document gateway server methods 2026-06-03 18:55:58 -04:00
Vincent Koc
1fde4ae3b1 fix(test): keep explicit ui tests scoped 2026-06-04 00:53:18 +02:00
Peter Steinberger
3ad7049cba docs: document gateway general helpers 2026-06-03 18:53:08 -04:00
Peter Steinberger
190fd034d5 docs: refresh Windows Hub platform guide 2026-06-03 15:52:00 -07:00
Peter Steinberger
59366ca420 docs: document gateway utility helpers 2026-06-03 18:48:32 -04:00
Shakker
0771a8ab6f fix: restore Skill Workshop current chat toggle 2026-06-03 23:46:43 +01:00
Shakker
03c730c48f test: cover awaited chat session switching 2026-06-03 23:46:43 +01:00
Shakker
8c91980123 refactor: add awaitable chat session switch 2026-06-03 23:46:43 +01:00
Vincent Koc
8fe67e4b70 test(canvas): verify a2ui compatibility assets 2026-06-04 00:45:34 +02:00
Peter Steinberger
a53a545542 docs: document remaining plugin sdk helpers 2026-06-03 18:45:11 -04:00
Vincent Koc
41e56d56fc fix(plugin-sdk): stabilize API baseline source paths 2026-06-03 15:44:08 -07:00
Peter Steinberger
381c5e0762 docs: document remaining cli helpers 2026-06-03 18:43:23 -04:00
Peter Steinberger
e254346bc2 chore(release): prepare 2026.6.3 beta 2026-06-03 23:42:34 +01:00
Ted Li
308114e148 fix(browser): honor Chrome MCP tab timeouts
Fixes #88213.

Browser tab routes now use the configured action timeout for Chrome MCP existing-session reachability checks instead of failing through the old 300ms probe. Non-Chrome-MCP profiles keep the short probe, and configured timeout values are clamped to the safe timer range.

Proof: autoreview clean; node scripts/run-vitest.mjs extensions/browser/src/browser/routes/tabs.test.ts; merge-base git diff --check; PR CI green.

Co-authored-by: Ted Li <tl2493@columbia.edu>
2026-06-03 15:36:09 -07:00
Kevin Lin
fce002ad03 revert(codex): revert first-party marketplace allowlist
Reverts openclaw/openclaw#82219.
2026-06-03 15:35:35 -07:00
Ted Li
d5c8e90e28 fix(agents): repair context-engine tool-result pairing
Context-engine assembly could replace an already-repaired attempt transcript with a view containing orphaned tool results. Reuse the attempt-level tool-use/tool-result pairing repair after initial assembly and ownsCompaction loop assembly so strict OpenAI-compatible replay does not emit free-floating tool messages.

Fixes #88561.

Verification:
- .agents/skills/autoreview/scripts/autoreview --mode branch --base origin/main
- git diff --check origin/main...HEAD
- PR CI: https://github.com/openclaw/openclaw/actions/runs/26766745554
- Real behavior proof: https://github.com/openclaw/openclaw/actions/runs/26767235053

Co-authored-by: Ted Li <tl2493@columbia.edu>
2026-06-03 15:34:27 -07:00
Vincent Koc
2ecd1d3299 docs(changelog): restore 2026.6.2 package notes 2026-06-04 00:33:30 +02:00
scotthuang
7f1a920a89 fix: keep stream-to-parent ACP spawns registered
Keep ACP sessions_spawn runs that request streamTo=parent in the subagent registry so completion handoffs can announce back to users while inline delivery suppression still prevents double delivery.

Fixes stream-to-parent child completions dropping in direct-session channels.

Thanks @scotthuang.
2026-06-03 15:32:22 -07:00
Glucksberg
32acef01b3 fix(agents): classify read-only shell commands as non-mutating
Classify clearly read-only exec/bash shell probes as non-mutating so failed inspection commands no longer add misleading final tool-warning messages after a useful assistant reply. Ambiguous or mutating shell forms still fail closed as mutating, including redirects, pipes, heredocs, mutating git/gh forms, and gh web-launch flags.

Verification:
- pnpm test src/agents/tool-mutation.test.ts
- pnpm test src/agents/embedded-agent-runner/run/payloads.test.ts src/agents/embedded-agent-runner/run/payloads.errors.test.ts
- CI/check rollup for head 346853fb07 had no pending or failing checks; historical cancelled/skipped Mantis proof jobs were non-blocking.

Co-authored-by: Markus <markuscontasul@gmail.com>
2026-06-03 15:28:39 -07:00
Peter Steinberger
95045b1d5b docs: document cli gateway plugin helpers 2026-06-03 18:25:21 -04:00
Peter Steinberger
4f4cd2e8ae docs: document cli shared helpers 2026-06-03 18:23:39 -04:00
Peter Steinberger
90b8b41c41 docs(changelog): expand 2026.6.3 release notes 2026-06-03 23:21:43 +01:00
Peter Steinberger
0b8aabe864 docs: document auth profile failure policy contract (#89613)
* docs: document markdown marker renderer

* docs: document rendered markdown chunking

* docs: document markdown text chunking

* docs: document shared text chunking

* docs: document plugin text chunking exports

* docs: document avatar policy constants

* docs: document node match candidates

* docs: document scoped expiring id cache

* docs: document runtime import normalization

* docs: document string sample summaries

* docs: document session usage timeseries types

* docs: document session usage response types

* docs: document manifest frontmatter shapes

* docs: document channel route input metadata

* docs: document pair loop guard settings

* docs: document migration config patch helpers

* docs: document api provider registry

* docs: document tool call repair payloads

* docs: document plugin tool payload helpers

* docs: document lazy promise loader

* docs: document store writer queue state

* docs: document thread binding lifecycle

* docs: document concurrency helper contract

* docs: document gateway client info contract

* docs: document delivery context contracts

* docs: document secret ref defaults contract

* docs: document command gating contract

* docs: document avatar policy contract

* docs: document node match policy

* docs: document message channel normalization

* docs: document boolean parsing contract

* docs: document zod parse helpers

* docs: document direct dm guard policy

* docs: document fixed window limiter contract

* docs: document node presence event contract

* docs: document secret normalization contract

* docs: document progress draft line removal

* docs: document usage formatting contracts

* docs: document agent run status contract

* docs: document runtime import helpers

* docs: document provider utility ownership

* docs: document invalid config helpers

* docs: document json compat parser

* docs: document channel config metadata ownership

* docs: document channel logging helpers

* docs: document sender identity validation ownership

* docs: document string sampling helper

* docs: document global singleton helpers

* docs: document transcript tool helpers

* docs: document exec safe-bin normalization

* docs: document reaction level resolver

* docs: document account snapshot redaction boundary

* docs: document messaging target helpers

* docs: document thread binding messages

* docs: document conversation binding context

* docs: document conversation resolution helper

* docs: document owner display secret retention

* docs: document provider request config types

* docs: document skills config types

* docs: document memory config types

* docs: document imessage config types

* docs: document crestodian config types

* docs: document tools config policies

* docs: document shared config base types

* docs: document channel config contracts

* docs: document openclaw config state types

* docs: document model config contracts

* docs: document shared agent config types

* docs: document agent defaults config types

* docs: document secret input contracts

* docs: document auth config contracts

* docs: document gateway config contracts

* docs: document tool call stream repair contracts

* docs: document memory host facades

* docs: document llm core contracts

* docs: document markdown core contracts

* docs: document gateway connect error contracts

* docs: document gateway protocol primitives

* docs: document gateway frame schemas

* docs: document gateway device schemas

* docs: document gateway environment schemas

* docs: document gateway push schemas

* docs: document gateway plugin schemas

* docs: document gateway artifact schemas

* docs: document gateway command schemas

* docs: document gateway task schemas

* docs: document gateway exec approval schemas

* docs: document gateway secret schemas

* docs: document gateway config schemas

* docs: document gateway snapshot schemas

* docs: document gateway chat schemas

* docs: document gateway wizard schemas

* docs: document gateway node schemas

* docs: document gateway plugin approval schemas

* docs: document gateway talk schemas

* docs: document gateway agent schemas

* docs: document gateway session schemas

* docs: document gateway cron schemas

* docs: document gateway agent model skill schemas

* docs: document gateway skill proposal tool schemas

* docs: document gateway protocol registry

* docs: document gateway channel status schemas

* docs: document gateway schema regression tests

* docs: document gateway schema barrel

* docs: document gateway validator tests

* docs: document gateway primitive push tests

* docs: document gateway contract tests

* docs: document native protocol guard

* docs: document channel schema tests

* docs: document gateway protocol smoke tests

* docs: document gateway protocol entrypoint

* docs: document gateway protocol type exports

* docs: document gateway error codes

* docs: document protocol schema registry

* docs: document talk audio codec

* docs: document talk activation names

* docs: document talk consult questions

* docs: document talk consult tool

* docs: document talk run control contracts

* docs: document talk run control adapter

* docs: document talkback consult queue

* docs: document talk consult transcript guard

* docs: document talk fast context runtime

* docs: document forced talk consult coordinator

* docs: document talk output activity tracker

* docs: document talk event metrics

* docs: document talk diagnostics

* docs: document talk observability hook

* docs: document talk provider resolver

* docs: document talk provider registry

* docs: document talk runtime primitives

* docs: document talk consult controller logs

* docs: document channel identity helpers

* docs: document channel account allowlist helpers

* docs: document channel metadata draft controls

* docs: document channel ingress policy

* docs: document channel sender access gates

* docs: document channel catalog message contracts

* docs: document channel account plugin helpers

* docs: document configured binding helpers

* docs: document channel acp approval config helpers

* docs: document channel bundled config write helpers

* docs: document channel plugin utility contracts

* docs: document channel config access helpers

* docs: document channel message action helpers

* docs: document channel outbound runtime helpers

* docs: document channel pairing promotion helpers

* docs: document channel registry helpers

* docs: document channel setup wizard helpers

* docs: document channel lifecycle status helpers

* docs: document channel target thread helpers

* docs: document channel session binding helpers

* docs: document channel package module probes

* docs: document channel setup wizard contracts

* docs: document channel plugin API barrels

* docs: document channel contract test helpers

* docs: document channel core helpers

* docs: document small core facades

* docs: document provider runtime helpers

* docs: document persistence and realtime helpers

* docs: document mcp and state helpers

* docs: document tool planner contracts

* docs: document music generation runtime

* docs: document crestodian command flow

* docs: document utility helpers

* docs: document node host helpers

* docs: document transcript contracts

* docs: document trajectory export contracts

* docs: document image generation contracts

* docs: document routing helper contracts

* docs: document session helper contracts

* docs: document video generation contracts

* docs: document model catalog contracts

* docs: document proxy capture contracts

* docs: document status rendering contracts

* docs: document test helper contracts

* docs: document wizard setup contracts

* docs: document process contracts

* docs: document memory host sdk contracts

* docs: document tts contracts

* docs: document secrets runtime contracts

* docs: document shared helper contracts

* docs: document hook runtime contracts

* docs: document security audit contracts

* docs: document flow contracts

* docs: document media understanding contracts

* docs: document tui contracts

* docs: document logging contracts

* docs: document llm contracts

* docs: document cron contracts

* docs: document daemon contracts

* docs: document task contracts

* docs: document acp contracts

* docs: document test utility contracts

* docs: document skill contracts

* docs: document config contracts

* docs: document outbound infra contracts

* docs: document command analysis contracts

* docs: document provider usage infra contracts

* docs: document file safety infra contracts

* docs: document exec approval infra contracts

* docs: document gateway runtime infra contracts

* docs: document infra utility contracts

* docs: document infra queue storage contracts

* docs: document heartbeat infra contracts

* docs: document remaining infra contracts

* docs: document gateway auth contracts

* docs: document gateway display helpers

* docs: document gateway http helpers

* docs: document gateway node helpers

* docs: document gateway mcp helpers

* docs: document gateway support helpers

* docs: document gateway server runtime helpers

* docs: document gateway runtime bootstrap helpers

* docs: document gateway session events

* docs: document gateway utility helpers

* docs: document gateway talk helpers

* docs: document gateway helper contracts

* docs: document gateway server method helpers

* docs: document gateway server auth helpers

* docs: document gateway server tests

* docs: document gateway test helpers

* docs: document gateway node tests

* docs: document gateway channel tests

* docs: document gateway session tests

* docs: document gateway server startup tests

* docs: document gateway tool test helpers

* docs: document gateway server test helpers

* docs: document gateway server method tests

* docs: document remaining gateway tests

* docs: document plugin sdk public subpaths

* docs: document plugin sdk runtime helpers

* docs: document plugin sdk memory provider helpers

* docs: document plugin sdk runtime facades

* docs: document plugin sdk command approval helpers

* docs: document plugin sdk runtime types

* docs: document plugin sdk browser account helpers

* docs: document plugin sdk media memory helpers

* docs: document plugin sdk core tests

* docs: document plugin sdk contract helpers

* docs: document plugin sdk test helpers

* docs: document remaining plugin sdk tests

* docs: document cli utility helpers

* docs: document cli runtime helpers

* docs: document cli command registration helpers

* docs: document node cli helpers

* docs: document cli program registration

* docs: document message cli registration

* docs: document daemon cli helpers

* docs: document cli route parsers
2026-06-03 15:20:39 -07:00
Peter Steinberger
8de37e1ce4 docs(changelog): normalize 2026.6.3 unreleased notes 2026-06-03 23:02:01 +01:00
Peter Steinberger
20fa8a92a7 docs(changelog): add 2026.6.3 notes 2026-06-03 23:01:03 +01:00
joshavant
10830bc4a7 docs: warn about install policy plugin update footguns 2026-06-03 14:51:45 -07:00
Vincent Koc
2b31ad2ee5 docs(plugin-sdk): refresh API baseline hash 2026-06-03 14:48:00 -07:00
Peter Steinberger
ed283490b5 docs(release): require verified Windows asset links 2026-06-03 22:42:53 +01:00
Peter Steinberger
bf368e7609 ci(release): promote Windows node installers 2026-06-03 22:39:58 +01:00
Vincent Koc
0756680421 docs(changelog): add 2026.6.2 notes 2026-06-03 14:33:40 -07:00
Peter Steinberger
639ff98509 chore(release): update appcast for 2026.6.1
Updates the stable Sparkle appcast generated by macOS publish for v2026.6.1.
2026-06-03 14:30:15 -07:00
Vincent Koc
0f05aff312 docs(config): refresh channel config baseline hash 2026-06-03 14:27:38 -07:00
Peter Steinberger
e9379ef22b docs(release): document GitHub release note edits 2026-06-03 22:25:43 +01:00
Peter Steinberger
b411c53248 docs(changelog): publish 2026.6.1 notes on main 2026-06-03 22:18:53 +01:00
Josh Avant
154f439c81 Add operator install policy and remove dangerous-code install scanners (#89516)
* feat: add operator install policy

* test: cover plain-file plugin install code

* fix: preserve locationless install policy findings

* refactor: remove install-time plugin scanner

* test: remove stale plugin install helper

* fix: preserve before-install builtin scan type

* fix: preserve plugin dependency denylist

---------

Co-authored-by: Mainframe <mainframe@MainfraacStudio.localdomain>
2026-06-03 14:17:29 -07:00
Vincent Koc
7b82901e58 ci(crabbox): keep hydrated modules under node_modules 2026-06-03 13:50:10 -07:00
Vincent Koc
932034f1fc test(live): keep cache prereq skips provider-aware 2026-06-03 13:45:27 -07:00
Vincent Koc
a0717ef61c fix(testing): speed channel contract loading 2026-06-03 21:14:05 +02:00
Dallin Romney
f0237caf27 fix(memory): warn after startup watcher pressure check (#89244)
* fix(config): warn only for large memory watch paths

* fix(config): align memory watch warning scan roots

* fix(config): import memory search config type

* fix(config): align memory watch warning sources

* fix(config): match memory source fallback

* fix(memory): warn from runtime watcher pressure

* fix(memory): warn after watcher fallback pressure

* fix(memory): keep watcher pressure warning startup scoped

* fix(memory): delay startup watcher pressure sample

* chore(memory): clarify watcher pressure comments

* test(memory): remove redundant watcher assertion

* refactor(memory): share watcher pressure warning policy

* refactor(memory): trim watcher pressure duplication

* refactor(memory): flatten watcher pressure calls

* docs(readme): use contributor-facing community wording

* Revert "docs(readme): use contributor-facing community wording"

This reverts commit 796bc03913.
2026-06-03 11:57:35 -07:00
Vincent Koc
892602eaba test(live): harden ARM live harness defaults 2026-06-03 11:27:21 -07:00
Dallin Romney
79a8dec44d test(channels): fix guardrail regex lint (#89960) 2026-06-03 11:01:28 -07:00
Vincent Koc
7098e335bf fix(models): recover bundled provider aliases 2026-06-03 19:38:54 +02:00
Vincent Koc
6d5061c234 fix(testing): harden bundled channel contract loading 2026-06-03 19:22:57 +02:00
Vincent Koc
286e5ffe07 test(startup): make cli startup budgets arch-aware 2026-06-03 09:50:04 -07:00
Vincent Koc
158c4d7540 fix(discord): match libopus error shape 2026-06-03 18:37:18 +02:00
Vincent Koc
344e04b5d5 fix(testing): route source targets through test planner 2026-06-03 18:07:32 +02:00
Vincent Koc
ec47d1cdd5 fix(canvas): restore A2UI compatibility assets 2026-06-03 17:31:15 +02:00
Vincent Koc
8c89d35a8a fix(gateway): cancel stop terminate fallback 2026-06-03 17:28:00 +02:00
Vincent Koc
d358294f89 test(plugins): anchor provider family inventory to source roots 2026-06-03 17:20:10 +02:00
Vincent Koc
3480832614 test(ui): defer control ui vite import 2026-06-03 08:13:30 -07:00
Vincent Koc
e0ab71d3dc fix(scripts): guard codex protocol generation disk headroom 2026-06-03 17:01:16 +02:00
Vincent Koc
21b262f507 fix(e2e): fail timed rpc commands 2026-06-03 16:48:50 +02:00
Vincent Koc
3a64302585 test(canvas): cover A2UI static asset compatibility 2026-06-03 16:42:55 +02:00
Vincent Koc
38f1db6d67 fix(e2e): rethrow lifecycle shutdown promptly 2026-06-03 16:36:37 +02:00
Vincent Koc
8f6f2617ec test(vitest): extend full agentic watchdog 2026-06-03 07:35:39 -07:00
Vincent Koc
f4868b79e3 fix(testing): keep plugin gauntlet pnpm noninteractive 2026-06-03 16:34:54 +02:00
Vincent Koc
d3ab7e92ef fix(ci): harden ARM smoke and browser checks 2026-06-03 07:30:12 -07:00
Vincent Koc
acacd32415 test(codex): cover bad dynamic tool schemas 2026-06-03 16:20:49 +02:00
Ayaan Zaidi
0b26a1bca7 fix(telegram): cancel clean restart stop timers 2026-06-03 19:49:12 +05:30
Ayaan Zaidi
0bcdb9c0d1 refactor(telegram): distill polling restart stops 2026-06-03 19:49:12 +05:30
Andy Ye
946eed685d fix(telegram): slow polling restart storms 2026-06-03 19:49:12 +05:30
Vincent Koc
c219c62598 refactor(gateway): share duplicated test helpers
Consolidate repeated gateway test setup into shared helpers and keep the preauth WebSocket fixture bounded with maxPayload.\n\nVerification: focused gateway Vitest passed, autoreview clean, and ready-state GitHub Actions CI passed on c6f6957e55.
2026-06-03 06:57:18 -07:00
Pavan Kumar Gondhi
5483ff705f fix(telegram): require admin for target writeback [AI] (#88973)
* fix: require admin for Telegram target writeback

* fix(telegram): preserve internal target writeback

* fix: scope Telegram target writeback authority

* fix: infer internal writeback for durable sends

* fix: preserve scoped Telegram writeback boundaries

* fix: preserve direct Telegram writeback

* test: make Telegram writeback scope intent explicit

* fix(telegram): keep target writeback authority local

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-06-03 19:25:40 +05:30
Vincent Koc
70a989a97a test(e2e): tighten onboard status assertions 2026-06-03 15:54:03 +02:00
Vincent Koc
b7450f83a1 ci(docker): disable alpha image publishes 2026-06-03 06:46:42 -07:00
Vincent Koc
ff5667a582 fix(installer): fail on onboarding exit code 2026-06-03 15:39:31 +02:00
Vincent Koc
d6bea4c5ac fix(e2e): clean clawhub install temp home 2026-06-03 15:30:02 +02:00
clawsweeper[bot]
79896a24d9 fix(outbound): keep channel send durable when transcript mirror fails (#89626) (#89812)
Summary:
- The PR wraps outbound post-delivery transcript mirroring in warning-only error handling and adds regression tests for thrown and not-ok mirror append failures.
- PR surface: Source +16, Tests +61. Total +77 across 2 files.
- Reproducibility: yes. A high-confidence source reproduction is to make appendAssistantMessageToSessionTransc ... a/outbound/deliver.ts:1970 and the caller retry path treats that exception as a failed direct announcement.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(outbound): keep channel send durable when transcript mirror fails…

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

Prepared head SHA: dfe0fd7119
Review: https://github.com/openclaw/openclaw/pull/89812#issuecomment-4611974387

Co-authored-by: harjoth <harjoth.khara@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: takhoffman
Co-authored-by: takhoffman <781889+takhoffman@users.noreply.github.com>
2026-06-03 13:20:52 +00:00
Vincent Koc
a7d5ae1872 fix(scripts): force stop memory fd gateway child 2026-06-03 15:19:29 +02:00
Vincent Koc
446a2b24c3 fix(e2e): require kitchen sink command rss samples 2026-06-03 15:11:40 +02:00
jmao
e4993ec00f fix(telegram): prevent preview duplication in partial and block streaming modes
Fix Telegram streamed replies so preview chunks are finalized once in partial and block streaming modes.

Fixes #87624. Thanks @jmao0001.
2026-06-03 18:36:08 +05:30
Vincent Koc
90493ee8e2 fix(scripts): stop rpc rtt process groups 2026-06-03 15:03:32 +02:00
zhang-guiping
60dcaa3cf5 fix #88773: [Bug]: Telegram DM exec requires approval despite allowlist + ask:off — works in webchat, not in Telegram (#89035)
* fix exec ask policy source

* fix gateway test type fixtures

* docs: update exec ask parameter docs to match runtime behavior

* fix: preserve trusted per-call exec ask hardening while blocking model-supplied overrides for channel runs

* docs: align exec ask contract with runtime

* refactor(agents): simplify exec ask policy cleanup

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>
2026-06-03 18:33:08 +05:30
Ayaan Zaidi
b3b203bf67 test(telegram): simplify preview race harness 2026-06-03 18:28:13 +05:30
张贵萍0668001030
0a4927d0b8 fix(telegram): retain preview on generation race 2026-06-03 18:28:13 +05:30
clawsweeper[bot]
a61c94b1f1 fix(feishu): wire setup runtime setter (#89814)
Summary:
- The PR adds a narrow Feishu runtime-setter entrypoint, wires it into the Feishu setup entry, and adds regression coverage for setup-only runtime registration.
- PR surface: Source +7, Tests +22. Total +29 across 4 files.
- Reproducibility: yes. source inspection gives a high-confidence reproduction path: current Feishu setup-only ... ate when that setter is present. I did not run a live Feishu tenant message repro in this read-only review.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(feishu): wire setup runtime setter

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

Prepared head SHA: befd074ca6
Review: https://github.com/openclaw/openclaw/pull/89814#issuecomment-4612032021

Co-authored-by: Glenn-Agent <glenn_agent@163.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: takhoffman
Co-authored-by: takhoffman <781889+takhoffman@users.noreply.github.com>
2026-06-03 12:36:42 +00:00
Vincent Koc
a9f099d279 test(qa): require channel scenario markers 2026-06-03 14:27:25 +02:00
Vincent Koc
2fa60af960 test(vitest): make channel helper config runnable 2026-06-03 05:23:44 -07:00
clawsweeper[bot]
07006943de fix(telegram): isolate verbose status after streamed finals (#89813)
Summary:
- The branch updates Telegram dispatch so a verbose/status final arriving after a streamed final answer uses a fresh answer-lane message, with default and progress-mode regression tests.
- PR surface: Source +14, Tests +52. Total +66 across 2 files.
- Reproducibility: yes. The linked bug report gives a concrete Telegram `/reset`, `/v on`, short-prompt path, and source inspection shows current main can route a second final payload through the finalized answer lane.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(telegram): isolate verbose status after streamed finals

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

Prepared head SHA: 4d476a957f
Review: https://github.com/openclaw/openclaw/pull/89813#issuecomment-4612006920

Co-authored-by: kesslerio <martin@kessler.io>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: takhoffman
Co-authored-by: takhoffman <781889+takhoffman@users.noreply.github.com>
2026-06-03 12:21:08 +00:00
Vincent Koc
9dc1694eb7 test: lengthen ARM contracts shard watchdog 2026-06-03 05:05:35 -07:00
Vincent Koc
98ff56d70e perf(ui): trace chat send server milestones
Add operator-only Control UI chat send timing milestones across gateway dispatch, model selection, agent-run start, dispatch completion, and post-dispatch completion. The Control UI records these server phases into the existing chat send timing buffer, and the gateway broadcast guard now scopes the new timing event with other read-visible chat events.
2026-06-03 05:02:06 -07:00
Vincent Koc
03ccdb9fbc test(e2e): assert mcp reconnect temp state 2026-06-03 13:59:34 +02:00
Vincent Koc
6d7b80fa1c test(gateway): shard default gateway vitest config 2026-06-03 04:57:27 -07:00
clawsweeper[bot]
409d1a7135 fix(agents): release session write lock if fence read throws on prompt release (#89811)
Summary:
- The PR makes prompt-release fence bookkeeping exception-safe so the session write lock is released even when fence reads throw, and adds a regression test for that path.
- PR surface: Source +6, Tests +27. Total +33 across 2 files.
- Reproducibility: yes. source-reproducible with provided real-output proof: current main clears `heldLock` be ... ire timing out after an injected `EIO`. I did not run the harness locally because this review is read-only.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(agents): release session write lock if fence read throws on promp…

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

Prepared head SHA: 394d978437
Review: https://github.com/openclaw/openclaw/pull/89811#issuecomment-4611966479

Co-authored-by: Spencer Fuller <spencer.p.fuller@gmail.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: takhoffman
Co-authored-by: takhoffman <781889+takhoffman@users.noreply.github.com>
2026-06-03 11:51:43 +00:00
Vincent Koc
d31f4e2d62 fix(e2e): stop interrupted docker builds 2026-06-03 13:48:31 +02:00
Ayaan Zaidi
e5e6cf04a2 fix(android): hide nav under command palette 2026-06-03 17:02:10 +05:30
Ayaan Zaidi
4f8740029a refactor(android): distill companion shell cleanup 2026-06-03 17:02:10 +05:30
Tosko4
9159b3bf8e Improve Android companion-first shell UX 2026-06-03 17:02:10 +05:30
Vincent Koc
eddf1c776d test(e2e): require kitchen sink tool coverage 2026-06-03 13:25:50 +02:00
Vincent Koc
6ec579a0c2 docs(web): document chat ack timing metadata (#89802) 2026-06-03 04:18:51 -07:00
Vincent Koc
87eaac4010 fix(e2e): bound image auth mock bodies 2026-06-03 13:15:51 +02:00
Val Alexander
529282dcff fix(ui): harden Workboard dialog accessibility
Harden Workboard modal and drawer accessibility.

Summary:
- Add Workboard dialog focus lifecycle handling for initial focus, Tab/Shift+Tab containment, Escape close, and opener restore.
- Mark Workboard background content inert/aria-hidden while modal or drawer dialogs are active.
- Add focused unit and Chromium browser smoke coverage for the audited modal/drawer accessibility requirements.
- Keep UI browser test aliases able to resolve shared workspace packages used by the Workboard view.

Verification:
- node scripts/run-vitest.mjs ui/src/ui/views/workboard.test.ts
- node scripts/run-vitest.mjs ui/src/ui/views/workboard.browser.test.ts
- (cd ui && pnpm exec vitest run --config vitest.config.ts --project browser src/ui/views/workboard.browser.test.ts)
- GitHub checks green at 6557012430
2026-06-03 06:14:40 -05:00
Vincent Koc
b1fccd0605 perf(ui): surface chat ack server timing (#89801) 2026-06-03 04:11:14 -07:00
Vincent Koc
287dee4593 fix(e2e): settle credential shutdown promptly 2026-06-03 13:01:58 +02:00
Vincent Koc
b96c0d932f test(codex): stabilize app-server startup races 2026-06-03 03:48:45 -07:00
Vincent Koc
a46181f168 test: stabilize timing-sensitive ARM suites 2026-06-03 03:47:48 -07:00
Vincent Koc
1b5cb4a0d3 fix(e2e): bound clickclack fixture bodies 2026-06-03 12:45:59 +02:00
Vincent Koc
9947a26768 test(ui): cover control chat send timing phases 2026-06-03 03:44:44 -07:00
Vincent Koc
2accf3875b test(e2e): assert channel credential fields 2026-06-03 12:25:14 +02:00
Vincent Koc
76c8b36031 fix(e2e): stop tracked process groups 2026-06-03 12:17:05 +02:00
Vincent Koc
44fea3c94a fix(tooling): cancel oversized audit responses 2026-06-03 12:05:39 +02:00
Vincent Koc
c68938c19e perf(gateway): overlap chat catalog startup
Start optional model catalog loading earlier during chat history/startup hydration so catalog discovery overlaps history projection without changing the metadata contract. The response still awaits catalog-backed session/default/agents metadata before replying.

Verification:
- git diff --check
- autoreview local caught and rejected the short-timeout variant; fixed to overlap-only
- autoreview commit clean
- Testbox tbx_01kt6edf5d328vqr43epy0cs0b targeted gateway/UI shards passed
- Testbox tbx_01kt6eh4fk409g4ar1kpa0edhz check:changed lanes core, coreTests passed
2026-06-03 03:02:47 -07:00
Vincent Koc
a7c8b2a46a fix(e2e): bound mock readiness probes 2026-06-03 11:58:45 +02:00
Vincent Koc
5a0d9d6326 fix(codex): retire abandoned app-server startups 2026-06-03 02:55:12 -07:00
Vincent Koc
7cee0bca0b fix(e2e): isolate plugin lifecycle artifacts 2026-06-03 11:50:33 +02:00
Vincent Koc
7074cf8e23 perf(ui): label delayed chat sends in telemetry (#89777) 2026-06-03 02:41:58 -07:00
Vincent Koc
26301f318f fix(ui): scroll pending sends into view 2026-06-03 02:30:22 -07:00
Vincent Koc
f49f5973b0 perf(ui): start chat refresh before bootstrap
Start the active Control UI chat refresh after Gateway hello without waiting for the slower bootstrap fetch. Keep startup canvas embeds fail-closed until bootstrap config arrives, and recreate preview iframes when sandbox policy changes.
2026-06-03 02:27:25 -07:00
Vincent Koc
1e4ff80604 fix(e2e): clean failed tarball extracts 2026-06-03 11:18:54 +02:00
Vincent Koc
84dca54ef2 fix(e2e): fail package worktree cleanup leaks 2026-06-03 11:12:02 +02:00
Vincent Koc
4a67e4b976 fix(test): avoid empty script changed runs 2026-06-03 11:05:04 +02:00
Ayaan Zaidi
41ee6b1dd6 feat(telegram): show commentary in progress drafts 2026-06-03 14:30:30 +05:30
Ayaan Zaidi
04f93c2fb4 refactor(channels): share progress draft primitives 2026-06-03 14:30:30 +05:30
Vincent Koc
3cdb87be86 fix(test): route parallels helper changes 2026-06-03 10:57:44 +02:00
Onur Solmaz
17a285f298 fix(ui): preserve visible chat stream text
Fix WebChat stream/history reconciliation so visible assistant text survives stale history reloads, tool-history catch-up, and terminal final/error/abort events.\n\nRefactors the UI path into stream reconciliation, stream text, and typed tool-message helpers so persisted history and live stream state use the same matching rules.\n\nCloses #67035.
2026-06-03 16:56:33 +08:00
zhang-guiping
c2d7b4a486 fix(ui): clear chat stream before terminal commits
Fix the Control UI WebChat race where terminal assistant messages could be committed while chatStream was still live, causing history and active stream to render the same reply twice. Terminal final/aborted handling now snapshots fallback text, clears the active run/stream through the lifecycle owner, then appends the visible assistant message.\n\nFixes #71992.\n\nVerification: node scripts/run-vitest.mjs run ui/src/ui/controllers/chat.test.ts ui/src/ui/chat/run-lifecycle.test.ts ui/src/ui/chat/build-chat-items.test.ts; node scripts/run-vitest.mjs run ui/src/ui/app-chat.test.ts ui/src/ui/controllers/sessions.test.ts; node scripts/run-vitest.mjs run --config test/vitest/vitest.ui-e2e.config.ts --configLoader runner ui/src/ui/e2e/chat-flow.e2e.test.ts; Blacksmith Testbox tbx_01kt6a4zn7awkdy12d6b0q2d1q / run 26873514898; autoreview clean; PR CI 121 pass / 10 skipped.
2026-06-03 01:45:59 -07:00
zhang-guiping
0b98aea71a fix(ui): reconcile completed chat sends
Fixes #87699.\n\nRoutes ACK-completed Control UI chat sends through the existing run lifecycle reconciliation path so stale selected-session rows cannot re-enable the composer/Stop state after the conversation has already completed.\n\nVerification: focused UI/unit tests, Control UI E2E chat-flow test, autoreview clean, Testbox changed gate tbx_01kt68xvz17fcnmd3wj6f7pk6f, and PR CI run 26872484363 green after failed-job rerun for transient runner setup failures.
2026-06-03 01:34:13 -07:00
Vincent Koc
114864185b fix(e2e): fail kitchen sink cleanup leaks 2026-06-03 10:28:19 +02:00
Ayaan Zaidi
1bd1483b62 refactor(auto-reply): unify transient failure visibility 2026-06-03 13:55:36 +05:30
FullerStackDev
a5ef086e3c test(auto-reply): cover channel-agnostic failure routing 2026-06-03 13:55:36 +05:30
FullerStackDev
a10faca06f fix(auto-reply): surface fatal channel errors 2026-06-03 13:55:36 +05:30
Vincent Koc
380a8f140e fix(e2e): fail rpc rtt cleanup leaks 2026-06-03 10:20:22 +02:00
Vincent Koc
34c3827290 fix(e2e): close rpc rtt gateway log handles 2026-06-03 10:10:39 +02:00
Vincent Koc
54fe0e7f71 fix(e2e): keep cleanup retries covered 2026-06-03 10:10:39 +02:00
Yzx
932d6ea8e5 fix(webchat): show sessions_send handoffs as forwarded
Fix WebChat display projection for sessions_send inter-session handoffs. Forwarded messages now render assistant-side with source attribution while keeping transcript user-role semantics, stripping generated inter-session envelopes from display text, and preserving heartbeat/TTS/message-tool cleanup boundaries. Fixes #89161.
2026-06-03 01:09:45 -07:00
Vincent Koc
d004b80c91 fix(e2e): surface secret proof cleanup failures 2026-06-03 09:48:54 +02:00
Vincent Koc
5820378b90 fix(e2e): isolate telegram package artifacts 2026-06-03 09:43:16 +02:00
Vincent Koc
d5df1a1cd6 fix(e2e): isolate multi-node artifacts 2026-06-03 09:36:43 +02:00
Vincent Koc
175cfe4846 fix(gateway): stabilize webchat prompt cache affinity
Keep WebChat run/idempotency ids per message while threading a stable hashed promptCacheKey through chat.send into embedded runs. Fixes #89139.
2026-06-03 00:33:02 -07:00
Alexzhu
85e5d486df perf(control-ui): render chat history incrementally
Render dashboard chat history incrementally; preserve Talk settings callback contracts, native Talk select labels, and raw-copy baseline after rebase.
2026-06-03 00:16:32 -07:00
Vincent Koc
b6cee3fc35 fix(scripts): clean run-with-env process groups 2026-06-03 09:10:09 +02:00
Dallin Romney
d48b9274d8 fix: report gateway health auth diagnostics (#89337)
* fix: handle gateway health credential errors

* fix: diagnose gateway health credential state
2026-06-03 00:04:47 -07:00
Vincent Koc
6d788a237c fix(ci): isolate ARM Testbox workflow 2026-06-03 00:04:12 -07:00
Vincent Koc
7ccbffcb1b fix(testing): bound rpc readiness probes 2026-06-03 08:46:17 +02:00
Vincent Koc
2c92973398 fix(release): bound cross-os discord fetches 2026-06-03 08:35:14 +02:00
Vincent Koc
ed4c4afc0f fix(release): bound candidate GitHub requests 2026-06-03 08:19:03 +02:00
Vincent Koc
a462601f05 fix(e2e): isolate release journey artifacts 2026-06-03 08:08:44 +02:00
Vincent Koc
f472778717 fix(codex): close startup client on timeout 2026-06-02 23:04:41 -07:00
Vincent Koc
7c1a83ff2e fix(build): externalize optional baileys image backends 2026-06-03 07:50:25 +02:00
Vincent Koc
f8fcb35064 fix(ui): lazy load usage dashboard 2026-06-03 07:41:43 +02:00
Vincent Koc
c0b05a2100 perf(control-ui): coalesce chat metadata startup
Add a coalesced chat.metadata Gateway method so the Control UI can fetch model and command metadata without blocking a clean first message path. Reuses existing models/commands builders, keeps compatibility fallback for older gateways, updates protocol artifacts, and adds focused gateway/UI/e2e coverage.
2026-06-02 22:34:54 -07:00
Ayaan Zaidi
2a512025ad feat(telegram): compose progress draft reasoning 2026-06-03 10:54:19 +05:30
Ayaan Zaidi
7f79bd8683 refactor(discord): use shared progress compositor 2026-06-03 10:54:19 +05:30
Ayaan Zaidi
a4b09d72b9 refactor(channels): share progress draft compositor 2026-06-03 10:54:19 +05:30
Dallin Romney
58160094e8 fix: allowlist pending agent sqlite scaffold (#89705) 2026-06-02 22:22:13 -07:00
Dallin Romney
c0c4156b6d fix(exec): reject corrupt shell snapshots (#89701) 2026-06-02 21:58:28 -07:00
Vincent Koc
3f66797578 Merge branch 'main' of https://github.com/openclaw/openclaw
* 'main' of https://github.com/openclaw/openclaw:
  fix(ci): trim docker e2e heartbeat latency
2026-06-02 21:57:21 -07:00
Vincent Koc
f02c1209aa fix(ui): narrow workboard dependency fixtures 2026-06-02 21:56:51 -07:00
Vincent Koc
5056dd47ca chore(scripts): add gateway rpc rtt probe 2026-06-02 21:56:51 -07:00
Vincent Koc
97dde19577 test(extensions): reset fake timers before tests 2026-06-02 21:56:51 -07:00
Vincent Koc
7cbdebc4ed feat(ui): tighten workboard card operations 2026-06-02 21:56:50 -07:00
Vincent Koc
17795c6c4c fix(ci): trim docker e2e heartbeat latency 2026-06-03 06:54:52 +02:00
Vincent Koc
6b25b78800 fix(ci): show docker build heartbeats 2026-06-03 06:36:06 +02:00
Vincent Koc
78b3f60dbd fix(ci): reset crabbox pnpm hydrate state 2026-06-03 05:33:59 +02:00
Ayaan Zaidi
8f1ae5967e fix(discord): sanitize tool progress scaffolding 2026-06-03 08:03:57 +05:30
Ayaan Zaidi
d82bfcecb1 fix(discord): cover compact gh failure traces 2026-06-03 08:03:57 +05:30
FullerStackDev
5629c44547 fix(discord): preserve channel-label suppression 2026-06-03 08:03:57 +05:30
FullerStackDev
a8bf14da84 fix(discord): suppress internal agent failure traces 2026-06-03 08:03:57 +05:30
Ayaan Zaidi
a9f014e9df refactor(telegram): fold reset boundary lookup 2026-06-03 08:01:57 +05:30
Ted Li
d76f2c0c3b perf: avoid broad Telegram reset boundary scan 2026-06-03 08:01:57 +05:30
Vincent Koc
f2a46b0661 fix(tooling): bound deadcode knip subprocesses 2026-06-03 03:47:27 +02:00
Vincent Koc
0fa384c6f6 fix(tooling): run knip through pnpm package dlx 2026-06-03 02:52:16 +02:00
Vincent Koc
6d643ccd11 fix(tooling): reject malformed release command limits 2026-06-03 02:52:16 +02:00
Gabriel F.
8b546facaf fix(outbound): stop schema-padded poll modifiers from blocking send (#89601)
Summary:
- The PR changes shared poll-intent detection so `pollDurationHours` and `pollMulti` alone no longer make `send` actions fail, with focused unit and outbound validation coverage.
- PR surface: Source -2, Tests +40. Total +38 across 3 files.
- Reproducibility: yes. Source inspection shows current main and `v2026.5.28` expose `pollDurationHours` throu ... d message schema, classify non-zero shared duration as poll intent, and throw before a `send` can dispatch.

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

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

Prepared head SHA: 0fd95756cd
Review: https://github.com/openclaw/openclaw/pull/89601#issuecomment-4606487310

Co-authored-by: Gabriel Fratica <gabriel@codez.ro>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: takhoffman
Co-authored-by: takhoffman <781889+takhoffman@users.noreply.github.com>
2026-06-03 00:30:02 +00:00
Vincent Koc
1f35ad12b3 fix(test): reject malformed parallels smoke limits 2026-06-03 02:19:49 +02:00
Vincent Koc
3d4d30fd5a fix(release): reject malformed beta smoke limits 2026-06-03 02:06:40 +02:00
Vincent Koc
dd46fd36a3 fix(tooling): reject malformed cross-os release timeouts 2026-06-03 01:59:48 +02:00
Vincent Koc
85633eb615 chore(tooling): drop stale deadcode allowlist entries 2026-06-03 01:49:25 +02:00
Vincent Koc
2a3421a0da fix(tooling): reject malformed crabbox sync limits 2026-06-03 01:07:41 +02:00
Vincent Koc
e38b8f6a20 fix(test): reject malformed cron cleanup limits 2026-06-03 00:07:24 +02:00
Gio Della-Libera
646974b7d8 fix(policy): reject unsupported policy keys (#87074)
Merged via squash.

Prepared head SHA: 3ab4ff1d8f
Co-authored-by: giodl73-repo <235387111+giodl73-repo@users.noreply.github.com>
Co-authored-by: giodl73-repo <235387111+giodl73-repo@users.noreply.github.com>
Reviewed-by: @giodl73-repo
2026-06-02 15:01:57 -07:00
Vincent Koc
a86a1de849 fix(tooling): reject malformed tsdown watchdog limits 2026-06-02 23:43:09 +02:00
Val Alexander
be336cc1e4 feat(ui): add workboard keyboard movement controls
Add compact keyboard-accessible Workboard status movement controls for writable operators. The control reuses the existing workboard.cards.move path, preserves drag/drop as the pointer enhancement, and suppresses mutation controls for read-only operators.\n\nVerification:\n- node scripts/run-vitest.mjs ui/src/ui/views/workboard.test.ts\n- corepack pnpm exec oxfmt --check --threads=1 ui/src/ui/views/workboard.ts ui/src/ui/views/workboard.test.ts ui/src/styles/workboard.css docs/plugins/workboard.md\n- git diff --check origin/main...HEAD\n- Chromium Control UI mock Gateway keyboard movement proof\n- .agents/skills/autoreview/scripts/autoreview --mode branch --base origin/main --no-web-search
2026-06-02 16:08:29 -05:00
Vincent Koc
8cecf2c7ea fix(test): reject malformed local check limits 2026-06-02 22:48:12 +02:00
Vincent Koc
6af047c7f6 fix(test): reject malformed boundary prep timeouts 2026-06-02 22:26:15 +02:00
Vincent Koc
ac8338bb02 fix(tooling): reject malformed topology limits 2026-06-02 22:19:10 +02:00
Vincent Koc
0188c541de fix(test): reject malformed extension boundary concurrency 2026-06-02 22:12:01 +02:00
Vincent Koc
97509ed1d7 fix(test): reject malformed extension batch parallelism 2026-06-02 22:05:44 +02:00
Vincent Koc
432a5978b9 fix(test): reject malformed extension shard counts 2026-06-02 21:59:42 +02:00
Vincent Koc
5f6a8083bf fix(perf): reject malformed cpuprofile limits 2026-06-02 21:53:34 +02:00
Vincent Koc
36d7ac31c2 fix(ci): reject malformed ci timing limits 2026-06-02 21:47:28 +02:00
Vincent Koc
aed3743630 fix(docker): reject malformed timing limits 2026-06-02 21:38:21 +02:00
Vincent Koc
28b1ea7c0d fix(test): reject malformed group report numeric flags 2026-06-02 21:31:16 +02:00
Vincent Koc
661c763b28 fix(docs): reject malformed mdx max error limits 2026-06-02 21:25:23 +02:00
Vincent Koc
36a596aa9f fix(ci): reject malformed targeted docker group size 2026-06-02 21:18:52 +02:00
Michael Appel
c208a10619 Harden node exec approval precheck env [AI] (#81488)
* fix: align node exec approval precheck env

* addressing ci

* fix: preserve node allow-always prechecks

* fix: finalize node exec approval port

* fix: align node prepare approval env

* test: tighten node marker reuse coverage proof

* test: fix node allow-always coverage mock typing

---------

Co-authored-by: Devin Robison <drobison@nvidia.com>
Co-authored-by: Devin Robison <drobison00@users.noreply.github.com>
2026-06-02 13:15:41 -06:00
Vincent Koc
e59e65be67 fix(test): reject malformed boundary check env 2026-06-02 21:12:42 +02:00
Vincent Koc
054e734e53 fix(lint): reject malformed oxlint shard env 2026-06-02 21:06:14 +02:00
Vincent Koc
d007b9aba3 fix(test): reject malformed full-suite parallel env 2026-06-02 20:59:02 +02:00
Vincent Koc
5d4868c036 fix(scripts): validate gateway watch numeric options 2026-06-02 20:36:46 +02:00
Vincent Koc
8bf6206a3e test(rpc): enforce kitchen sink command rss ceiling 2026-06-02 20:09:10 +02:00
Gio Della-Libera
1d3cfc4b01 Policy: add data handling conformance checks (#87056)
Merged via squash.

Prepared head SHA: 6a0e9730aa
Co-authored-by: giodl73-repo <
>
Co-authored-by: giodl73-repo <235387111+giodl73-repo@users.noreply.github.com>
Reviewed-by: @giodl73-repo
2026-06-02 10:48:07 -07:00
Vincent Koc
1ff2ffa160 chore(scripts): drop legacy moltbot rpc alias 2026-06-02 19:37:07 +02:00
Vincent Koc
d07ba5f265 fix(providers): avoid custom provider runtime fanout 2026-06-02 19:23:38 +02:00
Vincent Koc
f789081bae test(gateway): abort accepted agent run in e2e 2026-06-02 18:12:54 +02:00
Vincent Koc
388dc56ba5 test(gateway): defer sidecars in tools invoke e2e 2026-06-02 17:41:19 +02:00
Vincent Koc
6c7644268f fix(test): stabilize ARM extension timer tests 2026-06-02 07:53:25 -07:00
Peter Steinberger
c8d21fe7f0 fix: recover suspicious gateway startup configs (#89480) 2026-06-02 10:12:35 -04:00
Bryan Tegomoh, MD, MPH
00d846daf7 fix(kimi): strip anthropic cache markers
Closes #76612

Co-authored-by: Bryan Tegomoh <bryan.tegomoh@gmail.com>
2026-06-02 09:59:36 -04:00
Shakker
1b9860aa56 fix: restore Skill Workshop view switcher 2026-06-02 14:59:19 +01:00
Peter Steinberger
97d4d5effb docs(changelog): note update repair stall handling 2026-06-02 14:57:01 +01:00
Peter Steinberger
12c6ef6d57 fix(update): keep plugin repair fetch failures nonblocking 2026-06-02 14:55:55 +01:00
Vincent Koc
96277245dc fix(test): isolate gateway CPU QA state 2026-06-02 15:27:16 +02:00
Peter Steinberger
eef24d452f fix(models): preserve provider prompt cache boundaries
Split Anthropic system prompts at the cache boundary so only stable prefixes get cache_control, strip the internal marker when cache control is disabled, and keep OpenAI-compatible Anthropic cache-control routes from caching dynamic suffixes.\n\nFixes #89386.
2026-06-02 09:19:52 -04:00
Peter Steinberger
c3baec7136 docs: clarify autoreview follow-up scope 2026-06-02 06:15:51 -07:00
Coder
4bb86877e2 fix(google): forward Gemini stop sequences
Forward configured stop sequences to Gemini generationConfig.stopSequences in the bundled Google transport, matching the shared Google provider behavior and the @google/genai request contract.\n\nThanks @coder999999999.
2026-06-02 09:02:27 -04:00
Coy Geek
3509f7613e fix: audit and repair hooks token reuse with Gateway auth
Keep startup non-breaking for existing installs when hooks.token reuses Gateway auth, but surface a startup warning, critical security audit finding, and doctor --fix repair that rotates persisted hooks.token.

Closes #87376.

Co-authored-by: Coy Geek <65363919+coygeek@users.noreply.github.com>
2026-06-02 08:58:40 -04:00
Vincent Koc
36c1a3e006 fix(memory): avoid optional vector status dereference 2026-06-02 14:53:35 +02:00
Vincent Koc
212eaead01 fix(memory): force provider-none indexes to FTS-only vectors 2026-06-02 14:53:35 +02:00
Vincent Koc
984c3ded9a fix(scripts): avoid dead child assignment in fd repro 2026-06-02 14:53:35 +02:00
Vincent Koc
0b7c94a5e1 fix(memory): initialize provider-none lifecycle during sync 2026-06-02 14:53:35 +02:00
Vincent Koc
0b61add479 fix(memory): report provider-none probes as FTS-only 2026-06-02 14:53:35 +02:00
Vincent Koc
2d11402208 fix(scripts): avoid spread in runtime output collection 2026-06-02 14:53:35 +02:00
Vincent Koc
f6e8a1b2a8 fix(scripts): clean memory fd temp dirs after preindex failures 2026-06-02 14:53:35 +02:00
Vincent Koc
5a4f868de0 fix(memory): scope provider-none FTS bypass 2026-06-02 14:53:34 +02:00
Vincent Koc
4115f0c82f fix(scripts): keep watch proof asset copies out of idle window 2026-06-02 14:53:34 +02:00
Vincent Koc
cd0af35e5c fix(memory): keep FTS-only sync offline 2026-06-02 14:53:34 +02:00
Alix-007
1824aa07a0 fix(mistral): enable prompt cache keys
Enable Mistral prompt cache keys without long-retention forwarding. Update cached-read pricing and doctor migration for existing Mistral provider config. Fixes #83709.
2026-06-02 08:52:12 -04:00
Peter Steinberger
5259fa4495 fix(llm): keep OpenAI-compatible reasoning streams active 2026-06-02 08:40:03 -04:00
Peter Steinberger
2ffeca1d78 docs: document Android notification picker helpers 2026-06-02 08:38:23 -04:00
NVIDIAN
895dccd058 fix(agents): gate finalize hooks before delivery
Run `before_agent_finalize` for embedded agents before terminal delivery so revise decisions can retry without leaking a final assistant reply.

The embedded subscription now defers terminal assistant events, block replies, and lifecycle delivery until the pre-terminal gate resolves; accepted revise decisions suppress delivery, while hook failures and continue decisions finalize normally. It also preserves existing replay-invalid liveness behavior while still preventing revise after side-effecting turns.

Closes #87585

Co-authored-by: ai-hpc <mail.speedy.hpc@hotmail.com>
2026-06-02 08:27:36 -04:00
Peter Steinberger
06434d85a0 fix(llm): gate OpenAI-compatible reasoning output
Replaces #89343 because the contributor fork did not allow maintainer edits.

Co-authored-by: zz327455573 <327455573@qq.com>
2026-06-02 08:24:34 -04:00
Peter Steinberger
a326faa10c fix: recover corrupt managed npm installs 2026-06-02 05:21:19 -07:00
Peter Steinberger
6467ddd7ed fix(qqbot): migrate state stores to sqlite kv
Move QQBot credential backups, gateway sessions, known-user records, and ref-index rows into plugin SQLite KV stores. Import shipped JSON/JSONL state files on first use and keep auxiliary known-user/ref-index state best-effort so message delivery is not blocked by cache persistence failures.
2026-06-02 08:15:19 -04:00
Peter Steinberger
95880ae21c fix: align auth health status after Codex sidecar merge 2026-06-02 05:14:52 -07:00
Vincent Koc
d830e4affc fix(testing): probe plugin CLI help while installed 2026-06-02 14:01:18 +02:00
兰之
10d10faa25 feat(plugin-sdk): add resolve_exec_env hook
Summary:
- Add the plugin SDK `resolve_exec_env` hook for bounded exec environment contributions.
- Wire resolved exec env through exec preparation/final execution without exposing plugin env values to generic tool hooks.
- Cover lazy exec loading, host and command rewrites, node/gateway execution, filtering, and EXEC shell snapshot cache behavior.

Verification:
- `pnpm changed:lanes --json`
- `node scripts/run-vitest.mjs src/agents/bash-tools.exec.resolve-env-hook.test.ts src/agents/agent-tool-definition-adapter.test.ts src/agents/agent-tool-definition-adapter.after-tool-call.test.ts src/agents/shell-snapshot.test.ts src/plugins/hook-resolve-exec-env.test.ts`
- `pnpm check:test-types`
- `pnpm lint src/agents/bash-tools.exec.ts src/agents/bash-tools.exec.resolve-env-hook.test.ts`
- `.agents/skills/autoreview/scripts/autoreview --mode branch --base origin/main`
- PR CI clean on 1bbad8d071: https://github.com/openclaw/openclaw/actions/runs/26817910293

Co-authored-by: Lanzhi <lizhan3@xiaomi.com>
2026-06-02 08:00:42 -04:00
Andy Ye
e992af4b6e fix: surface unresolved OAuth sidecar auth failures
Surface stale Codex OAuth sidecar references as unresolved auth failures in auth health, model status, and gateway status instead of hiding them as generic missing auth.

Also refresh the running gateway after doctor auth-profile repairs by reloading secrets/runtime auth snapshots and then refreshing the model auth-status cache.

Thanks @TurboTheTurtle.

Fixes #84252.
2026-06-02 07:56:14 -04:00
Yzx
b1bdc29d33 fix(providers): use native reasoning mode for Gemini instead of tagged (#89379)
* fix(providers): use native reasoning mode for direct Gemini API, keep CLI tagged

Gemini 2.5+ delivers reasoning via native thinkingParts (thinkingConfig.
includeThoughts). Having tagged mode active at the same time injects a
<think>…</think>/<final>…</final> directive into the system prompt; the
model opens a <think> block before a tool call, never closes it, and
returns an empty post-tool turn (content:[], payloads=0 error, #69220).

Fix: override resolveReasoningOutputMode in buildGoogleProvider() only —
not in the shared GOOGLE_GEMINI_PROVIDER_HOOKS. The Gemini CLI backend
(google-gemini-cli) runs gemini --output-format json and parses a text
response field, not native thought parts; it must stay on tagged mode.
A regression test confirms google-gemini-cli remains "tagged".

Also remove the dead BUILTIN_REASONING_OUTPUT_MODES entry keyed on
"google-generative-ai" from provider-utils.ts — that string is only
ever the transport model.api value, never the provider id passed to
resolveReasoningOutputMode, so the map was unreachable.

Fixes #69220

* docs: clarify Gemini reasoning output modes

* fix(google): keep Antigravity reasoning tagged

* fix(google): default direct reasoning checks to native

* fix(google): import reasoning context from plugin entry

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-06-02 07:46:08 -04:00
Sebastien Tardif
e7aac172d5 fix(codex): clear stale context-engine projection after overflow retry
Fixes #88355.

When a resumed Codex context-engine thread overflows and OpenClaw retries on a fresh native thread, clear the stale thread-bootstrap projection metadata from the fresh binding. This prevents later turns from treating that fresh thread as already projected when it only received the bare retry prompt.

Verification:
- Autoreview clean: no accepted/actionable findings reported.
- CI run 26717883204 green on head 5438f8ad34.
2026-06-02 07:33:48 -04:00
Vincent Koc
4b7f39e406 refactor(gateway): derive connection auth options 2026-06-02 13:24:17 +02:00
Vincent Koc
335c3a8d31 refactor(gateway): share node agent dispatch 2026-06-02 13:24:17 +02:00
Vincent Koc
fd6b3255f8 refactor(gateway): share embedding remote options 2026-06-02 13:24:17 +02:00
Dirk
355cbc5071 fix(google): add missing gemini-3.1-flash-lite to google-vertex catalog (#89400)
* fix(google): add gemini-3.1-flash-lite to provider catalog

Adds the missing gemini-3.1-flash-lite model definition to the
GOOGLE_GEMINI_TEXT_MODELS array. This resolves the ProviderFailoverError
when configuring google-vertex/gemini-3.1-flash-lite.

Fixes #89390

* test(google): cover Gemini flash lite catalog row

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-06-02 07:21:50 -04:00
Peter Steinberger
b4dfa950b5 refactor: tighten agent harness surfaces
Refactor the agent harness surface after PR #88821 by moving compaction dispatch into its own module, splitting the harness type into explicit capability interfaces, and renaming the private agent-core class declaration to `CoreAgentHarness` while preserving the exported `AgentHarness` contract.

Verification:
- `node scripts/run-vitest.mjs src/agents/harness/selection.test.ts src/agents/command/cli-compaction.test.ts src/agents/embedded-agent-runner/compact.hooks.test.ts packages/agent-core/src/agent-loop.test.ts packages/agent-core/src/harness/messages.test.ts`
- `pnpm build`
- autoreview clean
- `pnpm check:changed` passed on Testbox `tbx_01kt407hq8sv1csm287pdj3fmp`
- PR CI merge state `CLEAN`
2026-06-02 07:20:43 -04:00
Mukunda Rao Katta
2d61521bd3 fix(update): pin post-core plugin compatibility to the downgraded core version (#87914) (#87952)
* fix(update): pin post-core plugin compatibility to the downgraded core version (#87914)

* fix(update): force plugin compatibility repair on rollback

* style(update): clarify downgrade compatibility note

* fix(plugins): resolve compatible prerelease plugin downgrades

* fix(plugins): honor host gates during npm downgrade repair

* fix(plugins): keep prerelease downgrade fallback on channel

---------

Co-authored-by: Gio Della-Libera <giodl73@gmail.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-06-02 07:13:26 -04:00
Dallin Romney
30b9e123b8 fix: repeat doctor state migration repairs
Stabilize repeated `openclaw doctor --fix` state repairs for legacy plugin state and installed plugin index migrations.

- Import legacy-only plugin-state sidecar rows before deciding whether live conflicts require keeping the sidecar.
- Drop expired sidecar rows only when the sidecar can be archived, avoiding repeated false migration changes.
- Let richer current install records cover legacy records only when durable legacy fields are actually preserved, without erasing npm selector intent or malformed legacy metadata.

Proof:
- `node scripts/run-vitest.mjs src/commands/doctor-state-migrations.test.ts`
- `git diff --check origin/main...HEAD`
- `.agents/skills/autoreview/scripts/autoreview --mode branch --base origin/main`
- PR CI clean for head `5f3a7e0749372a40cabd7a090cae155997481b71`

Co-authored-by: Dallin Romney <dallinromney@gmail.com>
2026-06-02 07:13:02 -04:00
Coy Geek
a14be505ff fix(qqbot): isolate credential backups by state root
QQBot credential backups now resolve under the active OpenClaw state directory instead of the old home-global QQBot data path. This keeps isolated gateway profiles from restoring each other's QQBot appId/clientSecret backups while preserving per-state-root recovery.

Proof: focused QQBot path/storage-laziness Vitest suite passed on Node 24.15.0, focused oxlint passed, source-runtime two-root backup proof passed, exact-head CI run 26814565282 passed, and ClawSweeper re-review run 26815054980 marked proof sufficient.

Closes #84313.

Co-authored-by: Coy Geek <65363919+coygeek@users.noreply.github.com>
2026-06-02 07:11:01 -04:00
charles-openclaw
2c48dd2277 fix(sessions): preserve corrupt-header transcripts
Fixes #89037.

Co-authored-by: Charles <charles-openclaw@9bcfae.inboxapi.ai>
2026-06-02 07:02:09 -04:00
Hussein Nourelddine
4a285d529a feat(status): detect external plugin version drift
Surface active official external plugin version drift in gateway status diagnostics so users can see when a host/package update left npm or ClawHub plugins behind the running local gateway. The advisory uses the daemon service install records, compares against the running gateway version, gives detailed fix commands in deep status, and avoids local-state drift checks for remote gateway mode or explicit status probe URLs.

Co-authored-by: Hussein Nourelddine <hussein@gptc.com.kw>
2026-06-02 06:59:23 -04:00
Vincent Koc
07821e4bb8 refactor(gateway): share secret ref input resolution 2026-06-02 12:52:02 +02:00
Vincent Koc
4bae78858f refactor(gateway): share runtime service helpers 2026-06-02 12:52:02 +02:00
Andy Ye
1db2c2a3e0 Treat soft plugin repair warnings as nonfatal (#84431)
* Treat soft plugin repair warnings as nonfatal

* fix: scope plugin repair convergence failures

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-06-02 06:51:11 -04:00
NVIDIAN
eb417bc672 fix(messages): preserve inbound audio for message-tool TTS
Preserve inbound-audio context for message-tool TTS across embedded reply runs, CLI MCP loopback, and queued follow-up paths.

Thanks @ai-hpc.

Co-authored-by: ai-hpc <mail.speedy.hpc@hotmail.com>
2026-06-02 06:45:34 -04:00
Peter Steinberger
5d6216a7f1 fix: detect shrinkwrapped npm installs
Fixes status/update detection for npm-installed OpenClaw packages that ship npm-shrinkwrap while preserving pnpm and Bun install ownership.

Fixes #87732.
Supersedes #88283.

Proof: focused infra Vitest shard, autoreview clean, Crabbox install matrix, and PR CI all green.
2026-06-02 06:39:22 -04:00
Bek
bce3d5bf92 trace: Correlate channel diagnostics into one trace
Correlates channel receive, agent lifecycle, model attempt diagnostics, and outbound delivery diagnostics into one trace waterfall so channel message runs can be inspected end-to-end.

Maintainer follow-up removed the internal `AgentHarnessV2` adapter surface and kept the harness path canonical through `src/agents/harness/lifecycle.ts`.

Proof:
- PR checks passed on `04e9189c15480d53663d533a04c9883164b4dd54`.
- `node scripts/run-vitest.mjs src/agents/harness/lifecycle.test.ts src/agents/harness/selection.test.ts src/channels/turn/kernel.test.ts`
- `pnpm check:changed` Testbox `tbx_01kt3xtrm70qc7nb90cqv5rah1`

Thanks @bek91.

Co-authored-by: Bek <bek.akhmedov@gmail.com>
2026-06-02 06:38:00 -04:00
LiLan0125
ad9f7f9a59 fix(diagnostics): requeue stuck session lane after recovery
Reset the session command lane when stuck-session recovery aborts and drains a ghost embedded run but queued lane work remains. This preserves pending user messages by using the existing lane recovery pump instead of leaving them stranded after recovery reports success.

Adds focused regression coverage for the abort=true, drained=true, queuedCount=1 path.

Fixes #89208.
Supersedes #89293.
Thanks @LiLan0125.

Co-authored-by: 李兰 0668001394 <li.lan3@xydigit.com>
2026-06-02 06:36:19 -04:00
Gio Della-Libera
a25338f2b7 fix(discord): accumulate reasoning progress deltas
Fix Discord progress-mode reasoning streams so delta chunks accumulate before display formatting, preserving raw Thinking/Reasoning-prefixed content and balanced truncation.\n\nFixes #83983.\n\nThanks @giodl73-repo for the fix and live Discord proof.
2026-06-02 06:35:29 -04:00
Bek
6997453098 fix: guard in-band macOS launchd stop
Summary:
- guard macOS launchd stop/restart against in-band service relaunch loops
- centralize current-service detection for launchd stop and restart handoff
- preserve external launchd label stop overrides while fixing inherited XPC restart handoff

Verification:
- node scripts/run-vitest.mjs src/daemon/launchd.test.ts src/daemon/launchd-current-service.test.ts src/daemon/launchd-restart-handoff.test.ts
- .agents/skills/autoreview/scripts/autoreview --mode local
- pnpm check:changed via Blacksmith Testbox through Crabbox: tbx_01kt3xkmfqhnzghfxdn62fa8qm

Closes #89174

Co-authored-by: Bek <bek.akhmedov@gmail.com>
2026-06-02 06:27:36 -04:00
Vincent Koc
c35fda3cfa refactor(gateway): derive websocket runtime params 2026-06-02 12:20:48 +02:00
Vincent Koc
8ea6b5d5b2 fix(scripts): clean package-boundary prep process groups 2026-06-02 12:15:53 +02:00
NVIDIAN
a02a7aaddb fix(codex): trace app-server thread lifecycle timing
Fixes #84640.
2026-06-02 06:11:58 -04:00
Pavan Kumar Gondhi
19fb9f1299 fix: redact trajectory exports consistently (#89354)
* fix trajectory export redaction

* fix trajectory export top-level redaction

* fix trajectory export key redaction

* fix trajectory export structural key redaction
2026-06-02 15:41:44 +05:30
兰之
2664f59519 fix(cron): reject blank delivery targets
Reject whitespace-only cron delivery target strings before cron input normalization can trim and drop them, so bad delivery targets return INVALID_REQUEST instead of behaving as omitted fields.

Keep explicit null update clears for delivery, failure destination, and completion destination fields.

Co-authored-by: gaozixiang1 <gaozixiang1@xiaomi.com>
Co-authored-by: Lanzhi <lizhan3@xiaomi.com>
2026-06-02 06:10:19 -04:00
兰之
1cca70940c fix: hide sessions_spawn timeout overrides
Remove model-facing per-call timeout overrides from sessions_spawn while keeping operator-controlled timeout behavior through agents.defaults.subagents.runTimeoutSeconds.

Reject stale camelCase and snake_case timeout arguments, update ACP/native timeout propagation, refresh docs and prompt snapshots, and cap ACP runtime option timeouts to the ACP control-plane maximum without shortening gateway dispatch or registry tracking.

Proof:
- node --import tsx - runtime probe against src/agents/tools/sessions-spawn-tool.ts
- node scripts/run-vitest.mjs src/agents/tools/sessions-spawn-tool.test.ts src/agents/acp-spawn.test.ts src/agents/openclaw-tools.subagents.sessions-spawn.lifecycle.test.ts
- pnpm docs:list
- git diff --check origin/main...HEAD
- .agents/skills/autoreview/scripts/autoreview --mode branch --base origin/main
- .agents/skills/autoreview/scripts/autoreview --mode local
- GitHub checks: 132 pass, 30 skipped

Co-authored-by: Lanzhi <lizhan3@xiaomi.com>
Co-authored-by: chenhaoqiang <chenhaoqiang@xiaomi.com>
2026-06-02 06:09:02 -04:00
兰之
43d0aaec3d fix(agents): honor provider idle timeout for unlimited runs
Honor explicit provider/model request timeoutSeconds when the agent run timeout is the no-timeout sentinel, and keep explicit run timeout overrides from being capped by agent defaults.

Verification:
- pnpm test src/agents/embedded-agent-runner/run/llm-idle-timeout.test.ts -- --reporter=verbose
- .agents/skills/autoreview/scripts/autoreview --mode branch --base origin/main
- CI run 26812803642 passed on the rebased PR head
- Real behavior proof run 26812917801 passed after maintainer proof override

Co-authored-by: zhongqiongbo1 <zhongqiongbo1@xiaomi.com>
Co-authored-by: Lanzhi <lizhan3@xiaomi.com>
2026-06-02 06:08:56 -04:00
Vincent Koc
5487855815 refactor(gateway): share talk relay session lifecycle 2026-06-02 11:57:08 +02:00
Vincent Koc
45f7aec156 refactor(gateway): share transcript path comparison 2026-06-02 11:57:08 +02:00
Vincent Koc
286c8e3632 fix(build): parallelize startup metadata rendering 2026-06-02 11:50:49 +02:00
Vincent Koc
e24582d53c fix(crabbox): preflight sparse sync disk space 2026-06-02 11:42:14 +02:00
Vincent Koc
3e9b197bd0 test(gateway): share node invoke acknowledgement 2026-06-02 11:24:01 +02:00
Vincent Koc
601ab84f35 test(gateway): share configured global session stores 2026-06-02 11:13:36 +02:00
clawsweeper[bot]
abc3fa0396 fix(memory-core): keep startup cron retries quiet (#89075)
Summary:
- The branch adds a memory-core `startup_retry` reconciliation mode and regression tests for quiet startup retries, retry-window exhaustion, and live-config retry semantics.
- PR surface: Source +9, Tests +114. Total +123 across 2 files.
- Reproducibility: yes. from source: current main routes the first startup retry through runtime reconciliatio ... st expects the warn-level `cron service unavailable` log. I did not execute tests in this read-only review.

Automerge notes:
- Ran the ClawSweeper repair loop before final review.
- Included post-review commit in the final squash: fix(memory-core): keep startup cron retries quiet

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

Prepared head SHA: 7220f940d0
Review: https://github.com/openclaw/openclaw/pull/89075#issuecomment-4592446250

Co-authored-by: bennewell35 <newelljben@gmail.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: takhoffman
Co-authored-by: takhoffman <781889+takhoffman@users.noreply.github.com>
2026-06-02 09:09:52 +00:00
Ayaan Zaidi
db576c4a2d refactor(agents): trim native compaction ownership follow-up 2026-06-02 14:39:35 +05:30
Cameron Beeley
5e52a9b513 docs(cli-backends): document ownsNativeCompaction opt-out contract 2026-06-02 14:39:35 +05:30
Cameron Beeley
3d7523b618 feat(agents): generalized native compaction ownership for CLI backends
Add `ownsNativeCompaction` capability to CliBackendPlugin so backends
that manage their own transcript compaction (e.g. Claude Code) can
declare it once and OpenClaw defers instead of fighting or failing.

Today only Codex declares compaction ownership (via the embedded runner
path + agentHarnessId). Claude-cli never reaches that path because it
runs as a CLI subprocess with no harness id set, so the safeguard
summarizer fires and hard-fails the turn.

This PR:
- Adds `ownsNativeCompaction?: boolean` to the backend plugin type
- Propagates it through all 4 backend resolution paths
- In `runCliTurnCompactionLifecycle`, when a backend declares ownership
  but has no harness endpoint, returns a no-op instead of falling
  through to the safeguard
- Sets the flag on claude-cli (first adopter)

Codex's existing native-harness path is unchanged: when
`isNativeHarnessCompactionSession` matches, the harness compaction
endpoint is still called as before.

Generalizes the partial fix in #87785 (codex-scoped) to a capability
any backend can opt into.
2026-06-02 14:39:35 +05:30
Vincent Koc
afbf895af0 test(gateway): share runtime state fixture 2026-06-02 10:55:01 +02:00
Vincent Koc
af9bad9fe7 fix(gateway): avoid sync Control UI asset reads 2026-06-02 10:53:31 +02:00
Vincent Koc
3995d57797 refactor(gateway): share fast-path secrets prepare args 2026-06-02 10:44:55 +02:00
Vincent Koc
dcf21ac3ad fix(e2e): isolate release scenario mock state 2026-06-02 10:42:22 +02:00
Vincent Koc
e128efa13a fix(e2e): isolate OpenAI web search smoke logs 2026-06-02 10:34:44 +02:00
Vincent Koc
7f1c991e44 fix(scripts): forward wrapper hangup signals 2026-06-02 10:23:18 +02:00
Vincent Koc
a682e64813 refactor(gateway): share plugin install diff walk 2026-06-02 10:15:06 +02:00
Ayaan Zaidi
e31f351923 fix(android): classify updated system apps 2026-06-02 13:44:45 +05:30
Tosko4
5f505236a6 docs(android): document device apps command 2026-06-02 13:44:45 +05:30
Tosko4
3d1ec37129 feat(android): add installed apps node command 2026-06-02 13:44:45 +05:30
Vincent Koc
6c8e065e3b test(gateway): share scheduled service activation setup 2026-06-02 09:59:09 +02:00
Vincent Koc
cd3887c28a fix(scripts): cancel timed-out response bodies 2026-06-02 09:49:02 +02:00
Vincent Koc
92d363773e test(gateway): reuse record assertions in artifact tests 2026-06-02 09:39:01 +02:00
Vincent Koc
4d3411349b test(gateway): reuse deferred helper in lane tests 2026-06-02 09:28:53 +02:00
Vincent Koc
5912b9e738 fix(gateway): return mcp oversized body errors 2026-06-02 09:25:38 +02:00
Vincent Koc
64d01ff8a8 test(gateway): share deferred helper 2026-06-02 09:12:31 +02:00
Vincent Koc
06f973dd4f test(gateway): share record assertion helpers 2026-06-02 09:02:54 +02:00
Sliverp
0552ec899f fix(qqbot): allow RFC2544 benchmark range for token fetch (#88984) (#89015)
* fix(qqbot): allow RFC2544 benchmark range for token fetch (#88984)

QQ Bot `bots.qq.com` token-fetch path was failing for users whose DNS resolver maps the hostname into the RFC 2544 benchmark range `198.18.0.0/15` (commonly seen with fake-IP proxy stacks: sing-box, Clash, Surge, WSL2 DNS). The default SSRF guard treats that range as private and blocks the request, surfacing as "Network error getting access_token: Blocked: resolves to private/internal/special-use IP address".

Pass a host-scoped `SsrFPolicy` (`allowRfc2544BenchmarkRange: true`) to the single hard-coded `TOKEN_URL` request, mirroring the existing `QQBOT_MEDIA_SSRF_POLICY` pattern used by the media path. Because `TOKEN_URL` is a const and not user-controlled, the relaxation cannot widen attack surface to other hosts.

Adds a regression test asserting `policy: { allowRfc2544BenchmarkRange: true }` is forwarded into `fetchWithSsrFGuard`, and updates the existing equality assertion accordingly.

Fixes #88984

* fix(qqbot): scope token ssrf policy
2026-06-02 15:00:39 +08:00
Vincent Koc
f37ce4ed9b fix(gateway): report pending drain pruning revisions 2026-06-02 08:55:47 +02:00
Dallin Romney
20e0d068a7 fix: bundle private llm core declarations (#89336) 2026-06-01 23:51:38 -07:00
Vincent Koc
c0400397df test(gateway): share agent image request helpers 2026-06-02 08:38:48 +02:00
Peter Steinberger
732d6972d7 fix: repair model provider edge cases
Repairs a batch of narrow model/provider edge cases:

- honor OpenAI and Anthropic base URL environment overrides when provider config does not set an explicit base URL
- preserve OpenRouter Anthropic cache retention while stripping unsupported transport options
- allow apply_patch for non-OpenAI providers when the tool config otherwise permits it
- prune stale same-provider model selections from configure/model picker state
- expose GitHub Copilot bundled thinking policy metadata to offline/provider-policy lookups
- repair additive SQLite shared-state upgrades for existing databases
- keep same-size rotated log readers from reusing stale content in CI tooling

Proof:

- GitHub PR checks green on exact head 46514909b0
- Crabbox delegated Blacksmith Testbox tbx_01kt3em5r9vd7g0bnykrff6jdk exited 0
- Focused local Vitest/oxlint/format proof recorded in PR body and land-ready comment

Fixes #80347.
Fixes #88357.
Fixes #45269.
Supersedes #74427, #74432, #79370, #79894, #80366, and #88359.
2026-06-02 02:35:12 -04:00
Vincent Koc
438eb26d39 fix(ci): keep crabbox sync checkouts alive 2026-06-02 08:29:50 +02:00
Vincent Koc
fd1e314e59 test(gateway): share boot run helpers 2026-06-02 08:23:03 +02:00
Onur Solmaz
a4b4fed412 fix(memory): validate memory index identity
* docs: add memory index identity plan

* fix(memory): validate memory index identity

* fix(memory): align status index identity with vector probe

* fix(memory): fail closed on stale fts-only search

* fix(memory): clear sessions-only identity reindex dirty state

* fix(memory): gate targeted session sync by index identity

* fix(memory): clear resolved index identity dirtiness

* fix(memory): block search on missing index identity

* fix(memory): preserve dirty events during identity reindex

* fix(memory): resolve provider aliases for index identity

* fix(memory): report missing identity states accurately

* fix(memory): mark missing session index identity dirty

* test(memory): expose provider alias resolver in mocks

* chore(memory): remove scratch implementation plan

* fix(memory): avoid automatic full reindex on provider cutover

* docs(memory): plan no-schema cutover repair

* fix(memory): pause vector search on index identity mismatch

* fix(memory): freeze dirty identity sync writes

* fix(memory): skip paused-index search retry

* test(memory): keep retry tests on same provider identity

* fix(memory): surface paused index recall

* chore(memory): remove scratch plan from pr

* fix(memory): preserve paused session dirtiness

* fix(memory): make paused recall warning explicit

* docs(memory): document explicit index repair
2026-06-02 14:22:25 +08:00
Abner Shang
5be282e459 fix(backup): accept root-relative hardlink targets (#89328) 2026-06-01 23:09:21 -07:00
Vincent Koc
4df832412e fix(ci): normalize macos crabbox locale 2026-06-02 08:06:54 +02:00
Vincent Koc
3901f48b0e test(gateway): share channel health fixtures 2026-06-02 07:57:14 +02:00
Vincent Koc
85d2dd8ed2 refactor(gateway): share session history snapshot build 2026-06-02 07:46:38 +02:00
Vincent Koc
46bd5ebd11 refactor(gateway): share realtime tool result broadcast 2026-06-02 07:37:52 +02:00
Vincent Koc
5c93de3e7f refactor(gateway): share hook dispatch session policy 2026-06-02 07:28:31 +02:00
Vincent Koc
b579c0a65b fix(llm): normalize streaming json args 2026-06-02 07:24:19 +02:00
Vincent Koc
94adfc8d10 test(gateway): share node catalog fixtures 2026-06-02 07:13:17 +02:00
Vincent Koc
6883351085 fix(e2e): detect same-size log rotation 2026-06-02 07:11:57 +02:00
Vincent Koc
93fd17447a fix(talk): preserve null lifecycle payloads 2026-06-02 07:05:05 +02:00
Vincent Koc
ebf20241bd test(gateway): share deferred test helper 2026-06-02 06:53:31 +02:00
Vincent Koc
16808524cb refactor: share mcp loopback scope params 2026-06-02 06:44:47 +02:00
Vincent Koc
58de2b689f fix(nodes): preserve falsy event payloads 2026-06-02 06:39:00 +02:00
Vincent Koc
55467f0b94 refactor: share config write response flow 2026-06-02 06:32:06 +02:00
Vincent Koc
6ba25c10dc fix(build): cap tsdown heap on native Windows 2026-06-02 06:25:27 +02:00
Vincent Koc
3419cf5a0d fix(codex): preserve null sandbox rpc results 2026-06-02 06:23:53 +02:00
Peter Steinberger
265926aa47 fix: honor channel model overrides in agent ingress 2026-06-02 00:20:21 -04:00
clawsweeper[bot]
63ed9adfe9 fix(auto-reply): guard missing dispatcher getFailedCounts without weakening the SDK type (#89318)
Summary:
- Adds defensive failed-count reads in auto-reply/ACP accounting and Feishu fallback paths, plus a focused regression test, while keeping `ReplyDispatcher.getFailedCounts` required.
- PR surface: Source +24, Tests +35. Total +59 across 5 files.
- Reproducibility: yes. from source inspection. Current main calls `dispatcher.getFailedCounts().final` and si ... issing that method follows a clear TypeError path; the source PR also supplied terminal before/after proof.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(auto-reply): guard missing dispatcher getFailedCounts without wea…

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

Prepared head SHA: 0bdfb4adeb
Review: https://github.com/openclaw/openclaw/pull/89318#issuecomment-4598624344

Co-authored-by: Alix-007 <li.long15@xydigit.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: takhoffman
Co-authored-by: takhoffman <781889+takhoffman@users.noreply.github.com>
2026-06-02 04:16:58 +00:00
Vincent Koc
e6b5083660 refactor: share gateway misc test helpers 2026-06-02 06:15:56 +02:00
WJzz1
6349af6502 docs: add ClawHub CLI page (#89297)
Summary:
- Adds `docs/clawhub/cli.md` documenting OpenClaw skill/plugin ClawHub commands plus standalone ClawHub publish, sync, and transfer workflows.
- PR surface: Docs +82. Total +82 across 1 file.
- Reproducibility: not applicable. this is a docs-only missing-route repair rather than a runtime bug. Source  ... rrent main lacks `docs/clawhub/cli.md` while navigation and existing docs already reference `/clawhub/cli`.

Automerge notes:
- PR branch already contained follow-up commit before automerge: docs: add ClawHub CLI page
- PR branch already contained follow-up commit before automerge: fix(clawsweeper): address review for automerge-openclaw-openclaw-8929…

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

Prepared head SHA: 11e071c344
Review: https://github.com/openclaw/openclaw/pull/89297#issuecomment-4598332147

Co-authored-by: Wang-Yeah623 <205193123+Wang-Yeah623@users.noreply.github.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: takhoffman
Co-authored-by: takhoffman <781889+takhoffman@users.noreply.github.com>
2026-06-02 04:13:50 +00:00
Vincent Koc
ffbd02fe8e fix(agents): preserve null node payloads 2026-06-02 06:03:06 +02:00
Vincent Koc
75bc80bb42 refactor: share exec approval iOS push fixtures 2026-06-02 06:02:15 +02:00
Vincent Koc
1e7a0d8987 refactor: share startup auth test helpers 2026-06-02 05:47:24 +02:00
Vincent Koc
39f319c7a4 fix(e2e): preserve gateway null payloads 2026-06-02 05:44:37 +02:00
Vincent Koc
7c4fb1bd2c refactor: share session search test helpers 2026-06-02 05:42:38 +02:00
Vincent Koc
7d5d62511f fix(e2e): preserve null rpc results 2026-06-02 05:33:07 +02:00
Vincent Koc
cc6a6f5682 refactor: share readiness test fixtures 2026-06-02 05:32:25 +02:00
Vincent Koc
7a8d307bdc refactor: share node invoke approval test helpers 2026-06-02 05:24:23 +02:00
Peter Steinberger
b7d363cadf fix(agents): bypass stale auth for plugin harnesses
Explicit non-Codex plugin harness runtimes now bypass stale OpenClaw provider auth cooldowns before harness startup, while Codex/OpenClaw and missing-harness gates remain fail-closed. Fixes #85105.
2026-06-01 23:22:54 -04:00
Vincent Koc
68b4dd1816 fix(crabbox): serialize macos node bootstrap 2026-06-02 05:21:16 +02:00
Vincent Koc
0e16e72091 refactor: share session reset hook test helpers 2026-06-02 05:16:03 +02:00
Peter Steinberger
9ead0ae921 fix: repair live model inference edge cases
Fix live model inference edge cases across provider streaming, model switching, outbound delivery, and gateway tool resolution.

Includes live/provider issue fixes and leaves #89100 explicitly partial for the remaining FM-2 group routing case.
2026-06-01 23:03:27 -04:00
Vincent Koc
3128ec9858 refactor: share gateway probe test helpers 2026-06-02 04:59:36 +02:00
Vincent Koc
1ec291c682 fix(ios): require explicit gateway log target 2026-06-02 04:52:50 +02:00
Vincent Koc
9d9a6140a3 refactor: share sessions list changed test helpers 2026-06-02 04:48:54 +02:00
Vincent Koc
674bd6fc93 fix(mac): isolate build run logs 2026-06-02 04:47:00 +02:00
Peter Steinberger
b2a55a282a fix(update): do not fail core update on plugin repair fetch 2026-06-02 03:42:54 +01:00
Vincent Koc
3cf4c1ad69 refactor: share connect policy test helpers 2026-06-02 04:38:59 +02:00
Vincent Koc
fa9ce6ea0e fix(mac): isolate dmg resize limits 2026-06-02 04:32:38 +02:00
Vincent Koc
0f1f1a1fd7 refactor: share startup config recovery test helpers 2026-06-02 04:29:40 +02:00
Vincent Koc
d944aaa9ec fix(test): reject retired live shard 2026-06-02 04:20:53 +02:00
Vincent Koc
baade28397 refactor: share subagent delivery context test helpers 2026-06-02 04:20:09 +02:00
Vincent Koc
883c0f1254 fix(mac): scope restart log by worktree 2026-06-02 04:11:23 +02:00
Vincent Koc
793ab78ebb refactor: share cron validation test helpers 2026-06-02 04:08:21 +02:00
Peter Steinberger
57ea5aff81 test(release): expect cheap docker preflight 2026-06-02 03:03:48 +01:00
Vincent Koc
f1d65b3cd6 fix(e2e): isolate trash shim bin dir 2026-06-02 04:01:47 +02:00
Vincent Koc
e6b951a6a6 refactor: share operator approval client test setup 2026-06-02 03:58:27 +02:00
Vincent Koc
55e9194a4c perf(scripts): avoid duplicate build cache input hashing 2026-06-02 03:50:19 +02:00
Vincent Koc
8929838159 refactor: share gateway credentials test fixtures 2026-06-02 03:49:48 +02:00
Peter Steinberger
a355c8897d ci(release): keep docker preflight cheap 2026-06-02 02:48:41 +01:00
Vincent Koc
b06dc17537 refactor: share gateway e2e test setup 2026-06-02 03:40:29 +02:00
Vincent Koc
7967a3582c fix(e2e): isolate onboard gateway logs 2026-06-02 03:39:10 +02:00
Vincent Koc
2e6016fdec fix(ci): keep crabbox pnpm hydrate off tmpfs 2026-06-02 03:38:51 +02:00
Peter Steinberger
8a1a8ea8a3 ci(release): wait out live provider rate limits 2026-06-02 02:38:22 +01:00
Vincent Koc
4608f7dcf9 refactor: share probe auth test fixtures 2026-06-02 03:29:33 +02:00
Vincent Koc
49ac93bda6 refactor: share talk session response helpers 2026-06-02 03:20:00 +02:00
Peter Steinberger
f6653b9b35 fix(ci): retry live Docker image pulls 2026-06-02 02:08:26 +01:00
Vincent Koc
2f92fddef0 refactor: share node invoke wake test helpers 2026-06-02 03:02:03 +02:00
Vincent Koc
489efc8f5e refactor: share device token authz test fixtures 2026-06-02 02:58:38 +02:00
Vincent Koc
459abfc26b fix(e2e): isolate plugin sweep scratch files 2026-06-02 02:50:41 +02:00
Vincent Koc
340cc2c1e4 refactor: share session history test fixtures 2026-06-02 02:41:09 +02:00
Vincent Koc
be8cb5d4ea refactor: share agent wait dedupe test fixtures 2026-06-02 02:37:48 +02:00
Vincent Koc
222ade9fa6 fix(e2e): clean kitchen sink sweep state 2026-06-02 02:29:52 +02:00
Peter Steinberger
6667b9734a fix(ci): avoid rg dependency in changelog gate 2026-06-02 01:29:15 +01:00
Vincent Koc
ebbb2e8f01 refactor: share handshake auth helper test fixtures 2026-06-02 02:20:52 +02:00
Vincent Koc
dea3e835c5 refactor: share channel health policy test fixtures 2026-06-02 02:16:09 +02:00
Peter Steinberger
722af385d2 test(release): accept gateway schema rejection wrapper 2026-06-02 01:10:00 +01:00
Vincent Koc
dacd18a8aa refactor: share chat attachment test helpers 2026-06-02 02:00:15 +02:00
Vincent Koc
8a9acd2940 test(mac): exercise codesign entitlement use 2026-06-02 01:56:24 +02:00
Vincent Koc
bd8353dbaa fix(testing): fail plugin gauntlet on failed qa summaries 2026-06-02 01:52:00 +02:00
Vincent Koc
3baf78dd0a refactor: share node invoke approval test helpers 2026-06-02 01:51:08 +02:00
Vincent Koc
1ed7692d2f test(changelog): exercise attribution gate policy 2026-06-02 01:46:34 +02:00
Omar Shahine
12798eb789 fix(agents): avoid duplicate generated media fallback (#89220)
Treat targetless current-chat message-tool media telemetry as delivered for generated-media completion dedupe while preserving fallback delivery for mismatched provider/account/thread evidence.

Real behavior proof was added from the live iMessage generated-image run: inbound id 5805, exactly one outgoing media reply id 5806, and no follow-up generated-image fallback.

Co-authored-by: omarshahine <10343873+omarshahine@users.noreply.github.com>
Reviewed-by: @lobster
2026-06-01 16:46:14 -07:00
Omar Shahine
02192bd27f fix(imessage): keep typing active during tool work (#88948)
Keep iMessage native typing indicators alive through long tool-running gaps by bridging tool-start activity into the existing typing controller, while preserving typingMode and sendPolicy suppression semantics.

Real behavior proof was added from the live iMessage generated-image run: inbound id 5805, outgoing media reply id 5806, and requester-observed typing during the 84s tool path.

Co-authored-by: omarshahine <10343873+omarshahine@users.noreply.github.com>
Reviewed-by: @lobster
2026-06-01 16:45:46 -07:00
Vincent Koc
086274fd7e test(e2e): exercise onboard wizard exit status 2026-06-02 01:38:46 +02:00
Vincent Koc
ed07a7a2de refactor: share node pairing authz test setup 2026-06-02 01:33:11 +02:00
Vincent Koc
829fb5dcb3 fix(e2e): clean generated docker client state 2026-06-02 01:30:10 +02:00
Peter Steinberger
4c6285e8ff test(release): retry google tool-read failovers 2026-06-02 00:26:55 +01:00
Vincent Koc
7c52969d49 fix(e2e): clean plugin fixture servers on timeout 2026-06-02 01:17:08 +02:00
Vincent Koc
42d3acfc99 refactor: share ios approval push delivery 2026-06-02 01:09:58 +02:00
Vincent Koc
32f98d7fe8 fix(e2e): forward sighup in node watchdogs 2026-06-02 01:05:29 +02:00
Vincent Koc
4bd7421182 refactor: share gateway auth request guards 2026-06-02 00:56:06 +02:00
Vincent Koc
d91d8ff060 refactor: share chat abort test setup 2026-06-02 00:47:43 +02:00
Vincent Koc
af44fb9b6c fix(test): preserve vitest batch wrapper signals 2026-06-02 00:46:55 +02:00
Vincent Koc
45e0545e82 refactor: share gateway shutdown abort helpers 2026-06-02 00:44:16 +02:00
Peter Steinberger
2d17cb295d fix(discord): use libopus structured decode errors 2026-06-01 23:43:31 +01:00
Peter Steinberger
e8120a72e1 ci(release): retry quiet node shard stalls 2026-06-01 23:43:03 +01:00
Dallin Romney
0904f3e553 revert: undo gateway memory watch warning (#89246) 2026-06-01 15:32:42 -07:00
Vincent Koc
2770aa5f4c fix(scripts): clean boundary step process groups 2026-06-02 00:29:22 +02:00
Vincent Koc
285401ced8 refactor: share cli session history test helpers 2026-06-02 00:25:39 +02:00
Vincent Koc
64697fbe24 chore(release): add matrix plugin changelog 2026-06-02 00:23:41 +02:00
Vincent Koc
e9aae26b22 fix(test): clean live wrapper children 2026-06-02 00:19:53 +02:00
Vincent Koc
cb12a9af94 refactor: share node pairing request test helpers 2026-06-02 00:17:07 +02:00
Peter Steinberger
65d7fa2420 fix(memory): reattach Linux watchers on directory rename
(cherry picked from commit 0db7781514cc84fac4f3a999d24b4b747fc871f9)
2026-06-01 23:15:00 +01:00
Peter Steinberger
bd4a7f4119 fix(discord): classify corrupt opus packets structurally 2026-06-01 23:14:23 +01:00
Vincent Koc
14f61d0637 fix(test): clean delegated vitest runners 2026-06-02 00:09:20 +02:00
Vincent Koc
0f3a63b12e refactor: share preauth hardening test helpers 2026-06-02 00:07:53 +02:00
Peter Steinberger
a14eacf372 chore(release): set version 2026.6.2 2026-06-01 23:06:55 +01:00
Colin
646df2da83 fix skill workshop filtered fallback 2026-06-01 23:00:40 +01:00
Colin
211321ce5c address skill workshop review comments 2026-06-01 23:00:40 +01:00
Colin
a34e822cd4 fix skill workshop filtered navigation 2026-06-01 23:00:40 +01:00
Colin
8c180c9153 fix(ui): render skill workshop tab 2026-06-01 23:00:40 +01:00
Vincent Koc
990f0baff9 fix(e2e): scope gateway cleanup to tracked pid 2026-06-01 23:59:03 +02:00
Peter Steinberger
bd8baeb323 perf(gateway): narrow plugin lookup memo key 2026-06-01 22:58:46 +01:00
Vincent Koc
0771bbbd20 refactor: share discovery runtime test setup 2026-06-01 23:50:24 +02:00
Vincent Koc
74cf5c7e7d refactor: share session permission client setup 2026-06-01 23:48:17 +02:00
Vincent Koc
0cfd6b0504 fix(e2e): clean timed-out docker harness containers 2026-06-01 23:45:56 +02:00
Peter Steinberger
4e45010203 ci(release): fail fast on red release children
(cherry picked from commit 8d7038775f0a0a1bb5354ba6b6b708c6b2c3167b)
2026-06-01 22:42:53 +01:00
Vincent Koc
afdf9aaea0 refactor: share talk config test helpers 2026-06-01 23:34:51 +02:00
Vincent Koc
72ed2121f8 fix(scripts): guard delayed docker package kills 2026-06-01 23:33:00 +02:00
Dallin Romney
2405bbcbaf fix(memory): warn on gateway watcher FD risk (#89185)
* fix(memory): default gateway memory watch off

* fix(memory): warn on gateway watcher fd risk

* fix(config): avoid warning helper narrowing

* fix(config): remove redundant warning boolean cast

* docs(memory): clarify watcher default wording

* docs(memory): simplify watcher warning copy

* fix(config): scope watcher warning to local gateway
2026-06-01 14:23:25 -07:00
Vincent Koc
403190572b fix(e2e): isolate release media memory artifacts 2026-06-01 23:19:47 +02:00
Vincent Koc
67983a00c8 refactor: share session reset model test helpers 2026-06-01 23:12:52 +02:00
Vincent Koc
61aa499b53 test(scripts): trap test-state temp homes 2026-06-01 23:09:58 +02:00
Vincent Koc
420450b5cb fix(ci): timeout dependency guard GitHub requests 2026-06-01 22:59:55 +02:00
Kevin Lin
f8491b0fcf enhance(slack): route plugin approvals through native UI
Route Slack plugin approval delivery through the shared native approval route gates while preserving Slack Block Kit buttons and plugin resolver semantics.

Verification: Slack/native approval unit tests, Slack QA Lab, and live clawd native plugin approval via Slack desktop.
2026-06-01 13:55:59 -07:00
Vincent Koc
98e943ebdd refactor: share voicewake model test helpers 2026-06-01 22:53:29 +02:00
Vincent Koc
f8d5f162a1 fix(ui): terminate child on wrapper shutdown 2026-06-01 22:37:25 +02:00
Vincent Koc
a2fdd5bc70 refactor: share session delete lifecycle test helpers 2026-06-01 22:31:06 +02:00
Vincent Koc
2af2111ae0 refactor: share session history test helpers 2026-06-01 22:28:14 +02:00
Vincent Koc
c9d35c7172 fix(scripts): forward run-with-env termination 2026-06-01 22:24:34 +02:00
Dallin Romney
50b69e16dc fix(agents): dispatch auth failures by type (#89181) 2026-06-01 13:23:05 -07:00
Vincent Koc
fe97c6000c refactor: share browser auth test helpers 2026-06-01 22:19:07 +02:00
Dallin Romney
a99cbf29bd test: reset gateway timers at test boundaries (#89212) 2026-06-01 13:13:08 -07:00
Vyctor H. Brzezowski
05ea36a81f docs: refresh ClawHub showcase cards (#88734) 2026-06-01 13:08:56 -07:00
Vincent Koc
eb58c88598 refactor: share model catalog test helpers 2026-06-01 21:58:49 +02:00
Dallin Romney
5a67c5c556 fix(memory-core): reduce Linux watcher fan-out (#89188)
* fix(memory-core): reduce Linux watcher fan-out

* fix(memory-core): satisfy watcher type and lint checks

* fix(memory-core): harden Linux watcher subtree races
2026-06-01 12:54:30 -07:00
NianJiu
5a55135146 fix(memory): retry transient FileProvider-backed reads (#85351) 2026-06-01 12:40:20 -07:00
Vincent Koc
193988bc5b fix(e2e): isolate onboard temp artifacts 2026-06-01 21:25:03 +02:00
Vincent Koc
a20f57bf2e refactor: share startup auth test assertions 2026-06-01 21:16:55 +02:00
Vincent Koc
66f797b22c fix(e2e): wait for plugin update registry cleanup 2026-06-01 21:01:26 +02:00
Vincent Koc
65a805ac28 fix(e2e): harden web search cleanup 2026-06-01 20:35:33 +02:00
Vincent Koc
b18bab0bcc refactor: share session kill http test fixtures 2026-06-01 20:35:08 +02:00
Alexzhu
9ac30b587e Keep machine-readable CLI startup output parseable (#88689)
Constraint: CLI startup progress can render before Commander resolves a command's JSON output contract.

Rejected: Leaving Clack on its default stdout | contaminates JSON stdout when startup progress appears.

Confidence: high

Scope-risk: narrow

Directive: Keep progress output off stdout before full command parsing for machine-readable invocations.

Tested: git diff --check origin/main; OPENCLAW_HEAVY_CHECK_LOCK_SCOPE=worktree OPENCLAW_VITEST_MAX_WORKERS=1 node scripts/run-vitest.mjs src/cli/progress.test.ts src/cli/run-main.exit.test.ts; source CLI sessions --json parse proof.

Not-tested: broad pnpm check.
2026-06-01 11:33:22 -07:00
Peter Steinberger
82de264710 test(release): tolerate MiniMax portal nonce drift 2026-06-01 19:30:46 +01:00
Vincent Koc
7f7f0775ed fix(testing): keep crabbox sync checkouts durable 2026-06-01 20:30:08 +02:00
Vincent Koc
30819ed3da refactor: share http endpoint test scaffolding 2026-06-01 20:25:40 +02:00
Vincent Koc
1c3095e029 test(deps): clean dependency evidence temp roots 2026-06-01 20:20:42 +02:00
Vincent Koc
62cfc613f1 refactor: share startup early test inputs 2026-06-01 20:17:30 +02:00
Dallin Romney
64a946ac21 fix(agents): actionable copy for exhausted auth-profile failover (#85798)
* fix(agents): actionable copy for exhausted auth-profile failover

The pi-embedded runner threw a generic "No available auth profile for
<provider> (all in cooldown or unavailable)" message whenever every
configured profile was in cooldown, even though the failover machinery
had already resolved a concrete reason (auth, billing, rate_limit,
session_expired, etc.). The user-facing copy never used that reason and
never told the user how to recover.

Route the resolved reason through a single presenter
(`formatAuthProfileFailureMessage`) that composes a reason-specific
sentence with `buildProviderAuthRecoveryHint`, so FailoverError.message
ships with the right `openclaw models auth login --provider <id>` hint
when the cause is authentication/session/billing, and falls back to the
underlying provider error text otherwise. Helper moved out of
`src/commands/` into `src/agents/` because `src/agents/` cannot depend
on `src/commands/`.

* fix(agents): soften auth-profile failure copy for non-technical users

* refactor(agents): drop guidance re-export shim and de-brittle failure-copy tests

- Delete `src/commands/provider-auth-guidance.ts` and point doctor-auth, auth-choice.model-check, and models/list.status-command directly at `src/agents/provider-auth-recovery-hint.ts`. The cold-imports test moves with it.
- Rewrite `failure-copy.test.ts` to assert behavior (recovery-hint dispatch, provider mention, cause-suffix dedup) instead of pinning exact long copy strings, so wording tweaks no longer require a test update in two places.
2026-06-01 11:16:25 -07:00
Vincent Koc
96187089d4 refactor: share session history message fixtures 2026-06-01 20:05:18 +02:00
Vincent Koc
965e680603 test(control-ui): clean i18n timeout temp dirs 2026-06-01 20:03:05 +02:00
Vincent Koc
1cf39a2d6f refactor: table-drive lifecycle state tests 2026-06-01 19:57:35 +02:00
Vincent Koc
92b3d52e8a fix(e2e): isolate release media temp files 2026-06-01 19:56:05 +02:00
Dallin Romney
8ba6dfeaf6 fix(ci): restore dist cache before artifact builds (#89169) 2026-06-01 10:55:27 -07:00
Peter Steinberger
bddcf4448c fix(subagents): rotate steered restart sessions 2026-06-01 18:50:36 +01:00
Vincent Koc
c8a67768e3 fix(e2e): require expected web search rejection 2026-06-01 19:49:11 +02:00
Vincent Koc
26e61b2087 refactor: share single-row cache test helpers 2026-06-01 19:48:19 +02:00
Vincent Koc
ee48028028 fix(dev): clean tui pty watch children 2026-06-01 19:40:42 +02:00
Vincent Koc
3c324590ae refactor: share compaction checkpoint test helpers 2026-06-01 19:33:41 +02:00
Vincent Koc
ba88b7a178 fix(e2e): clean plugin lifecycle temp state 2026-06-01 19:27:04 +02:00
Vincent Koc
d767e296e2 refactor: share plugin node auth test helpers 2026-06-01 19:26:59 +02:00
Vincent Koc
83cd3cbe2a fix(e2e): bound bundled plugin lifecycle commands 2026-06-01 19:18:26 +02:00
Vincent Koc
16807824cc refactor: share node invoke approval test helpers 2026-06-01 19:18:14 +02:00
Dallin Romney
e3d24faecd fix: allow admins to approve dependency guard (#88966)
* fix: allow admins to approve dependency guard

* fix: auto-bypass trusted dependency authors
2026-06-01 10:17:14 -07:00
Peter Steinberger
469bec97ef test(codex): keep live subagent smoke lightweight 2026-06-01 18:09:48 +01:00
Vincent Koc
101db565ca refactor: share startup plugin test helpers 2026-06-01 19:09:39 +02:00
Vincent Koc
ef26e8dfce fix(repro): clean webchat tts proof artifacts 2026-06-01 19:04:12 +02:00
Vincent Koc
25c19e013a refactor: share startup memory test helpers 2026-06-01 19:00:26 +02:00
Vincent Koc
f2eea90dac fix(e2e): bound cron mcp probe waits 2026-06-01 18:52:13 +02:00
Vincent Koc
3113fe95ea refactor: share startup secrets test helpers 2026-06-01 18:49:58 +02:00
Vincent Koc
4e1f8b8ac7 fix(e2e): clean timed-out runtime commands 2026-06-01 18:43:25 +02:00
Vincent Koc
0b8f6b81e6 refactor: share probe request dispatch helper 2026-06-01 18:35:38 +02:00
Vincent Koc
ab1042d115 refactor: share talk transcription relay test setup 2026-06-01 18:34:05 +02:00
Peter Steinberger
9153aab037 fix(codex): abort app-server thread startup cleanly 2026-06-01 17:33:00 +01:00
Vincent Koc
285a792aa8 refactor: share maintenance test fixtures 2026-06-01 18:25:54 +02:00
Vincent Koc
a8bc1716dd fix(usage): skip empty timeseries scans 2026-06-01 18:20:52 +02:00
Vincent Koc
373ef81e83 refactor: share codex harness model assertions 2026-06-01 18:12:11 +02:00
Vincent Koc
c053b90290 refactor: share shared auth rotation test helpers 2026-06-01 18:10:57 +02:00
Pavan Kumar Gondhi
fbdf593778 fix: bound remote media reference reads [AI] (#88974)
* fix: bound remote media reference reads

* fix: remove unreachable video timeout wiring

* test: cover remote video reference handoff
2026-06-01 21:35:40 +05:30
Vincent Koc
488b65ab87 refactor: share session reset test helpers 2026-06-01 18:00:39 +02:00
Peter Steinberger
6668eb8225 test(codex): drop unused live harness helper 2026-06-01 16:56:48 +01:00
Vincent Koc
72436217ff fix(e2e): isolate MCP channel client temp state 2026-06-01 17:51:04 +02:00
Peter Steinberger
460cf7ed75 test(codex): avoid sessions list wait in live harness start probe 2026-06-01 16:49:20 +01:00
Vincent Koc
461999c060 fix(dev): clean Telegram flow previews on failure 2026-06-01 17:37:15 +02:00
Vincent Koc
9cb347e4c3 fix(dev): close gateway smoke websocket on failures 2026-06-01 17:26:15 +02:00
Vincent Koc
1d7e5f48ed fix(dev): close stalled gateway websocket handshakes 2026-06-01 17:18:40 +02:00
Vincent Koc
1fd2259e28 refactor: share config patch test helpers 2026-06-01 17:15:48 +02:00
Peter Steinberger
3f54d150b3 test(openrouter): stabilize music timeout clamp assertion 2026-06-01 16:09:23 +01:00
Vincent Koc
a9866a405c test(agents): align provider auth alias fixtures 2026-06-01 17:08:31 +02:00
Vincent Koc
0b9187c780 test(gateway): fix node invoke capture race 2026-06-01 17:08:31 +02:00
Vincent Koc
b1ec23e05f fix(e2e): escalate stuck PTY children 2026-06-01 17:07:42 +02:00
Vincent Koc
050f0c0af6 refactor: share device pair authz test helpers 2026-06-01 16:58:10 +02:00
Vincent Koc
dfeb5b81ca fix(e2e): harden Parallels helper cleanup 2026-06-01 16:57:27 +02:00
Vincent Koc
d9f6e03e32 refactor: share silent reconnect test helpers 2026-06-01 16:54:10 +02:00
Peter Steinberger
fed7d1f385 test(release): stabilize beta validation regressions 2026-06-01 15:47:56 +01:00
Vincent Koc
0a9e594420 fix(scripts): clean Anthropic prompt probe temp state 2026-06-01 16:47:27 +02:00
Sally O'Malley
c1ce51546e fix(ui): clear chat composer after send (#89106) 2026-06-01 10:42:35 -04:00
Vincent Koc
1b928592ef refactor: share startup recovery test helpers 2026-06-01 16:37:09 +02:00
Vincent Koc
12087ac9d4 test(e2e): exercise Parallels smoke cleanup path 2026-06-01 16:33:11 +02:00
Peter Steinberger
00caead80a test: close oxlint signal readiness race 2026-06-01 10:26:08 -04:00
Peter Steinberger
4b54a423f0 test: harden changed-gate assertions 2026-06-01 10:26:08 -04:00
Peter Steinberger
bdd6cf3d5e test: stabilize order-sensitive assertions 2026-06-01 10:26:08 -04:00
Peter Steinberger
cb7a4239ef fix: stabilize full-suite regressions 2026-06-01 10:26:08 -04:00
Peter Steinberger
b226a752a1 test: stabilize slow shard regressions 2026-06-01 10:26:08 -04:00
Vincent Koc
110f7d55e3 fix(scripts): clean Z.AI fallback repro temp state 2026-06-01 16:25:05 +02:00
Vincent Koc
645c7dc40b refactor: share gateway misc test helpers 2026-06-01 16:18:22 +02:00
Vincent Koc
a4847297b8 fix(ci): clean check-changed pnpm shim temp dirs 2026-06-01 16:16:26 +02:00
Vincent Koc
4253517070 refactor: share node allowlist test helpers 2026-06-01 16:14:59 +02:00
Peter Steinberger
e8c126eaf2 fix(ci): use QA runtime build for release checks 2026-06-01 15:12:50 +01:00
Peter Steinberger
2075d19923 test(gateway): scope lazy server mock 2026-06-01 15:12:50 +01:00
Vincent Koc
9e58ef1c82 test(scripts): clean session log temp roots 2026-06-01 16:00:41 +02:00
Vincent Koc
eaeccf5fdf refactor: share node registry system run test helpers 2026-06-01 16:00:36 +02:00
Vincent Koc
2c0e835b48 test(codex): clean up fake timer spies 2026-06-01 14:57:47 +01:00
Vincent Koc
b942a958b3 test(qa): cover QA lab help runtime boundary 2026-06-01 15:54:16 +02:00
Vincent Koc
42bcf9cd0b fix(test): keep runtime tests raw-sync safe 2026-06-01 15:53:37 +02:00
Vincent Koc
a0fbb6cfe2 fix(test): keep app parity checks sparse safe 2026-06-01 15:53:37 +02:00
Vincent Koc
408fa6e951 fix(test): stabilize watch-node shutdown tests 2026-06-01 15:53:37 +02:00
Vincent Koc
671909d6d3 refactor: share server aux reload test helpers 2026-06-01 15:51:05 +02:00
Vincent Koc
409f78a1ea fix(e2e): clean OTEL collector startup failures 2026-06-01 15:46:02 +02:00
Vincent Koc
3e592a8bd7 refactor: share mcp http loopback test helpers 2026-06-01 15:39:28 +02:00
Vincent Koc
e895479a21 fix(ci): fail gateway watch spawn errors promptly 2026-06-01 15:38:16 +02:00
Peter Steinberger
930bc9691b fix(ci): page CI timing job reads 2026-06-01 14:33:39 +01:00
Vincent Koc
b9f181635f fix(ci): fail gateway CPU spawn errors 2026-06-01 15:27:13 +02:00
Vincent Koc
c2aaf8afec refactor: share sessions patch test helpers 2026-06-01 15:17:55 +02:00
Vincent Koc
cbc5f277bb refactor: share session reset hook test helpers 2026-06-01 15:11:10 +02:00
Vincent Koc
44b388f863 fix(e2e): keep kitchen-sink process snapshots wide 2026-06-01 15:09:33 +02:00
Vincent Koc
c0e49a2c52 fix(e2e): catch runtime package-manager descendants 2026-06-01 14:58:39 +02:00
Peter Steinberger
c1e132195d test(release): activate manifest channels in bundle smoke 2026-06-01 13:51:38 +01:00
Vincent Koc
5bd8dbd0b8 refactor: share system run approval test helpers 2026-06-01 14:44:46 +02:00
Vincent Koc
421ea1f458 fix(e2e): bound Parallels host VM commands 2026-06-01 14:41:46 +02:00
Vincent Koc
1f91e97353 refactor: share startup secrets test helpers 2026-06-01 14:31:58 +02:00
Vincent Koc
d4f6e0a1f2 fix(docs): clean link audit temp docs 2026-06-01 14:26:21 +02:00
Peter Steinberger
ec2455a842 test(memory): drive timeout tests with explicit fake clocks
(cherry picked from commit d75eea53c9)
2026-06-01 13:12:07 +01:00
Vincent Koc
1742f3f77c refactor: share mcp http test helpers 2026-06-01 14:10:41 +02:00
Vincent Koc
5117f457bb fix(ci): clean gateway watch temp home 2026-06-01 14:09:58 +02:00
Vincent Koc
8fe5e83462 refactor: share sessions list changed test helpers 2026-06-01 14:00:20 +02:00
Vincent Koc
27097bed65 fix(ci): bound deadcode knip scan 2026-06-01 13:57:16 +02:00
Vincent Koc
1849a86dd2 refactor: share session history revocation helpers 2026-06-01 13:47:39 +02:00
Vincent Koc
5280d1d95d fix(e2e): stream Parallels phase logs 2026-06-01 13:46:21 +02:00
Vincent Koc
bcdc93d651 refactor: share auth compat backend scope assertion 2026-06-01 13:31:03 +02:00
Vincent Koc
0751b6f2c9 fix(e2e): bound upgrade survivor config commands 2026-06-01 13:30:23 +02:00
Peter Steinberger
7d9fae5b3a fix(memory): keep embedding timeout watchdog active
(cherry picked from commit 591f310869)
2026-06-01 12:29:27 +01:00
Vincent Koc
a595aba60e refactor: share sessions send result assertions 2026-06-01 13:21:09 +02:00
Vincent Koc
75645aec08 fix(e2e): clean Telegram proof child processes 2026-06-01 13:20:03 +02:00
Vincent Koc
d10d71cdb6 fix(codex): stabilize app-server cleanup tests 2026-06-01 13:15:05 +02:00
Vincent Koc
c69a8d633d perf(control-ui): hydrate chat startup state
Add a combined chat.startup gateway method for Control UI startup hydration so first chat load can receive history and agents in one RPC, while falling back to chat.history for older/unadvertised gateways. Verified with focused UI/gateway tests, tsgo/oxlint/diff checks, clean autoreview, and Testbox changed gate tbx_01kt1dt6fqdtdbprsk48z8fn71.
2026-06-01 12:14:19 +01:00
Vincent Koc
d8ebbedf45 refactor: share plugin http auth request assertions 2026-06-01 13:10:09 +02:00
Peter Steinberger
9ed1766696 test(whatsapp): align direct last-route envelope
(cherry picked from commit 5d902b0f20)
2026-06-01 12:04:51 +01:00
Vincent Koc
bed0fb7bad refactor: share session resolve assertions 2026-06-01 13:00:51 +02:00
Vincent Koc
db6fc20559 fix(e2e): clean Windows background smoke timeouts 2026-06-01 12:55:15 +02:00
Vincent Koc
1364acbe4c refactor: share gateway http stage error assertions 2026-06-01 12:45:20 +02:00
Vincent Koc
d2988e0248 refactor: share preview resolve alias fixtures 2026-06-01 12:42:30 +02:00
Vincent Koc
8c8c8c8e32 perf(control-ui): prioritize first connect startup (#89030)
* perf(control-ui): prioritize first connect startup

* fix(control-ui): close connect timing gaps

* fix(control-ui): default embeds strict before bootstrap

* fix(control-ui): keep bootstrap identity deferred

* fix(control-ui): gate startup chat on bootstrap

* fix(control-ui): restore composer after hello

* fix(control-ui): restore drafts before hello
2026-06-01 11:41:22 +01:00
Vincent Koc
8bee3be90a fix(e2e): bound Parallels fresh lanes 2026-06-01 12:34:29 +02:00
Vincent Koc
87d890003d refactor: share shutdown drain session setup 2026-06-01 12:31:32 +02:00
Peter Steinberger
aed7de306e fix(qa-matrix): detect sqlite dedupe commits by payload
(cherry picked from commit 2fc497e67b)
2026-06-01 11:27:10 +01:00
Vincent Koc
859cb52b44 refactor: share unauthorized response assertions 2026-06-01 12:22:58 +02:00
Vincent Koc
4685a84e9b fix(e2e): bound bundled runtime gateway cleanup 2026-06-01 12:19:37 +02:00
Vincent Koc
f30235bed2 test: fix gateway test type fixtures 2026-06-01 12:13:36 +02:00
Vincent Koc
4f8f6c7693 refactor: share thinking e2e session setup 2026-06-01 12:13:36 +02:00
Peter Steinberger
055063f06b fix(qa-matrix): read sqlite inbound dedupe state 2026-06-01 11:07:53 +01:00
Vincent Koc
dac33c8ecb fix(e2e): cap pty transcript output 2026-06-01 11:49:58 +02:00
Vincent Koc
75ebf1c870 refactor: share device token authz test helpers 2026-06-01 11:49:06 +02:00
Vincent Koc
e4a32b9e8e lint(e2e): remove redundant channel fallback 2026-06-01 11:38:28 +02:00
Vincent Koc
22e3b2e94e fix(dev): wait for watch-node shutdown 2026-06-01 11:38:28 +02:00
Peter Steinberger
729420c34a test: split slow vitest shards 2026-06-01 05:34:59 -04:00
Peter Steinberger
0b5be66ef7 perf(gateway): trim startup plugin planning work 2026-06-01 10:33:28 +01:00
Peter Steinberger
8e28c773fe chore(release): prepare 2026.6.1 2026-06-01 10:30:15 +01:00
Vincent Koc
2dcb681f38 refactor: share session search test fixtures 2026-06-01 11:28:59 +02:00
Peter Steinberger
e733774e3c fix(test): repair telegram prerelease blockers 2026-06-01 10:26:12 +01:00
Mason Huang
004835f4c7 fix(plugins): block untrusted workspace setup-only channel loads (#86953)
Summary:
- This PR blocks disabled workspace-origin channel plugins from setup-only scoped imports, rejects their channel registrations at registry assembly, documents the trust rule, and adds regression coverage.
- PR surface: Source +46, Tests +610, Docs +13. Total +669 across 22 files.
- Reproducibility: yes. source inspection gives a high-confidence reproduction path: current main's setup-only ... ce channel plugin can be imported before this PR. I did not run the repro locally in this read-only review.

Automerge notes:
- PR branch already contained follow-up commit before automerge: test(plugins): cover workspace channel registry guard
- PR branch already contained follow-up commit before automerge: fix(plugins): isolate setup channel registration errors
- PR branch already contained follow-up commit before automerge: fix(channels): mark raw catalog listing internal
- PR branch already contained follow-up commit before automerge: test(channels): cover trusted catalog filtering
- PR branch already contained follow-up commit before automerge: test(channels): mock raw catalog helper
- PR branch already contained follow-up commit before automerge: docs(changelog): credit setup channel hardening

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

Prepared head SHA: 11438bc1a0
Review: https://github.com/openclaw/openclaw/pull/86953#issuecomment-4545730044

Co-authored-by: masonxhuang <masonxhuang@tencent.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Mason Huang <masonxhuang@tencent.com>
Co-authored-by: Sebastien Tardif <sebtardif@ncf.ca>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: hxy91819
Co-authored-by: hxy91819 <8814856+hxy91819@users.noreply.github.com>
2026-06-01 09:25:56 +00:00
Vincent Koc
97d373ff37 perf(ui): speed up first global chat sends
Speed up Control UI first global chat sends by letting safe literal-global startup refresh use the fresh hello default before agents.list finishes, while keeping stale carried/cached agent ids out of that fast path. Adds chat history/send and gateway chat.send timing markers for the next latency pass.
2026-06-01 10:25:22 +01:00
Vincent Koc
3119f08009 fix(scripts): bound shrinkwrap npm commands 2026-06-01 11:23:20 +02:00
Peter Steinberger
9d55fc4579 fix(plugins): skip peer links in rollback snapshots 2026-06-01 10:18:30 +01:00
Vincent Koc
2bac970abc refactor: share node invoke policy test setup 2026-06-01 11:17:38 +02:00
Vincent Koc
f8e9ba3718 fix(codex): prevent aborted app-server turn handles 2026-06-01 10:12:36 +01:00
Vincent Koc
26aaf03719 fix(scripts): clean control ui i18n timeouts 2026-06-01 11:10:57 +02:00
Vincent Koc
e85be626a4 refactor: share plugin runtime scope test setup 2026-06-01 11:07:29 +02:00
Vincent Koc
9cb052ccef refactor: share plugin http route test setup 2026-06-01 10:56:09 +02:00
Peter Steinberger
637b073119 test(ui): update gateway session chat mock 2026-06-01 04:53:51 -04:00
Vincent Koc
174e7711f3 fix(build): clean CLI startup metadata timeouts 2026-06-01 10:52:27 +02:00
Vincent Koc
b13af38f99 perf(ui): trace chat first output latency
Add chat-send first visible assistant output telemetry in the Control UI, plus Gateway diagnostics correlation attributes for chat.send dispatch spans. Verified with focused UI/Gateway tests, tsgo, oxlint, autoreview, PR checks, and Testbox-through-Crabbox check:changed.
2026-06-01 09:47:45 +01:00
Vincent Koc
4094c94a8f refactor: share event loop health expectation 2026-06-01 10:47:05 +02:00
Peter Steinberger
32113e38ab perf(ci): speed up prompt snapshot checks 2026-06-01 04:44:41 -04:00
Peter Steinberger
07a425aa14 fix: preserve colon slash commands 2026-06-01 09:41:19 +01:00
Vincent Koc
db5bb1cbe7 refactor: share auth state test setup 2026-06-01 10:38:12 +02:00
Vincent Koc
947dde976c fix(release): bound plugin npm verification commands 2026-06-01 10:36:46 +02:00
Peter Steinberger
1d4c1ba56d fix: harden memory envelope sanitization
Co-authored-by: amittell <mittell@me.com>
2026-06-01 09:30:08 +01:00
Vincent Koc
de3ee3daa6 refactor: share auth context test helpers 2026-06-01 10:24:04 +02:00
Vincent Koc
61574eb50b perf(ui): keep chat draft local while typing (#88998) 2026-06-01 09:19:53 +01:00
Vincent Koc
e680604577 fix(e2e): clean telegram credential timeouts 2026-06-01 10:13:57 +02:00
Vincent Koc
2ea7c518a5 test(agents): avoid provider runtime in subagent spawn tests 2026-06-01 09:13:36 +01:00
Vincent Koc
7f95733bee refactor: share handshake locality test inputs 2026-06-01 10:12:30 +02:00
Peter Steinberger
a4196a4445 fix(ci): cache plugin sdk declarations safely 2026-06-01 04:09:07 -04:00
Vincent Koc
688634ccb9 refactor: share ws health test harness setup 2026-06-01 10:01:27 +02:00
Vincent Koc
060d4a4d2d test(gateway): widen live helper connect budget 2026-06-01 09:00:47 +01:00
Vincent Koc
f2d0fe6417 fix(release): clean cross-os process groups 2026-06-01 10:00:23 +02:00
Vincent Koc
6627b4fbdd perf(ui): guard chat composer controls
Reduce Control UI draft-update work by guarding chat composer controls while keeping locale, session, model, settings, and busy-state invalidation. Verification: focused UI tests, format/lint/typecheck, autoreview clean, and changed gate tbx_01kt12rgjs8c077p2s0wmcsbyf.
2026-06-01 08:56:14 +01:00
Peter Steinberger
3b64ea83e8 fix: migrate legacy OpenAI Codex lastGood auth state 2026-06-01 03:47:43 -04:00
Vincent Koc
1d62f4c014 fix(ci): satisfy scripts lint spread rule 2026-06-01 08:45:42 +01:00
Vincent Koc
3feeb95668 refactor: share minimal gateway test helpers 2026-06-01 09:44:48 +02:00
Vincent Koc
402e2bb81a perf(ui): guard chat transcript rerenders
Reduce Control UI draft-update work by guarding transcript group rendering while preserving assistant attachment availability invalidation. Verification: focused UI tests, format/lint/typecheck, autoreview clean, and changed gate tbx_01kt11qyc20ejbsbt8kd79bamx.
2026-06-01 08:41:04 +01:00
Peter Steinberger
bc470713bb fix(e2e): enable smoke-tested plugin channels 2026-06-01 08:38:50 +01:00
Vincent Koc
3322212f14 fix(ci): tolerate pnpm workspace state on Windows hydrate 2026-06-01 09:36:41 +02:00
Peter Steinberger
7591dc6f4b test(telegram): reset spooled polling handler state 2026-06-01 08:36:32 +01:00
Vincent Koc
6640d57b64 refactor: share websocket connection test harness 2026-06-01 09:29:43 +02:00
Vincent Koc
ac734d8e16 fix(e2e): clean package candidate timeouts 2026-06-01 09:22:07 +02:00
Vincent Koc
0ece07cc20 fix(test): wait for telegram timer flushes
Revert release-time extension lane isolation for Telegram and memory, and make Telegram timer-flush tests wait for async side effects after manually firing timers.

Verification:
- pnpm test:serial extensions/telegram/src/bot.create-telegram-bot.channel-post-media.test.ts extensions/telegram/src/bot.create-telegram-bot.media-group-skip-warning.test.ts extensions/telegram/src/bot.media.stickers-and-fragments.e2e.test.ts extensions/telegram/src/bot.media.downloads-media-file-path-no-file-download.e2e.test.ts test/vitest-scoped-config.test.ts
- pnpm exec oxfmt --check on touched files
- git diff --check on touched files
2026-06-01 08:17:33 +01:00
Vincent Koc
5e09113ede refactor: share selected global session test setup 2026-06-01 09:14:31 +02:00
Vincent Koc
bff66a3e49 perf(ui): skip closed slash menu rerenders
Reduce Control UI typing work by avoiding slash-menu rerenders for ordinary non-command drafts. Verification: focused UI tests, format/lint/typecheck, autoreview clean, and changed gate tbx_01kt1086xrbxfzm85vynsf25hq.
2026-06-01 08:14:16 +01:00
Vincent Koc
8071b06634 perf(ui): debounce chat draft persistence
Debounce draft-only Control UI chat composer persistence while snapshotting pending drafts so session changes and teardown still flush the correct state. Verified with focused UI lifecycle/composer tests, format, oxlint, tsgo core/UI test, clean autoreview, and PR checks.
2026-06-01 08:04:23 +01:00
Vincent Koc
61ffd6bc66 fix(ci): bootstrap raw changed gates from clean checkouts 2026-06-01 08:01:11 +01:00
Vincent Koc
474ec157bc test(scripts): use runner vitest resolver in expectations 2026-06-01 08:01:11 +01:00
Vincent Koc
1377fd82a9 refactor: share openai compat http test helpers 2026-06-01 08:55:28 +02:00
Vincent Koc
8fdb1d0f55 fix(e2e): stream Parallels fresh logs 2026-06-01 08:54:22 +02:00
Vincent Koc
68bfacae03 test(ci): wait for MCP tools list log 2026-06-01 07:49:01 +01:00
Vincent Koc
371617f9ed refactor: share gateway error response assertions 2026-06-01 08:42:59 +02:00
Vincent Koc
69b2c8bd15 perf(ui): record pending send paint timing (#88960) 2026-06-01 07:42:24 +01:00
Vincent Koc
c11ff35841 fix(e2e): bound Parallels update logs 2026-06-01 08:42:08 +02:00
Vincent Koc
ddbd595f2f fix(ci): link Windows hydrate node modules 2026-06-01 08:38:25 +02:00
Vincent Koc
01124cfca9 fix(e2e): clean secret proof timeouts 2026-06-01 08:30:17 +02:00
Vincent Koc
e8f3bce9f0 fix(ci): exempt child process test helper from sdk guard 2026-06-01 07:27:47 +01:00
Vincent Koc
cb0ad281ce perf(ui): cache chat transcript renders (#88952) 2026-06-01 07:27:08 +01:00
Vincent Koc
c429a3c472 fix(codex): skip stale bootstrap history without engine 2026-06-01 07:26:08 +01:00
Vincent Koc
444bdc4286 refactor: share child process test mock helper 2026-06-01 08:22:25 +02:00
Vincent Koc
28550c3847 fix(e2e): harden Parallels host timeouts 2026-06-01 08:15:34 +02:00
Vincent Koc
3e91c688ae fix(ui): scroll pending sends into view
Scroll the chat thread as soon as a submitted pending send is enqueued, so delayed `chat.send` ACKs no longer leave the user's just-sent message below the viewport.

Verification:
- focused UI Vitest suite: 86 tests passed
- oxlint, core tsgo, core-test tsgo, diff check
- Testbox changed gate: tbx_01kt0wspy1ks5wpb6kp5gr0512
- branch autoreview clean
2026-06-01 07:14:07 +01:00
Vincent Koc
4d49a76039 test(secrets): secure plugin exec fixtures 2026-06-01 07:11:28 +01:00
Vincent Koc
988ec0234e fix(agents): validate shell snapshots with trusted env 2026-06-01 07:11:28 +01:00
Vincent Koc
9a7e0d43da fix(codex): accept legacy app-server auth provider 2026-06-01 07:11:28 +01:00
Vincent Koc
f55ff8dd1b fix(codex): skip stale bootstrap history without engine 2026-06-01 07:11:28 +01:00
Vincent Koc
5314a39ee5 refactor: share usage UTC range assertions 2026-06-01 08:03:23 +02:00
Vincent Koc
44cad6f8a4 refactor: simplify wake APNs test mock 2026-06-01 07:59:17 +02:00
Vincent Koc
275caeb5f5 fix(ui): render pending sends in chat thread
Render submitted Control UI sends directly in the chat thread before the Gateway acknowledges `chat.send`.

Pending sends now share acknowledged user-message content rendering for text and attachments, stay searchable with active chat filters, and failed queued sends remain queue-only.

Verification:
- focused UI Vitest suite: 201 tests passed
- oxlint, core tsgo, core-test tsgo, diff check
- Testbox changed gate: tbx_01kt0vnr2bv55aa6x588r77x0z
- autoreview clean
2026-06-01 06:57:05 +01:00
Peter Steinberger
0f2732b066 test(release): isolate telegram extension vitest lane 2026-06-01 06:54:55 +01:00
Vincent Koc
59f1472bd5 refactor: share error coercion helper 2026-06-01 07:41:19 +02:00
Vincent Koc
630f0d6938 refactor: share push test response assertions 2026-06-01 07:36:51 +02:00
Peter Steinberger
6173a4babb docs(plugin-sdk): refresh API baseline 2026-06-01 06:29:51 +01:00
Vincent Koc
6a1b2e6463 refactor: share skills handler test helper 2026-06-01 07:27:52 +02:00
Vincent Koc
fb9e091852 fix(e2e): harden plugin gauntlet cleanup 2026-06-01 07:27:35 +02:00
Peter Steinberger
00399d6c75 test(release): repair beta validation blockers 2026-06-01 06:27:02 +01:00
Peter Steinberger
b23ace1d04 fix(agents): strip streamed reasoning tags (#88924) 2026-06-01 01:26:29 -04:00
Peter Steinberger
db4990d260 refactor: compact copilot sessions through sdk state
Route Copilot compaction through SDK-backed state, remove marker sidecars, preserve auth/session binding behavior in SQLite-backed plugin state, and route Copilot CLI budget compaction through native harness compaction.
2026-06-01 01:18:46 -04:00
Vincent Koc
4550cfa6a7 fix(qa): run plugin MCP probes from repo root 2026-06-01 07:13:24 +02:00
Chunyue Wang
c0195f7ed5 fix(diagnostics): clear embedded-run activity when recovery declares lane idle (#88820)
* fix(diagnostics): clear embedded-run activity when recovery declares lane idle

Stuck-session recovery transitions a lane to idle via the recovery
coordinator, but only mutated the session-state store. When an aborted
embedded run was removed without markDiagnosticEmbeddedRunEnded, the
activity store kept hasActiveEmbeddedRun set, so the liveness sweep
reported idle/embedded_run and isIdleQueuedRecoverableSessionStall
re-triggered recovery indefinitely.

Reconcile the activity store from the authoritative idle declaration by
clearing the session's embedded-run owners. The existing generation
guard already excludes any newer run that re-armed activity, so a live
requeued run is preserved.

* fix(diagnostics): reconcile tool/model activity on authoritative idle cleanup

clearDiagnosticEmbeddedRunActivityForSession (renamed from
clearDiagnosticEmbeddedRunsForSession) now clears the aborted run's tool and
model markers alongside the embedded-run owners, matching the default
markDiagnosticEmbeddedRunEnded teardown. Clearing only the owner set left the
lane as idle + orphaned tool/model activity, which
isIdleQueuedRecoverableSessionStall still treats as recoverable while work is
queued, so the liveness sweep kept re-triggering recovery instead of converging.
Adds regression cases with stale tool and model markers plus queued work.

* test(phone-control): align service mocks with keyed store API

* fix(diagnostics): preserve rearmed recovery activity

* fix(diagnostics): clear recovered owner markers

* fix(diagnostics): clear recovered embedded work keys

* fix(diagnostics): ignore stale same-key recovery owners

* fix(diagnostics): preserve same-session recovery rearm

* fix(diagnostics): ignore stale queued activity starts

* fix(diagnostics): record recovery cutoffs for empty activity

* fix(diagnostics): preserve fresh recovery markers

* fix(diagnostics): prune stale activity before fresh recovery block

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-06-01 01:07:35 -04:00
Tosko4
785849d395 fix(android): add notification app picker 2026-06-01 10:37:19 +05:30
Vincent Koc
12d5043913 refactor: share chat parentid test helpers 2026-06-01 07:06:05 +02:00
Peter Steinberger
d925249ac0 docs(plugin-sdk): refresh API baseline hash 2026-06-01 06:05:37 +01:00
Vincent Koc
74a075077c fix(e2e): harden docker all cleanup 2026-06-01 07:05:15 +02:00
Peter Steinberger
4e57546a87 test(memory): isolate qmd timer state in prerelease shard 2026-06-01 06:03:43 +01:00
Neerav Makwana
711ab45025 fix(agents): clear legacy auto fallback pins (#87484)
* fix(agents): clear legacy auto fallback pins

* fix(agents): repair legacy auto-fallback test mock and tighten review feedback

Add hasLegacyAutoFallbackWithoutOrigin to the live-model-switch agent-scope mock so the agents-core lane runs, simplify the redundant hasSessionModelOverride guard, use a single source of truth for the legacy-pin staleness check with a comment on the load-bearing modelKey guard, and add preservation/edge-case/guard regression coverage. Rename the misleading primary-probe agent test.

* style(agents): format rebased fallback fix

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-06-01 01:03:31 -04:00
Vincent Koc
e7e21caa20 fix(ui): keep first control chat sends responsive
Make first Control UI chat sends visibly queue during pending model saves, preserve early streaming deltas that arrive before chat.send ACK, and keep model-wait queued prompts scoped/retryable across session switches.
2026-06-01 05:59:04 +01:00
amittell
945faf8e67 fix(memory-lancedb): reject envelope metadata sludge
Summary:
- Strip memory-lancedb envelope and metadata sludge before auto-capture/recall, including pending history wrappers, current-message reply context, message-tool delivery hints, media annotations, and marker-free channel envelopes.
- Expose bundled chat-channel IDs/prefixes through the plugin SDK so sanitizer matching follows the channel catalog.
- Refactor cron tool schemas to fresh factory instances while preserving runtime nullable clears and provider-facing OpenAPI projection.

Verification:
- git diff --check origin/main...HEAD
- ./node_modules/.bin/oxfmt --check src/plugin-sdk/chat-channel-ids.ts src/plugin-sdk/chat-channel-ids.test.ts extensions/memory-lancedb/index.ts extensions/memory-lancedb/index.test.ts src/agents/tools/cron-tool.ts src/agents/tools/cron-tool.schema.test.ts
- pnpm plugin-sdk:api:check
- node scripts/run-vitest.mjs run src/plugin-sdk/chat-channel-ids.test.ts extensions/memory-lancedb src/agents/tools/cron-tool.schema.test.ts src/agents/tools/cron-tool.test.ts --reporter=dot
- pnpm lint:extensions --threads=8
- .agents/skills/autoreview/scripts/autoreview --mode branch --base origin/main
- GitHub checks on 62d1da1257: 139 pass, 0 pending, 0 fail, 22 skipped.
2026-06-01 00:57:25 -04:00
Vincent Koc
1aa1a70ac5 test(installer): isolate install shell HOME 2026-06-01 05:55:34 +01:00
Vincent Koc
abe2145153 refactor: share cron delivery test fixture 2026-06-01 06:52:52 +02:00
Vincent Koc
0ae0051ae7 feat(ui): improve Workboard task details
Make Workboard cards compact by moving expanded task/run metadata, proof, diagnostics, worker logs, automation, protocol state, events, and operator notes into a detail drawer.

Keep execution state simple and safe: active, linked, and archived cards avoid duplicate start paths; stale task cache is ignored when session lifecycle is authoritative; recent proof/events stay visible; dispatcher capacity distinguishes unclaimed review cards from claimed cards.
2026-06-01 05:52:40 +01:00
Vincent Koc
5957bfdc54 fix(e2e): fail bundled smoke on missing channels 2026-06-01 06:45:58 +02:00
Vincent Koc
e843a3612b refactor: inline secrets error response guard 2026-06-01 06:40:17 +02:00
Vincent Koc
8cab0f23f8 fix(e2e): clean bundled runtime smoke state 2026-06-01 06:35:28 +02:00
Vincent Koc
296cd8c912 fix(plugin-sdk): isolate provider catalog projection failures (#88767)
* fix(plugin-sdk): isolate provider catalog projection failures

* fix(plugin-sdk): share safe provider catalog projection

* fix(cron): preserve raw null clear schema

* fix(plugin-sdk): copy provider catalog model rows safely

* fix(plugin-sdk): keep id-only catalog models

* fix(plugin-sdk): require readable provider catalog base url

* fix(ci): satisfy cron and matrix lint checks

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-06-01 00:35:19 -04:00
Vincent Koc
3386bf989f refactor: share secrets resolve test helper 2026-06-01 06:31:03 +02:00
Peter Steinberger
5b79e81569 fix: harden CLI and plugin edge cases (#88896)
* fix: harden CLI and plugin edge cases

* fix: preserve explicit TTS provider credentials

* fix: preserve direct TTS credentials

* fix: type TTS credential hydration config

* fix: preserve scoped TTS channel credentials

* fix: pin hydrated TTS runtime config

* fix: satisfy TTS hydration lint

* fix: preserve inherited TTS provider keys

* fix: read resolved TTS provider keys
2026-06-01 00:30:12 -04:00
Vincent Koc
ec6ad888a4 fix(e2e): bound telegram proof commands 2026-06-01 06:26:44 +02:00
Kip
c213827aa5 fix(cron): include job name when reading single-job run history (#88294)
* fix(cron): include job name in single-job run history

The cron.runs gateway handler enriches log entries with jobName in the all-jobs scope, but the single-job scope did not pass any job-name lookup into the SQLite run-log reader. Entries returned for one job could therefore reach Control UI without jobName, making the run-history title fall back to the raw job id.

Build a one-entry jobNameById map for the current job and pass it through the same reader enrichment path used by all-jobs history. If the job no longer exists, the map stays undefined and existing fallback behavior is unchanged.

* test(cron): cover single-job run history job name enrichment

Asserts that readCronRunLogEntriesPage stamps a supplied jobNameById map onto single-job page entries, matching the gateway data shape used for both all-jobs and single-job cron.runs responses.

Addresses review feedback on #88294.

* test(cron): preserve nullable tool schema validation

* test(cron): assert runtime nullable tool schema

* test(cron): refresh prompt snapshots

---------

Co-authored-by: Kip Claw <kip@kipclaw.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-06-01 00:25:39 -04:00
Vincent Koc
dc9e67d2d4 refactor: share tools catalog test helpers 2026-06-01 06:13:33 +02:00
Peter Steinberger
b2a1c5caa8 test(matrix): keep async monitor callbacks lint-clean 2026-06-01 05:11:28 +01:00
Vincent Koc
51bad9b319 refactor: share config open file test helpers 2026-06-01 06:08:13 +02:00
Vincent Koc
fb17986af5 fix(ci): preserve hydrated Windows test deps 2026-06-01 06:08:10 +02:00
Vincent Koc
17245a0890 fix(test): bound qa otel smoke runs 2026-06-01 06:04:30 +02:00
Peter Steinberger
3b802a7fbc docs(plugin-sdk): refresh API baseline hash 2026-06-01 04:59:39 +01:00
Vincent Koc
e9c7a64c5e refactor: share update test helpers 2026-06-01 05:58:33 +02:00
Peter Steinberger
817c4ce4fc test(release): stabilize installer and matrix async checks 2026-06-01 04:55:21 +01:00
Vincent Koc
d4240cde5b refactor: share native hook relay test helpers 2026-06-01 05:48:14 +02:00
Ted Li
6cb06f5fbc fix(reply): preserve sessions_send external routes (#88803)
* fix(reply): preserve sessions_send external routes

* fix(reply): preserve inherited route thread ids

* fix(reply): keep sessions_send delivery single-owner

* fix(reply): satisfy dispatch route lint

* fix(reply): preserve inherited ACP route metadata

* test(reply): type inherited route event assertions

* test(ci): satisfy current lint rules

* fix(reply): avoid stale inherited route threads

* fix(reply): trust explicit inherited route threads

* fix(reply): require trusted route thread sources

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-31 23:43:05 -04:00
Firas Alswihry
70c59f59b2 feat(dreaming): score candidates with shadow trial results
Add report-only memory-core dreaming shadow trial scoring and ranking helpers. Keep rank lookup keyed by durable candidate keys and document the advisory behavior. Thanks @iFiras-Max1.
2026-05-31 23:40:20 -04:00
Vincent Koc
a30c12e711 refactor: share restart test helpers 2026-06-01 05:38:35 +02:00
Vincent Koc
56a7000b3e fix(mattermost): route send attachments through upload
Routes Mattermost send attachments through the upload path so local file paths and structured attachment media are uploaded instead of being posted as plain text. Preserves scoped media access for local uploads, rejects unsupported or ambiguous attachment payloads, and keeps HTTP media fallback behavior.

Fixes #87930.

Proof:
- autoreview clean
- node scripts/run-vitest.mjs extensions/mattermost/src/channel.test.ts extensions/mattermost/src/channel.message-adapter.test.ts extensions/mattermost/src/mattermost/send.test.ts src/infra/outbound/message-action-params.test.ts src/infra/outbound/outbound-send-service.test.ts src/infra/outbound/message-action-runner.media.test.ts src/media/load-options.test.ts
- pnpm prompt:snapshots:check
- GitHub Actions completed with no pending/failing checks for head 2a65cbb1ee
2026-05-31 23:38:17 -04:00
Vincent Koc
5054b20832 fix(test): harden secret provider proof cleanup 2026-06-01 05:37:45 +02:00
Vincent Koc
a5ee3569d3 test(ci): refresh cron prompt snapshots 2026-06-01 04:33:39 +01:00
Peter Steinberger
33349269fd fix: wake legacy cron jobs without enabled 2026-05-31 23:31:44 -04:00
Vincent Koc
2dcee8ac2b refactor: share webchat media audio fixture 2026-06-01 05:28:03 +02:00
Peter Steinberger
e2c9c06de1 fix: advance exact-boundary every schedules 2026-05-31 23:27:24 -04:00
Peter Steinberger
ebcdb637bb perf(memory-core): defer embedding engine startup imports 2026-06-01 04:22:22 +01:00
Peter Steinberger
592b6e2916 docs(config): refresh config baseline hash 2026-06-01 04:20:57 +01:00
Peter Steinberger
45b5f876dd fix: reject blank cron payloads 2026-05-31 23:20:04 -04:00
Vincent Koc
76fa1b99c3 fix(test): bound test group report runs 2026-06-01 05:17:27 +02:00
Vincent Koc
aab1e727c6 refactor: share chat abort authorization helpers 2026-06-01 05:16:37 +02:00
Vincent Koc
a46d331723 fix(ci): reword durable final alias comment 2026-06-01 04:12:46 +01:00
Vincent Koc
916ee82814 test(installer): isolate install shell snippets 2026-06-01 04:11:22 +01:00
Vincent Koc
fcc279e233 fix(test): avoid Vite runtime import in UI config helpers 2026-06-01 04:08:53 +01:00
Vincent Koc
9dd7f04b71 fix(ci): repair phone control and cron schema gates 2026-06-01 04:06:25 +01:00
Vincent Koc
6e985931de refactor: share models list test helper 2026-06-01 05:05:20 +02:00
Vincent Koc
dc1cfcc28d refactor: share tasks handler test helpers 2026-06-01 05:05:20 +02:00
Peter Steinberger
ee6373aa5f fix: preserve cron failure destination clears 2026-05-31 23:04:31 -04:00
Peter Steinberger
6deded6698 fix: raise bootstrap file default limit 2026-06-01 04:02:51 +01:00
Peter Steinberger
f879e3d6a0 docs(plugin-sdk): refresh API baseline hash 2026-06-01 04:01:25 +01:00
Vincent Koc
f42cf9059e fix(ci): repair phone control and cron schema gates 2026-06-01 04:00:18 +01:00
Andy Ye
c317fd2bd7 docs(imessage): document SSH wrapper TCC send failure (#88758) 2026-05-31 23:00:08 -04:00
Vincent Koc
be967545c5 fix(plugins): fail closed on trusted policy errors
Fail closed when bundled trusted tool policy registry, registration, owner id, evaluation, or decision reads fail, so malformed trusted-policy state cannot crash diagnostics or accidentally allow a tool call.

Route before-tool-call diagnostics through guarded trusted-policy readers and keep healthy no-op policy behavior unchanged.

Add focused host-hook contract and before-tool-call e2e coverage for the new fail-closed paths.

PR: #88394
2026-05-31 22:57:38 -04:00
Nayrosk
388ba3218b fix(ui): bypass service worker for top-level navigations
HTTP auth challenges (basic, digest, negotiate) only fire the browser's
native credentials dialog when the response comes straight from the
network. Service worker responses bypass the WWW-Authenticate flow, so
reverse-proxy deployments with HTTP auth in front of the gateway show
a bare 401 after the browser's HTTP-auth memory cache expires (e.g. on
full browser restart) — forcing users to clear site data to recover.

Skip event.request.mode === "navigate" so the browser handles those
requests natively. Offline navigation of the app shell is lost, but
the SPA cannot function without network (all API calls go to the
network), so the trade-off is acceptable.

Refs: #85939, #71669, #53274
2026-05-31 22:57:27 -04:00
Peter Steinberger
7722ade22e test(install): clear node lookup cache in floor check 2026-06-01 03:56:37 +01:00
Vincent Koc
b2b9fbe033 fix(test): bound mock OpenAI request bodies 2026-06-01 04:48:32 +02:00
Peter Steinberger
551c9637d8 fix(ios): polish iPad gateway setup 2026-06-01 03:47:09 +01:00
Vincent Koc
c5eddadd9d refactor: share channel start test helpers 2026-06-01 04:40:21 +02:00
Vincent Koc
98b8e85beb refactor: share agent wait dedupe test helpers 2026-06-01 04:35:37 +02:00
Vincent Koc
a9938907dc fix(test): harden MCP E2E proof checks 2026-06-01 04:34:25 +02:00
Peter Steinberger
4c824aa809 perf(phone-control): use startup config for expiry guard 2026-06-01 03:32:38 +01:00
Peter Steinberger
1e7510ae10 docs: continue inline comment pass (#88849)
Adds broad inline comments and JSDoc for CLI, cron, outbound/channel, plugin SDK, ACP, shared helpers, net policy, and related utility contracts. Proof: git diff --check on latest exact head plus focused cron tests passed; CI had no failing checks observed before merge attempt.
2026-05-31 22:32:28 -04:00
Peter Steinberger
4932391e8a fix(ui): scope global agent model controls 2026-05-31 22:25:43 -04:00
Vincent Koc
822864c539 refactor: share channel status test helpers 2026-06-01 04:24:57 +02:00
Vincent Koc
a7ae3f6707 refactor: share usage session test state setup 2026-06-01 04:24:57 +02:00
Dallin Romney
78165cc387 docs: clarify diffs language pack additions (#88865) 2026-05-31 19:24:45 -07:00
Peter Steinberger
44765cfabe fix(acpx): seed Codex ACP auth from API key 2026-05-31 22:24:29 -04:00
Vincent Koc
0c3644cb24 perf(ui): stream stable markdown blocks 2026-06-01 03:23:47 +01:00
Peter Steinberger
53a7545ae3 perf(phone-control): avoid disarmed startup state lookup 2026-06-01 03:19:08 +01:00
Andy Ye
921598442a fix(hooks): expose inbound reply metadata before dispatch
Fixes #88521.

Expose finalized inbound reply metadata on plugin-visible hook payloads so before_dispatch and message hooks can implement reply-aware behavior without channel-specific workarounds.
2026-05-31 22:15:17 -04:00
Peter Steinberger
e72def6983 Persist Discord thread bindings in SQLite (#88866)
* refactor: persist discord thread bindings in sqlite

* test: read discord thread bindings from sqlite smoke
2026-05-31 22:10:30 -04:00
ksj3421
45bdaa2f7b fix(agents): return schema lookup misses in-band
Return unknown config.schema.lookup paths as an in-band agent gateway tool result instead of throwing into channel warning surfaces.

The direct gateway RPC still reports INVALID_REQUEST, preserving the existing protocol contract, while the agent-facing gateway tool returns schema_path_not_found for exploratory misses.

Fixes #88813.
Thanks @ksj3421.
Reported by @cjalden.
2026-05-31 22:10:02 -04:00
Vincent Koc
91ca036717 test(agents): use neutral tool schema fixtures (#88848) 2026-05-31 22:09:48 -04:00
Ted Li
c002887223 fix(memory): rehydrate daily list promotions
* fix(memory): rehydrate daily list promotions

* fix(memory): preserve multi-line daily list promotions

* fix(memory): preserve daily list promotion context

* fix(memory): rehydrate capped daily list promotions

* test(memory): cover capped daily list promotion

* test(agents): update model selection mocks

* ci: ignore lazy three dependency

* fix(memory): skip heading-only rehydration

* fix(memory): preserve list rehydration mode

* fix(memory): match capped renamed heading bodies

* fix(memory): avoid duplicate tail heading matches

* fix(microsoft-foundry): satisfy provider lint

* perf(memory): precompute promotion heading context

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-31 22:08:35 -04:00
Peter Steinberger
912ea4897f fix: scope global in-flight history to default agent 2026-06-01 03:08:29 +01:00
Peter Steinberger
6ad73e173b fix: keep hidden runs out of visible chat state 2026-06-01 03:08:29 +01:00
Vincent Koc
6c73ffc51a fix(test): bound MCP code mode client responses 2026-06-01 04:05:23 +02:00
Dallin Romney
632447d66d test(ui): remove stylesheet grep tests (#88847) 2026-05-31 19:05:02 -07:00
Dallin Romney
4b56c44c02 test: consolidate plugin registration contracts (#88824) 2026-05-31 19:04:53 -07:00
Peter Steinberger
d86b6da012 fix: allow cron delivery clears 2026-05-31 22:04:25 -04:00
Vincent Koc
d2c5ad2b36 refactor: share commands test helpers 2026-06-01 04:01:02 +02:00
Dallin Romney
b097cec219 fix(microsoft-foundry): satisfy extension lint (#88855) 2026-05-31 18:58:56 -07:00
Vincent Koc
207359a056 fix(ci): repair current main checks
Summary:
- Guard child-session candidate lookup when the session store is absent.
- Refresh Talk UI and compaction rotation tests for current main.
- Clean up Microsoft Foundry provider lint that blocked the refreshed CI lane.

Verification:
- node scripts/run-vitest.mjs src/gateway/session-utils.test.ts ui/src/ui/views/chat.test.ts src/agents/agent-command.compaction-rotation.test.ts --reporter=dot
- node scripts/run-vitest.mjs extensions/microsoft-foundry/index.test.ts --reporter=dot
- node_modules/.bin/oxfmt --check --threads=1 extensions/microsoft-foundry/provider.ts src/gateway/session-utils.ts ui/src/ui/views/chat.test.ts src/agents/agent-command.compaction-rotation.test.ts
- node scripts/run-oxlint.mjs extensions/microsoft-foundry/provider.ts src/gateway/session-utils.ts ui/src/ui/views/chat.test.ts src/agents/agent-command.compaction-rotation.test.ts
- pnpm lint --threads=8
- autoreview clean
- GitHub checks on f96270ed7e: 135 success, 29 skipped, 1 neutral, 0 pending/failing

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
2026-05-31 21:57:07 -04:00
Rohit
3fc485ca92 fix(browser): isolate Chrome MCP pending attach aborts (#88305)
* fix(browser): isolate Chrome MCP pending attach aborts

* fix(browser): evict closing Chrome MCP sessions

* fix(browser): clean chrome mcp pending session lifecycle

* fix(browser): handle stale chrome mcp pending sessions

* fix(browser): serialize stale chrome mcp replacement

* fix(browser): skip cancelled chrome mcp attach

* fix(browser): retire timed-out chrome mcp pending sessions

* fix(browser): retire stale chrome mcp after readiness

* fix(browser): keep shared chrome mcp timeouts isolated

* fix(browser): bound stale chrome mcp ready retries

* fix(browser): narrow pending session lease release

* fix(browser): keep ephemeral probes out of pending attaches

* fix(foundry): satisfy provider lint

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-31 21:55:38 -04:00
Peter Steinberger
2b184ac3a0 docs(changelog): refresh 2026.5.31 notes 2026-06-01 02:52:49 +01:00
Vincent Koc
be1273182e refactor: share models auth status test helpers 2026-06-01 03:49:46 +02:00
Vincent Koc
c764eb96c4 fix(test): tolerate vanished RPC gateway teardown 2026-06-01 03:48:59 +02:00
Peter Steinberger
0369672691 feat(minimax): add m3 model support (#88860) 2026-05-31 21:47:47 -04:00
Vincent Koc
9919e4601f refactor: share skills clawhub test helpers 2026-06-01 03:38:39 +02:00
Vincent Koc
b6bac3cc2b test(agents): include Ollama in small live model matrix (#87838)
* test(agents): include Ollama in small live model matrix

* test: avoid Ollama cloud key in local live runs

* test: recognize Ollama env secret refs

* test: type Ollama live key fixtures

* test: prevent Ollama cloud auth in local live probes

* test: preserve equivalent Ollama live credentials

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-31 21:38:31 -04:00
Peter Steinberger
72bc9ae952 fix: keep cron update delivery validation scoped 2026-05-31 21:32:23 -04:00
Peter Steinberger
d2f1c0eac8 fix: harden cron validation and restart state 2026-05-31 21:32:23 -04:00
kiranmagic7
cc97eca9b1 test(installer): keep Node floor tied to package engine
Adds a focused installer regression test tying install.sh's accepted Node 22 floor to the package engine floor. Thanks @kiranmagic7.
2026-05-31 21:32:00 -04:00
Vincent Koc
dbc83b4213 refactor: share chat reply media test helpers 2026-06-01 03:29:30 +02:00
Vincent Koc
2d0c755013 fix(test): order unit-fast fake-timer project 2026-06-01 02:24:48 +01:00
Peter Steinberger
fb64546d9e fix: preserve no-policy native hook fallback
Keep selected no-policy Codex PreToolUse relay hooks installed with an explicit unavailable no-op marker, while unknown unavailable PreToolUse and PermissionRequest still fail closed.

Refs #87543.
Replaces #88620.

Verification:
- pnpm test extensions/codex/src/app-server/native-hook-relay.test.ts src/agents/harness/native-hook-relay.test.ts src/cli/native-hook-relay-cli.test.ts
- pnpm lint --threads=8
- autoreview --mode branch --base origin/main
- GitHub CI run 26729700996, Real behavior proof 26729874455, OpenGrep 26729701010, CodeQL high 26729701003

Co-authored-by: woodym-dotcom <266261448+woodym-dotcom@users.noreply.github.com>
2026-05-31 21:24:09 -04:00
EmpX2025
83f290005a feat(ios): support native iPad display
Make the iOS app a universal iPhone+iPad app by targeting device family 1,2 in the XcodeGen source of truth.

Update iOS docs and App Store metadata so user-facing copy no longer describes the app as iPhone-only.

Verification:
- git diff --check
- cd apps/ios && xcodegen generate
- xcodebuild -project apps/ios/OpenClaw.xcodeproj -scheme OpenClaw -configuration Debug -destination 'platform=iOS Simulator,id=410B81D3-784E-4A01-B69C-490B79EAFCEA' CODE_SIGNING_ALLOWED=NO build
- GitHub CI: Real behavior proof, macos-swift, macos-node, check-docs, preflight, security-fast, actionlint, no-tabs, dependency-guard, OpenGrep

Thanks @EmpX2025.
2026-05-31 21:23:33 -04:00
William Liu AI
8eeb9300df fix: restore in-flight TUI run switch-back
Restore TUI switch-back adoption for backgrounded visible chat-send runs by surfacing a bounded `chat.history.inFlightRun` snapshot.

The snapshot keeps the run id even when buffered text is empty or over budget, filters live text through the same projection path as streaming chat, scopes bare global history to the default agent, and excludes hidden internal agent runs.

Proof:
- node scripts/run-vitest.mjs run src/gateway/chat-abort.test.ts src/tui/tui-session-actions.test.ts
- node scripts/run-tsgo.mjs -p tsconfig.core.json
- pnpm --silent exec oxfmt --check src/gateway/chat-abort.ts src/gateway/chat-abort.test.ts src/gateway/server-methods/chat.ts src/tui/tui-session-actions.ts src/tui/tui-session-actions.test.ts
- .agents/skills/autoreview/scripts/autoreview --mode branch --base origin/main
- CI: Real behavior proof, TUI PTY, dependency guard, OpenGrep precise diff, workflow sanity passed on PR head 2b8bf5f214.

Co-authored-by: William Liu <william@williamliu.ai>
2026-05-31 21:22:52 -04:00
Vincent Koc
52c809a759 fix(infra): bridge WSL clipboard through shell
* fix(infra): bridge WSL2 clipboard through shell

* test(infra): assert wsl clipboard argv stays token-free

* fix(infra): keep wsl clipboard timeout ownership
2026-05-31 21:22:08 -04:00
elfka toruviel
f22e39823d fix(doctor): respect explicit PI runtime policy
Respect explicit PI/OpenClaw runtime policy when deciding whether Codex plugin diagnostics are actionable.

Diagnostics now use the resolved OpenAI route: intentional PI and custom OpenAI-compatible routes suppress only the missing `plugins.entries.codex` noise, while enabled/stale Codex policy still warns.

Proof: focused doctor/config/agent routing Vitest coverage, full lint, test types, dependency checks, isolated live doctor configs, autoreview clean, and GitHub CI green at c5a84de4ca.

Fixes #88706.

Co-authored-by: Elfka Toruviel <aeb31988340aa87b@toruviel.online>
2026-05-31 21:21:11 -04:00
Vincent Koc
30bde29893 refactor: share config auth test helpers 2026-06-01 03:20:04 +02:00
Peter Steinberger
6b940ed3ca perf: streamline chat startup metadata (#88825)
* perf: streamline chat startup metadata

* fix: defer global queued agent selection

* style: format gateway startup refresh
2026-05-31 21:18:41 -04:00
Andy Ye
1b10739d60 fix(agents): guard vanished workspaces
Fixes #88333

Preserves contributor workspace contents when an attested workspace disappears or is partially regenerated, and clears OpenClaw-owned attestation state on delete/reset/uninstall.

Co-authored-by: Andy Ye <35905412+TurboTheTurtle@users.noreply.github.com>
2026-05-31 21:14:54 -04:00
Vincent Koc
efd5d07734 fix(ci): preserve lint cleanup behavior 2026-06-01 03:12:53 +02:00
Peter Steinberger
1d4277b071 refactor: persist openrouter model cache in sqlite (#88851) 2026-05-31 21:12:43 -04:00
Vincent Koc
b029634bd6 refactor: share cron validation test helpers 2026-06-01 03:08:54 +02:00
Vincent Koc
af927038cc test(gateway): fail strict codex subagent timeouts 2026-06-01 03:08:02 +02:00
Peter Steinberger
5b0c4c0491 fix: align Foundry chat reasoning metadata 2026-06-01 02:05:38 +01:00
Vincent Koc
570e2db252 fix(plugins): isolate cached tool runtime siblings 2026-05-31 21:05:23 -04:00
Vincent Koc
53990d5bbf fix(plugins): isolate web provider factory failures (#88807) 2026-05-31 21:04:18 -04:00
NVIDIAN
37169697d7 fix(status): resolve gateway auth secrets for deep audit
Resolve gateway auth SecretRef targets in status deep audit.

The static secret target coverage now includes gateway auth and remote token/password keys for both status and security audit scans. Focused status/secret-target tests passed, Auto Review reported no actionable findings, and CI is running on rebased head 41b052a181.

Fixes #87815
2026-05-31 21:02:11 -04:00
Alix-007
909c24e3b7 fix(config): skip state-dir dotenv values that are unresolved shell references (#88288)
* fix(config): skip state-dir dotenv values that are unresolved shell references

readStateDirDotEnvVarsFromStateDir accepted any non-empty value from the
state-dir .env file and passed it into the managed service env. When a value
contains an unresolved shell variable reference such as "${SUPERMEMORY_KEY}"
or "$MY_VAR", dotenv preserves the literal string. The value then reaches
the LaunchAgent/systemd wrapper as a single-quoted literal, so the credential
is never resolved.

Add containsUnresolvedShellReference() and skip any value matching
$IDENTIFIER, ${...}, or $(...) in parseStateDirDotEnvContent(). Real credential
values (e.g. "sm_abc123") are unaffected.

Fixes #88274

* fix(config): narrow shell-reference detector to whole-value patterns only

The previous /$[\w{(]/ regex matched any value containing $ followed by
a word character, which would incorrectly drop real credentials that merely
contain a dollar sign (e.g. a password like abc$2!xyz).

Replace with isUnresolvedShellReference() that only matches values whose
ENTIRE content is a recognised reference form:
  - $VAR_NAME (simple reference)
  - ${VAR_NAME} (brace-form reference)
  - $(command) (command substitution)

Add a regression test that verifies dollar-bearing real secrets are kept.

* fix(config): use letter/underscore-anchored pattern to avoid matching dollar-numbers

$100, $2, etc. are NOT shell variable references — shell variable names must
begin with a letter or underscore. The previous /^$[\w_]/ would match them.

Change to /^$[A-Za-z_]\w*$/ so only genuine named-variable references like
$MY_VAR are rejected. Dollar-number sequences are now preserved.

* fix(daemon): drop stale systemd env-file refs for skipped state-dir dotenv keys

When a state-dir .env value is an unresolved shell reference ($VAR/${VAR}/$(cmd))
the parser skips it from the managed environment. A prior install could have
written that literal reference into gateway.systemd.env; because the skipped key
no longer appeared in the incoming env or the managed-key removal sets, the stale
literal survived re-stage and could override fresh inline Environment= values.

Surface the skipped shell-reference keys from the state-dir dotenv parser and add
them to the systemd env-file managed-key removal set so re-staging strips the
obsolete literal while preserving operator-only secrets that were never managed
via state-dir .env. launchd regenerates its env file wholesale, so it is
unaffected.

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

* fix(config): skip quoted shell parameter dotenv refs

* fix(config): preserve lowercase dollar-prefixed dotenv literals

* fix(daemon): clear stale unresolved systemd env refs

* fix(daemon): avoid re-staging unresolved file env refs

* fix(daemon): drop unresolved file env refs inline

* fix(daemon): drop inline-and-file unresolved env refs

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-05-31 21:01:33 -04:00
Vincent Koc
732748c8c5 perf(ui): skip markdown parsing while chat streams 2026-06-01 02:00:06 +01:00
Brian
fda5254e99 fix: preserve npm plugin root on blocked install (#77237)
Preserve the active per-plugin managed npm project when npm-backed install validation blocks a candidate after npm has already mutated local state.

This snapshots package.json, package-lock.json, and node_modules before managed npm installs, restores that exact project state on failed validation, and rolls back staged npm-pack archives so blocked pack installs do not leave candidate debris.

Validation:
- OPENCLAW_VITEST_MAX_WORKERS=1 node scripts/run-vitest.mjs src/plugins/install.npm-spec.test.ts
- pnpm tsgo:core && pnpm tsgo:core:test
- node scripts/run-oxlint.mjs src/plugins/install.ts src/plugins/install.npm-spec.test.ts
- .agents/skills/autoreview/scripts/autoreview --mode local
- GitHub CI 26729255950
- Crabbox run_26e9f9f7591c

Thanks @zhuisDEV.

Co-authored-by: Brian <95547369+zhuisDEV@users.noreply.github.com>
2026-05-31 20:59:32 -04:00
Vincent Koc
9da4835cdf refactor: share artifacts test helpers 2026-06-01 02:57:01 +02:00
Vincent Koc
43ced7bc49 fix(ui): preserve startup chat sends during history load 2026-06-01 01:52:58 +01:00
Vincent Koc
49b62079f7 fix(ui): unblock initial control chat send 2026-06-01 01:52:58 +01:00
Andy Ye
432312a17c test: cover Vertex API key model config
Adds regression coverage for Google Vertex API-key model config planning when the credential comes from an env-backed auth profile. This keeps the planner-level guard around the Vertex static catalog rows that fixed #88816 on main.

Verification:
- `node scripts/run-vitest.mjs src/agents/models-config.applies-config-env-vars.test.ts extensions/google/provider-catalog.test.ts extensions/google/provider-models.test.ts`
- `./node_modules/.bin/oxfmt --check --threads=1 src/agents/models-config.applies-config-env-vars.test.ts extensions/ollama/src/stream.ts extensions/qa-lab/src/mantis/slack-desktop-smoke.runtime.ts extensions/qa-lab/src/mantis/telegram-desktop-builder.runtime.ts extensions/qa-lab/src/mantis/visual-task.runtime.ts`
- `git diff --check`
- `pnpm deadcode:dependencies`

CI note: PR CI had an unrelated `check-dependencies` failure for `ui/package.json: three`; the PR diff is one `src/agents` test file.

Refs #88816
2026-05-31 20:51:50 -04:00
Peter Steinberger
5443baa852 Persist plugin install index in SQLite (#88794)
* refactor: persist plugin install index in sqlite

* fix: merge legacy plugin index records into sqlite

* test: update plugin index sqlite fixtures

* fix: migrate custom plugin install indexes

* test: update plugin index sentinel

* fix: exclude migrated plugin index archives

* fix: read post-upgrade plugin index from sqlite

* fix: migrate legacy plugin index before agent runs

* fix: respect disabled persisted plugin registry reads

* test: type plugin install record fixtures

* fix: simplify plugin index record reader type

* test: fix sqlite plugin index CI fallout

* test: mock provider normalization in agent command tests

# Conflicts:
#	src/commands/agent-command.test-mocks.ts

* build: remove unused ui three dependency
2026-05-31 20:51:33 -04:00
Vincent Koc
b475de834a refactor: share plugin approval test helpers 2026-06-01 02:45:37 +02:00
Matthew Schleder
6a96058f50 fix(minimax): use account oauth endpoints
Routes MiniMax OAuth device-code and token polling directly to account-hosted OAuth2 endpoints for global and CN regions, avoiding guarded-fetch cross-origin redirect body stripping. Keeps provider API base URLs unchanged and adds regression coverage for both endpoint pairs.

Proof: local minimax OAuth tests, oxfmt check, lint, autoreview clean, official MiniMax CLI/source check, live MiniMax endpoint probes, and CI run 26729242892 on 6bfe20eb06.

Co-authored-by: Matt Schleder <schledermatthew@gmail.com>
2026-05-31 20:44:41 -04:00
Vincent Koc
82d24b26ea fix(workboard): wire task-backed board runs
Summary:
- remove the leftover Workboard mini-game/prototype surface
- wire autonomous Workboard card starts through Gateway task-backed agent runs
- reconcile card task/session lifecycle for starts, stops, stale tasks, reassignment, and default-agent sessions
- clarify dispatch summary copy and admin-only model override behavior

Verification:
- autoreview clean: no accepted/actionable findings
- targeted Workboard/UI Vitest: 72 tests passed
- Workboard extension Vitest: 9 tests passed
- UI build, docs list, docs format, diff check, and focused oxlint passed
- PR CI checks: 50 ok, 0 attention
- Testbox tbx_01kt07mk5sjyj2whjq2sc967hg: pnpm verify check phase passed; broad test phase exposed unrelated latest-main failures/stalls in memory, Codex app-server, provider timeout, command daemon env, Telegram worker OOM, and gateway-client timeout suites
2026-06-01 01:41:21 +01:00
Vincent Koc
015c6b40ae fix(ci): clear extension lint regressions 2026-06-01 01:36:16 +01:00
Vincent Koc
915c156115 refactor: share tools effective test helpers 2026-06-01 02:33:47 +02:00
Vincent Koc
b3742b9edb fix(ui): stream chat deltas incrementally 2026-06-01 01:32:48 +01:00
Vincent Koc
bcaf326c3a refactor: share sessions abort scope test helpers 2026-06-01 02:21:44 +02:00
Vincent Koc
3c7c03f236 test(ci): update agent command model-selection mocks 2026-06-01 01:18:09 +01:00
Peter Steinberger
7562afdca3 fix(ollama): suppress disabled reasoning output 2026-06-01 01:16:47 +01:00
Peter Steinberger
27dde7a4d6 chore(lint): enable stricter error rules 2026-06-01 01:12:21 +01:00
Vincent Koc
0bfba7e26d fix(ui): detect system chromium for e2e 2026-06-01 01:09:46 +01:00
Vincent Koc
d95471afef test: type manifest catalog mock 2026-06-01 02:06:26 +02:00
Vincent Koc
69c948a752 refactor: share web start test snapshot 2026-06-01 02:06:26 +02:00
Andy Ye
002c1d2d5a test(agents): cover nonfatal trajectory flush timeout
Fixes #88520.

Adds focused regression coverage for the embedded attempt trajectory recorder cleanup boundary so a stalled trajectory flush resolves after the cleanup timeout and logs pending write details instead of rejecting attempt cleanup.

Verification:
- node scripts/run-vitest.mjs src/agents/run-cleanup-timeout.test.ts
- git diff --check origin/main...origin/pr/88802
- PR CI green: https://github.com/openclaw/openclaw/actions/runs/26727232564

Co-authored-by: Andy Ye <35905412+TurboTheTurtle@users.noreply.github.com>
2026-05-31 20:01:12 -04:00
Vincent Koc
2fc5072021 fix(ci): repair Copilot thinking compat types 2026-06-01 00:53:35 +01:00
Vincent Koc
160aad6fb3 fix(agents): preserve exact custom provider models 2026-06-01 01:50:30 +02:00
Vincent Koc
dd8d52c7d9 refactor: share optional model catalog loading 2026-06-01 01:49:51 +02:00
Peter Steinberger
219d854178 fix: keep tool detail redaction canonical 2026-06-01 00:49:43 +01:00
Vincent Koc
37d79a4303 test(ui): make chat sessions e2e deterministic 2026-06-01 00:45:29 +01:00
16475 changed files with 223692 additions and 55831 deletions

View File

@@ -22,6 +22,8 @@ Use when:
- Read dependency docs/source/types when the finding depends on external behavior.
- Reject unrealistic edge cases, speculative risks, broad rewrites, and fixes that over-complicate the codebase.
- Prefer small fixes at the right ownership boundary; no refactor unless it clearly improves the bug class.
- When an accepted finding shows a bug class or repeated pattern, inspect the current PR scope for sibling instances before fixing.
- Fix the scoped bug class at once when practical; stop at touched surfaces, owner boundaries, and clear follow-up territory.
- Keep going until structured review returns no accepted/actionable findings.
- If a review-triggered fix changes code, rerun focused tests and rerun the structured review helper.
- For security-audit suppression changes, verify accepted findings remain auditable: suppressed findings stay in structured output, active output keeps an unsuppressible suppression notice, and aggregate findings cannot hide unrelated active risk.

View File

@@ -1,6 +1,8 @@
#!/usr/bin/env node
// Secret scanning alert handler for OpenClaw maintainers.
// Usage: node secret-scanning.mjs <command> [options]
/**
* Secret scanning alert handler for OpenClaw maintainers.
* Usage: node secret-scanning.mjs <command> [options]
*/
import { spawnSync } from "node:child_process";
import crypto from "node:crypto";
@@ -57,6 +59,7 @@ function isBodyLocationType(locationType) {
return locationType === "issue_body" || locationType === "pull_request_body";
}
/** Decides whether redacting an issue/PR body requires notifying the reporter. */
export function decideBodyRedaction(currentBody, redactedBody) {
const bodyChanged = String(currentBody) !== String(redactedBody);
return {
@@ -65,6 +68,7 @@ export function decideBodyRedaction(currentBody, redactedBody) {
};
}
/** Loads redaction-result metadata for issue/PR body secret locations. */
export function loadBodyRedactionResult(locationType, resultFile) {
if (!isBodyLocationType(locationType)) {
return { notify_required: true };

View File

@@ -1,4 +1,7 @@
#!/usr/bin/env node
/**
* Heap snapshot diff utility for OpenClaw test memory leak investigations.
*/
import fs from "node:fs";
import path from "node:path";

View File

@@ -16,6 +16,10 @@ Use this with `$release-openclaw-maintainer` and `$openclaw-testing` when a rele
- Watch one parent run plus compact child summaries. Avoid broad `gh run view` polling loops; REST quota is easy to burn.
- Fetch logs only for failed or currently-blocking jobs. If quota is low, stop polling and wait for reset.
- Treat live-provider flakes separately from code failures: prove key validity, provider HTTP status, retry evidence, and exact failing lane before editing code.
- Full Release Validation parent monitors fail fast: once a required child job
fails, the parent cancels the remaining child matrix and prints the failed
job summary. Inspect that first red job instead of waiting for unrelated
matrix tails.
## Preflight
@@ -73,6 +77,9 @@ gh workflow run full-release-validation.yml \
```
Use `release_profile=stable` unless the operator explicitly asks for the broad advisory provider/media matrix. Use narrow `rerun_group` after focused fixes.
Publish with `openclaw-release-publish.yml` using `release_profile=from-validation`
unless a maintainer intentionally wants to cross-check a specific profile; the
publish workflow reads the effective profile from the full-validation manifest.
## Watch

View File

@@ -1,4 +1,8 @@
#!/usr/bin/env node
/**
* Release CI summary helper that prints parent and child workflow status for a
* full release run.
*/
import { execFileSync } from "node:child_process";
import process from "node:process";

View File

@@ -1,4 +1,8 @@
#!/usr/bin/env node
/**
* Release preflight helper that verifies required provider API keys can reach
* their model-list endpoints without printing secret values.
*/
import process from "node:process";
const args = new Map();

View File

@@ -49,17 +49,21 @@ Use this skill for release and publish-time workflow. Load `$release-private` if
the next beta number until the matching npm package has actually published.
If a published beta needs a fix, commit the fix on the release branch and
increment to the next `-beta.N`.
- For a beta release train, run the fast local preflight first, publish the
beta to npm `beta`, then run the expensive published-package roster focused
on install/update/Docker/Parallels/NPM Telegram. If anything fails, fix it on
the release branch, commit/push/pull, increment beta number, and repeat. Run
the full expensive roster at least once before stable/latest promotion; for
later beta attempts, rerun only lanes whose evidence changed unless the fix
touches broad release, install/update, plugin, Docker, Parallels, or live QA
behavior. After each beta is published, scan current `main` once for critical
fixes that landed after the release branch cut and backport only important
low-risk fixes. Operators may authorize up to 4 autonomous beta attempts;
after 4 failed beta attempts, stop and report.
- For a beta release train, keep Full Release Validation as a pre-publish gate
unless the operator explicitly waives it. Run the fast local preflight, npm
preflight, full release validation, and performance in parallel where safe.
If anything fails before npm publish, fix it on the release branch,
forward-port the fix to `main`, move the unpublished beta tag/prerelease to
the fixed commit, and rerun the affected pre-publish gates. If anything fails
after npm publish, fix it, forward-port to `main`, increment beta number, and
repeat. After each beta publish, run the published-package roster focused on
install/update/Docker/Parallels/NPM Telegram. For later beta attempts, rerun
only lanes whose evidence changed unless the fix touches broad release,
install/update, plugin, Docker, Parallels, or live QA behavior. After each
beta is live, scan current `main` once for critical fixes that landed after
the release branch cut and backport only important low-risk fixes. Operators
may authorize up to 4 autonomous beta attempts; after 4 failed beta attempts,
stop and report.
- As soon as the release candidate SHA exists, dispatch `OpenClaw Performance`
with `target_ref=<release-sha>` in parallel with the other release work. Do
not wait for full release validation to start the performance signal.
@@ -107,9 +111,10 @@ Use this skill for release and publish-time workflow. Load `$release-private` if
- For fallback correction tags like `vYYYY.M.D-N`, the repo version locations still stay at `YYYY.M.D`.
- “Bump version everywhere” means all version locations above except `appcast.xml`.
- Release signing and notary credentials live outside the repo in the private maintainer docs.
- Every stable OpenClaw release ships the npm package and macOS app together.
Beta releases normally ship npm/package artifacts first and skip mac app
build/sign/notarize unless the operator requests mac beta validation.
- Every stable OpenClaw release ships the npm package, macOS app, and signed
Windows Hub installers together. Beta releases normally ship npm/package
artifacts first and skip native app build/sign/notarize/promote unless the
operator requests native beta validation.
- Do not let the slower macOS signing/notary path block npm publication once
the npm preflight has passed. Keep mac validation/publish running in
parallel, publish npm from the successful npm preflight, then start published
@@ -139,6 +144,17 @@ Use this skill for release and publish-time workflow. Load `$release-private` if
at `YYYY.M.D`, but the mac release must use a strictly higher numeric
`APP_BUILD` / Sparkle build than the original release so existing installs
see it as newer.
- Stable Windows Hub release closeout requires the signed
`OpenClawCompanion-Setup-x64.exe`, `OpenClawCompanion-Setup-arm64.exe`, and
`OpenClawCompanion-SHA256SUMS.txt` assets on the canonical
`openclaw/openclaw` GitHub Release. Use the public `Windows Node Release`
workflow after the matching `openclaw/openclaw-windows-node` release exists;
it verifies Authenticode signatures on Windows before uploading assets.
- Website Windows Hub download links should target exact canonical
`openclaw/openclaw/releases/download/vYYYY.M.D/...` assets for the current
stable release, or `releases/latest/download/...` only after verifying the
redirect resolves to that same tag, so the installable signed Windows artifact
is visible from both the GitHub release page and openclaw.ai.
## Build changelog-backed release notes
@@ -174,6 +190,13 @@ Use this skill for release and publish-time workflow. Load `$release-private` if
`CHANGELOG.md` version section, not highlights or an excerpt. When creating
or editing a release, extract from `## YYYY.M.D` through the line before the
next level-2 heading and use that complete block as the release notes.
- To update an existing GitHub Release body, resolve the numeric release id and
patch that resource with the notes file as the `body` field:
`gh api repos/openclaw/openclaw/releases/tags/vYYYY.M.D --jq .id`, then
`gh api -X PATCH repos/openclaw/openclaw/releases/<id> -F body=@/tmp/notes.md`.
Do not trust `gh release edit --notes-file` or `--input` JSON if verification
disagrees; verify with `gh api repos/openclaw/openclaw/releases/<id>` because
the tag lookup and `gh release view` can lag or show stale body text.
- When preparing release notes, scan `src/plugins/compat/registry.ts` and
`src/commands/doctor/shared/deprecation-compat.ts` for compatibility records
with `warningStarts` or `removeAfter` within 7 days after the release date.
@@ -468,8 +491,10 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
- The npm workflow and the private mac publish workflow accept
`preflight_only=true` to run validation/build/package steps without uploading
public release assets.
- Real npm publish requires a prior successful npm preflight run id so the
publish job promotes the prepared tarball instead of rebuilding it.
- Real npm publish requires a prior successful npm preflight run id and the
successful Full Release Validation run id for the same tag/SHA so the publish
job promotes the prepared tarball instead of rebuilding it and attaches the
correct release evidence.
- Real private mac publish requires a prior successful private mac preflight
run id so the publish job promotes the prepared artifacts instead of
rebuilding or renotarizing them again.
@@ -499,11 +524,12 @@ node --import tsx scripts/openclaw-npm-postpublish-verify.ts <published-version>
instead of uploading public GitHub release assets.
- Private smoke-test runs upload ad-hoc, non-notarized build artifacts as
workflow artifacts and intentionally skip stable `appcast.xml` generation.
- For stable releases, npm preflight, public mac validation, private mac
validation, and private mac preflight must all pass before any real publish
run starts. For beta releases, npm preflight plus the selected Docker,
install/update, Parallels, and release-check lanes are sufficient unless mac
beta validation was explicitly requested.
- For stable releases, npm preflight, Full Release Validation, public mac
validation, private mac validation, and private mac preflight must all pass
before any real publish run starts. For beta releases, npm preflight and Full
Release Validation must pass before npm publish unless the operator explicitly
waives the full gate; mac beta validation is still only required when
requested.
- Real publish runs may be dispatched from `main` or from a
`release/YYYY.M.D` branch. For release-branch runs, the tag must be contained
in that release branch, and the real publish must reuse a successful preflight

4
.github/labeler.yml vendored
View File

@@ -574,6 +574,10 @@
- changed-files:
- any-glob-to-any-file:
- "extensions/openshell/**"
"extensions: parallel":
- changed-files:
- any-glob-to-any-file:
- "extensions/parallel/**"
"extensions: perplexity":
- changed-files:
- any-glob-to-any-file:

View File

@@ -0,0 +1,156 @@
name: Blacksmith ARM Testbox
on:
workflow_dispatch:
inputs:
testbox_id:
type: string
description: "Testbox session ID"
required: true
pull_request:
paths:
- ".github/workflows/**"
permissions:
contents: read
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
PNPM_CONFIG_STORE_DIR: "/tmp/openclaw-pnpm-store"
PNPM_CONFIG_VERIFY_DEPS_BEFORE_RUN: "false"
jobs:
check-arm:
if: ${{ github.event_name != 'pull_request' || !github.event.pull_request.draft }}
permissions:
contents: read
name: "check-arm"
runs-on: blacksmith-16vcpu-ubuntu-2404-arm
timeout-minutes: 120
steps:
- name: Begin Testbox
uses: useblacksmith/begin-testbox@d0e04585c26905fdd92c94a09c159544c7ee1b67
with:
testbox_id: ${{ inputs.testbox_id }}
- name: Verify ARM runner
shell: bash
run: |
set -euo pipefail
runner_arch="$(uname -m)"
echo "check-arm runner architecture: ${runner_arch}"
case "$runner_arch" in
aarch64 | arm64)
;;
*)
echo "check-arm requires an ARM64 runner; got ${runner_arch}" >&2
exit 1
;;
esac
- name: Checkout
shell: bash
env:
CHECKOUT_REPO: ${{ github.repository }}
CHECKOUT_SHA: ${{ github.sha }}
CHECKOUT_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
workdir="$GITHUB_WORKSPACE"
if [[ -z "$CHECKOUT_TOKEN" ]]; then
echo "checkout token is missing" >&2
exit 1
fi
auth_header="$(printf 'x-access-token:%s' "$CHECKOUT_TOKEN" | base64 | tr -d '\n')"
reset_checkout_dir() {
mkdir -p "$workdir"
find "$workdir" -mindepth 1 -maxdepth 1 -exec rm -rf {} +
}
checkout_attempt() {
local attempt="$1"
reset_checkout_dir
git init "$workdir" >/dev/null
git config --global --add safe.directory "$workdir"
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}"
git -C "$workdir" config gc.auto 0
timeout --signal=TERM --kill-after=10s 30s git -C "$workdir" \
-c protocol.version=2 \
-c "http.extraheader=AUTHORIZATION: basic ${auth_header}" \
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1
git -C "$workdir" checkout --force --detach "$CHECKOUT_SHA" || return 1
test -f "$workdir/.github/actions/setup-node-env/action.yml" || return 1
echo "checkout attempt ${attempt}/5 succeeded"
}
for attempt in 1 2 3 4 5; do
if checkout_attempt "$attempt"; then
exit 0
fi
echo "checkout attempt ${attempt}/5 failed"
sleep $((attempt * 5))
done
echo "checkout failed after 5 attempts" >&2
exit 1
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
install-bun: "false"
- name: Prepare Testbox shell
shell: bash
run: |
set -euo pipefail
timeout --signal=TERM --kill-after=10s 30s git \
-c protocol.version=2 \
fetch --no-tags --prune --no-recurse-submodules --depth=50 origin \
"+refs/heads/main:refs/remotes/origin/main"
node_bin="$(dirname "$(node -p 'process.execPath')")"
sudo ln -sf "$node_bin/node" /usr/local/bin/node
sudo ln -sf "$node_bin/npm" /usr/local/bin/npm
sudo ln -sf "$node_bin/npx" /usr/local/bin/npx
sudo ln -sf "$node_bin/corepack" /usr/local/bin/corepack
sudo tee /usr/local/bin/pnpm >/dev/null <<'PNPM'
#!/usr/bin/env bash
exec /usr/local/bin/corepack pnpm "$@"
PNPM
sudo chmod 0755 /usr/local/bin/pnpm
- name: Hydrate Testbox provider env helper
shell: bash
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
ANTHROPIC_API_KEY_OLD: ${{ secrets.ANTHROPIC_API_KEY_OLD }}
ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }}
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}
DEEPINFRA_API_KEY: ${{ secrets.DEEPINFRA_API_KEY }}
FACTORY_API_KEY: ${{ secrets.FACTORY_API_KEY }}
FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }}
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
KIMI_API_KEY: ${{ secrets.KIMI_API_KEY }}
MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }}
MISTRAL_API_KEY: ${{ secrets.MISTRAL_API_KEY }}
MOONSHOT_API_KEY: ${{ secrets.MOONSHOT_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
QWEN_API_KEY: ${{ secrets.QWEN_API_KEY }}
TOGETHER_API_KEY: ${{ secrets.TOGETHER_API_KEY }}
XAI_API_KEY: ${{ secrets.XAI_API_KEY }}
ZAI_API_KEY: ${{ secrets.ZAI_API_KEY }}
Z_AI_API_KEY: ${{ secrets.Z_AI_API_KEY }}
run: bash scripts/ci-hydrate-testbox-env.sh
- name: Run Testbox
uses: useblacksmith/run-testbox@5ca05834db1d3813554d1dd109e5f2087a8d7cbc
if: success()
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"

View File

@@ -139,139 +139,3 @@ jobs:
if: success()
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
check-arm:
if: ${{ github.event_name != 'pull_request' || !github.event.pull_request.draft }}
permissions:
contents: read
name: "check-arm"
runs-on: blacksmith-16vcpu-ubuntu-2404-arm
timeout-minutes: 120
steps:
- name: Begin Testbox
uses: useblacksmith/begin-testbox@d0e04585c26905fdd92c94a09c159544c7ee1b67
with:
testbox_id: ${{ inputs.testbox_id }}
- name: Verify ARM runner
shell: bash
run: |
set -euo pipefail
runner_arch="$(uname -m)"
echo "check-arm runner architecture: ${runner_arch}"
case "$runner_arch" in
aarch64 | arm64)
;;
*)
echo "check-arm requires an ARM64 runner; got ${runner_arch}" >&2
exit 1
;;
esac
- name: Checkout
shell: bash
env:
CHECKOUT_REPO: ${{ github.repository }}
CHECKOUT_SHA: ${{ github.sha }}
CHECKOUT_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
workdir="$GITHUB_WORKSPACE"
if [[ -z "$CHECKOUT_TOKEN" ]]; then
echo "checkout token is missing" >&2
exit 1
fi
auth_header="$(printf 'x-access-token:%s' "$CHECKOUT_TOKEN" | base64 | tr -d '\n')"
reset_checkout_dir() {
mkdir -p "$workdir"
find "$workdir" -mindepth 1 -maxdepth 1 -exec rm -rf {} +
}
checkout_attempt() {
local attempt="$1"
reset_checkout_dir
git init "$workdir" >/dev/null
git config --global --add safe.directory "$workdir"
git -C "$workdir" remote add origin "https://github.com/${CHECKOUT_REPO}"
git -C "$workdir" config gc.auto 0
timeout --signal=TERM --kill-after=10s 30s git -C "$workdir" \
-c protocol.version=2 \
-c "http.extraheader=AUTHORIZATION: basic ${auth_header}" \
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
"+${CHECKOUT_SHA}:refs/remotes/origin/ci-target" || return 1
git -C "$workdir" checkout --force --detach "$CHECKOUT_SHA" || return 1
test -f "$workdir/.github/actions/setup-node-env/action.yml" || return 1
echo "checkout attempt ${attempt}/5 succeeded"
}
for attempt in 1 2 3 4 5; do
if checkout_attempt "$attempt"; then
exit 0
fi
echo "checkout attempt ${attempt}/5 failed"
sleep $((attempt * 5))
done
echo "checkout failed after 5 attempts" >&2
exit 1
- name: Setup Node environment
uses: ./.github/actions/setup-node-env
with:
install-bun: "false"
- name: Prepare Testbox shell
shell: bash
run: |
set -euo pipefail
timeout --signal=TERM --kill-after=10s 30s git \
-c protocol.version=2 \
fetch --no-tags --prune --no-recurse-submodules --depth=50 origin \
"+refs/heads/main:refs/remotes/origin/main"
node_bin="$(dirname "$(node -p 'process.execPath')")"
sudo ln -sf "$node_bin/node" /usr/local/bin/node
sudo ln -sf "$node_bin/npm" /usr/local/bin/npm
sudo ln -sf "$node_bin/npx" /usr/local/bin/npx
sudo ln -sf "$node_bin/corepack" /usr/local/bin/corepack
sudo tee /usr/local/bin/pnpm >/dev/null <<'PNPM'
#!/usr/bin/env bash
exec /usr/local/bin/corepack pnpm "$@"
PNPM
sudo chmod 0755 /usr/local/bin/pnpm
- name: Hydrate Testbox provider env helper
shell: bash
env:
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
ANTHROPIC_API_KEY_OLD: ${{ secrets.ANTHROPIC_API_KEY_OLD }}
ANTHROPIC_API_TOKEN: ${{ secrets.ANTHROPIC_API_TOKEN }}
CEREBRAS_API_KEY: ${{ secrets.CEREBRAS_API_KEY }}
DEEPINFRA_API_KEY: ${{ secrets.DEEPINFRA_API_KEY }}
FACTORY_API_KEY: ${{ secrets.FACTORY_API_KEY }}
FIREWORKS_API_KEY: ${{ secrets.FIREWORKS_API_KEY }}
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
GOOGLE_API_KEY: ${{ secrets.GOOGLE_API_KEY }}
GROQ_API_KEY: ${{ secrets.GROQ_API_KEY }}
KIMI_API_KEY: ${{ secrets.KIMI_API_KEY }}
MINIMAX_API_KEY: ${{ secrets.MINIMAX_API_KEY }}
MISTRAL_API_KEY: ${{ secrets.MISTRAL_API_KEY }}
MOONSHOT_API_KEY: ${{ secrets.MOONSHOT_API_KEY }}
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
OPENAI_BASE_URL: ${{ secrets.OPENAI_BASE_URL }}
OPENROUTER_API_KEY: ${{ secrets.OPENROUTER_API_KEY }}
QWEN_API_KEY: ${{ secrets.QWEN_API_KEY }}
TOGETHER_API_KEY: ${{ secrets.TOGETHER_API_KEY }}
XAI_API_KEY: ${{ secrets.XAI_API_KEY }}
ZAI_API_KEY: ${{ secrets.ZAI_API_KEY }}
Z_AI_API_KEY: ${{ secrets.Z_AI_API_KEY }}
run: bash scripts/ci-hydrate-testbox-env.sh
- name: Run Testbox
uses: useblacksmith/run-testbox@5ca05834db1d3813554d1dd109e5f2087a8d7cbc
if: success()
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"

View File

@@ -92,7 +92,7 @@ jobs:
for attempt in 1 2 3; do
timeout --signal=TERM --kill-after=10s 30s git -C "$GITHUB_WORKSPACE" \
-c protocol.version=2 \
fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \
fetch --no-tags --prune --no-recurse-submodules --depth=2 origin \
"+${ref}:refs/remotes/origin/checkout" && return 0
fetch_status="$?"
if [ "$fetch_status" != "124" ] && [ "$fetch_status" != "137" ]; then
@@ -146,12 +146,12 @@ jobs:
if [ "${{ github.event_name }}" = "push" ]; then
BASE="${{ github.event.before }}"
node scripts/ci-changed-scope.mjs --base "$BASE" --head HEAD
else
BASE="${{ github.event.pull_request.base.sha }}"
node scripts/ci-changed-scope.mjs --base "$BASE" --head HEAD --merge-head-first-parent
fi
node scripts/ci-changed-scope.mjs --base "$BASE" --head HEAD
- name: Build CI manifest
id: manifest
env:
@@ -605,7 +605,19 @@ jobs:
restore-keys: |
${{ runner.os }}-build-all-v3-
- name: Restore dist build cache
id: dist_build_cache
uses: actions/cache/restore@v5
with:
path: |
dist/
dist-runtime/
extensions/*/src/host/**/.bundle.hash
extensions/*/src/host/**/*.bundle.js
key: ${{ runner.os }}-dist-build-${{ needs.preflight.outputs.checkout_revision }}
- name: Build dist
if: steps.dist_build_cache.outputs.cache-hit != 'true'
env:
NODE_OPTIONS: --max-old-space-size=8192
run: pnpm build:ci-artifacts
@@ -614,14 +626,6 @@ jobs:
if: needs.preflight.outputs.run_control_ui_i18n == 'true'
run: pnpm ui:i18n:check
- name: Cache dist build
uses: actions/cache@v5
with:
path: |
dist/
dist-runtime/
key: ${{ runner.os }}-dist-build-${{ needs.preflight.outputs.checkout_revision }}
- name: Pack built runtime artifacts
run: tar --posix -cf dist-runtime-build.tar.zst --use-compress-program zstdmt dist dist-runtime
@@ -751,6 +755,18 @@ jobs:
done
exit "$failures"
- name: Save dist build cache
if: steps.dist_build_cache.outputs.cache-hit != 'true'
uses: actions/cache/save@v5
continue-on-error: true
with:
path: |
dist/
dist-runtime/
extensions/*/src/host/**/.bundle.hash
extensions/*/src/host/**/*.bundle.js
key: ${{ steps.dist_build_cache.outputs.cache-primary-key }}
- name: Upload gateway watch regression artifacts
if: always() && needs.preflight.outputs.run_check_additional == 'true'
uses: actions/upload-artifact@v7
@@ -1151,7 +1167,8 @@ jobs:
OPENCLAW_NODE_TEST_CONFIGS_JSON: ${{ toJson(matrix.configs) }}
OPENCLAW_NODE_TEST_INCLUDE_PATTERNS_JSON: ${{ toJson(matrix.includePatterns) }}
OPENCLAW_VITEST_SHARD_NAME: ${{ matrix.shard_name }}
OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS: "900000"
OPENCLAW_VITEST_NO_OUTPUT_TIMEOUT_MS: "300000"
OPENCLAW_VITEST_NO_OUTPUT_RETRY: "1"
OPENCLAW_TEST_PROJECTS_PARALLEL: "2"
shell: bash
run: |

View File

@@ -35,7 +35,7 @@ jobs:
java-version: "21"
- name: Initialize CodeQL
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
uses: github/codeql-action/init@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4
with:
languages: java-kotlin
build-mode: manual
@@ -46,6 +46,6 @@ jobs:
run: ./gradlew --no-daemon :app:assemblePlayDebug
- name: Analyze
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
uses: github/codeql-action/analyze@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4
with:
category: "/codeql-critical-security/android"

View File

@@ -342,13 +342,13 @@ jobs:
submodules: false
- name: Initialize CodeQL
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
uses: github/codeql-action/init@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4
with:
languages: javascript-typescript
config-file: ./.github/codeql/codeql-core-auth-secrets-critical-quality.yml
- name: Analyze
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
uses: github/codeql-action/analyze@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4
with:
category: "/codeql-critical-quality/core-auth-secrets"
@@ -365,13 +365,13 @@ jobs:
submodules: false
- name: Initialize CodeQL
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
uses: github/codeql-action/init@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4
with:
languages: javascript-typescript
config-file: ./.github/codeql/codeql-config-boundary-critical-quality.yml
- name: Analyze
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
uses: github/codeql-action/analyze@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4
with:
category: "/codeql-critical-quality/config-boundary"
@@ -388,13 +388,13 @@ jobs:
submodules: false
- name: Initialize CodeQL
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
uses: github/codeql-action/init@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4
with:
languages: javascript-typescript
config-file: ./.github/codeql/codeql-gateway-runtime-boundary-critical-quality.yml
- name: Analyze
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
uses: github/codeql-action/analyze@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4
with:
category: "/codeql-critical-quality/gateway-runtime-boundary"
@@ -411,13 +411,13 @@ jobs:
submodules: false
- name: Initialize CodeQL
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
uses: github/codeql-action/init@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4
with:
languages: javascript-typescript
config-file: ./.github/codeql/codeql-channel-runtime-boundary-critical-quality.yml
- name: Analyze
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
uses: github/codeql-action/analyze@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4
with:
category: "/codeql-critical-quality/channel-runtime-boundary"
@@ -460,7 +460,7 @@ jobs:
- name: Initialize CodeQL
if: ${{ github.event_name != 'pull_request' }}
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
uses: github/codeql-action/init@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4
with:
languages: javascript-typescript
config-file: ./.github/codeql/codeql-network-runtime-boundary-critical-quality.yml
@@ -468,7 +468,7 @@ jobs:
- name: Analyze
id: analyze
if: ${{ github.event_name != 'pull_request' }}
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
uses: github/codeql-action/analyze@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4
with:
output: sarif-results
category: "/codeql-critical-quality/network-runtime-boundary"
@@ -518,13 +518,13 @@ jobs:
submodules: false
- name: Initialize CodeQL
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
uses: github/codeql-action/init@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4
with:
languages: javascript-typescript
config-file: ./.github/codeql/codeql-agent-runtime-boundary-critical-quality.yml
- name: Analyze
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
uses: github/codeql-action/analyze@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4
with:
category: "/codeql-critical-quality/agent-runtime-boundary"
@@ -541,13 +541,13 @@ jobs:
submodules: false
- name: Initialize CodeQL
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
uses: github/codeql-action/init@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4
with:
languages: javascript-typescript
config-file: ./.github/codeql/codeql-mcp-process-runtime-boundary-critical-quality.yml
- name: Analyze
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
uses: github/codeql-action/analyze@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4
with:
category: "/codeql-critical-quality/mcp-process-runtime-boundary"
@@ -564,13 +564,13 @@ jobs:
submodules: false
- name: Initialize CodeQL
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
uses: github/codeql-action/init@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4
with:
languages: javascript-typescript
config-file: ./.github/codeql/codeql-memory-runtime-boundary-critical-quality.yml
- name: Analyze
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
uses: github/codeql-action/analyze@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4
with:
category: "/codeql-critical-quality/memory-runtime-boundary"
@@ -587,13 +587,13 @@ jobs:
submodules: false
- name: Initialize CodeQL
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
uses: github/codeql-action/init@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4
with:
languages: javascript-typescript
config-file: ./.github/codeql/codeql-session-diagnostics-boundary-critical-quality.yml
- name: Analyze
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
uses: github/codeql-action/analyze@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4
with:
category: "/codeql-critical-quality/session-diagnostics-boundary"
@@ -610,13 +610,13 @@ jobs:
submodules: false
- name: Initialize CodeQL
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
uses: github/codeql-action/init@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4
with:
languages: javascript-typescript
config-file: ./.github/codeql/codeql-plugin-sdk-reply-runtime-critical-quality.yml
- name: Analyze
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
uses: github/codeql-action/analyze@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4
with:
category: "/codeql-critical-quality/plugin-sdk-reply-runtime"
@@ -633,13 +633,13 @@ jobs:
submodules: false
- name: Initialize CodeQL
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
uses: github/codeql-action/init@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4
with:
languages: javascript-typescript
config-file: ./.github/codeql/codeql-provider-runtime-boundary-critical-quality.yml
- name: Analyze
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
uses: github/codeql-action/analyze@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4
with:
category: "/codeql-critical-quality/provider-runtime-boundary"
@@ -655,13 +655,13 @@ jobs:
submodules: false
- name: Initialize CodeQL
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
uses: github/codeql-action/init@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4
with:
languages: javascript-typescript
config-file: ./.github/codeql/codeql-ui-control-plane-critical-quality.yml
- name: Analyze
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
uses: github/codeql-action/analyze@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4
with:
category: "/codeql-critical-quality/ui-control-plane"
@@ -677,13 +677,13 @@ jobs:
submodules: false
- name: Initialize CodeQL
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
uses: github/codeql-action/init@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4
with:
languages: javascript-typescript
config-file: ./.github/codeql/codeql-web-media-runtime-boundary-critical-quality.yml
- name: Analyze
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
uses: github/codeql-action/analyze@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4
with:
category: "/codeql-critical-quality/web-media-runtime-boundary"
@@ -700,13 +700,13 @@ jobs:
submodules: false
- name: Initialize CodeQL
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
uses: github/codeql-action/init@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4
with:
languages: javascript-typescript
config-file: ./.github/codeql/codeql-plugin-boundary-critical-quality.yml
- name: Analyze
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
uses: github/codeql-action/analyze@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4
with:
category: "/codeql-critical-quality/plugin-boundary"
@@ -723,12 +723,12 @@ jobs:
submodules: false
- name: Initialize CodeQL
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
uses: github/codeql-action/init@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4
with:
languages: javascript-typescript
config-file: ./.github/codeql/codeql-plugin-sdk-package-contract-critical-quality.yml
- name: Analyze
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
uses: github/codeql-action/analyze@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4
with:
category: "/codeql-critical-quality/plugin-sdk-package-contract"

View File

@@ -35,7 +35,7 @@ jobs:
swift --version
- name: Initialize CodeQL
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
uses: github/codeql-action/init@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4
with:
languages: swift
build-mode: manual
@@ -46,7 +46,7 @@ jobs:
- name: Analyze
id: analyze
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
uses: github/codeql-action/analyze@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4
with:
output: sarif-results
upload: failure-only
@@ -83,7 +83,7 @@ jobs:
done
- name: Upload filtered SARIF
uses: github/codeql-action/upload-sarif@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
uses: github/codeql-action/upload-sarif@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4
with:
sarif_file: sarif-results-filtered
category: "/codeql-critical-security/macos"

View File

@@ -101,12 +101,12 @@ jobs:
.github/codeql
- name: Initialize CodeQL
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
uses: github/codeql-action/init@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4
with:
languages: ${{ matrix.language }}
config-file: ${{ matrix.config_file }}
- name: Analyze
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
uses: github/codeql-action/analyze@87557b9c84dde89fdd9b10e88954ac2f4248e463 # v4
with:
category: "/codeql-security-high/${{ matrix.category }}"

View File

@@ -32,11 +32,11 @@ permissions:
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true"
PNPM_CONFIG_CHILD_CONCURRENCY: "1"
PNPM_CONFIG_MODULES_DIR: "/tmp/openclaw-pnpm-node-modules"
PNPM_CONFIG_MODULES_DIR: "/var/tmp/openclaw-pnpm/node_modules"
PNPM_CONFIG_NETWORK_CONCURRENCY: "1"
PNPM_CONFIG_STORE_DIR: "/tmp/openclaw-pnpm-store"
PNPM_CONFIG_STORE_DIR: "/var/cache/crabbox/pnpm/store"
PNPM_CONFIG_VERIFY_DEPS_BEFORE_RUN: "false"
PNPM_CONFIG_VIRTUAL_STORE_DIR: "/tmp/openclaw-pnpm-virtual-store"
PNPM_CONFIG_VIRTUAL_STORE_DIR: "/var/tmp/openclaw-pnpm/virtual-store"
jobs:
hydrate:
@@ -120,6 +120,27 @@ jobs:
append_pnpm_option_arg PNPM_CONFIG_MODULES_DIR modules-dir
append_pnpm_option_arg PNPM_CONFIG_NETWORK_CONCURRENCY network-concurrency
append_pnpm_option_arg PNPM_CONFIG_VIRTUAL_STORE_DIR virtual-store-dir
require_safe_writable_dir() {
local dir="$1"
if [ -L "$dir" ] || [ ! -d "$dir" ] || [ ! -w "$dir" ]; then
echo "::error::Refusing unsafe pnpm directory: $dir"
exit 1
fi
}
prepare_crabbox_pnpm_dirs() {
local volatile_root="/var/tmp/openclaw-pnpm"
case "${PNPM_CONFIG_MODULES_DIR:?}" in "$volatile_root"/*) ;; *) echo "::error::PNPM_CONFIG_MODULES_DIR must stay under $volatile_root"; exit 1 ;; esac
case "${PNPM_CONFIG_VIRTUAL_STORE_DIR:?}" in "$volatile_root"/*) ;; *) echo "::error::PNPM_CONFIG_VIRTUAL_STORE_DIR must stay under $volatile_root"; exit 1 ;; esac
rm -rf -- "$volatile_root"
mkdir -p "$volatile_root" "$PNPM_CONFIG_STORE_DIR"
require_safe_writable_dir "$volatile_root"
require_safe_writable_dir "$PNPM_CONFIG_STORE_DIR"
mkdir -p "$PNPM_CONFIG_MODULES_DIR" "$PNPM_CONFIG_VIRTUAL_STORE_DIR"
}
prepare_crabbox_pnpm_dirs
if [ -L node_modules ] && [ "$(readlink node_modules)" = "${PNPM_CONFIG_MODULES_DIR:-}" ]; then
rm -f node_modules
fi
if [ -n "${PNPM_CONFIG_MODULES_DIR:-}" ]; then
mkdir -p "$PNPM_CONFIG_MODULES_DIR"
ln -sfn . "$PNPM_CONFIG_MODULES_DIR/node_modules"
@@ -357,9 +378,10 @@ jobs:
$env:XDG_CACHE_HOME = Join-Path $cacheRoot "cache"
$env:COREPACK_HOME = Join-Path $env:XDG_CACHE_HOME "corepack"
$env:PNPM_HOME = Join-Path $cacheRoot "pnpm-home"
$env:PNPM_CONFIG_STORE_DIR = Join-Path $cacheRoot "openclaw-pnpm-store"
$env:PNPM_CONFIG_MODULES_DIR = Join-Path $workspace "node_modules"
$env:PNPM_CONFIG_VIRTUAL_STORE_DIR = Join-Path $workspace "node_modules\.pnpm"
$pnpmCacheRoot = Join-Path $cacheRoot "openclaw-pnpm"
$env:PNPM_CONFIG_STORE_DIR = Join-Path $pnpmCacheRoot "store"
$env:PNPM_CONFIG_MODULES_DIR = Join-Path $pnpmCacheRoot "node_modules"
$env:PNPM_CONFIG_VIRTUAL_STORE_DIR = Join-Path $pnpmCacheRoot "virtual-store"
$env:PNPM_CONFIG_CHILD_CONCURRENCY = "4"
$env:PNPM_CONFIG_NETWORK_CONCURRENCY = "8"
$env:PNPM_CONFIG_VERIFY_DEPS_BEFORE_RUN = "false"
@@ -431,6 +453,25 @@ jobs:
if ($LASTEXITCODE -ne 0) {
exit $LASTEXITCODE
}
$workspaceNodeModules = Join-Path $workspace "node_modules"
if (Test-Path $workspaceNodeModules) {
$workspaceNodeModulesItem = Get-Item $workspaceNodeModules -Force
if (($workspaceNodeModulesItem.Attributes -band [System.IO.FileAttributes]::ReparsePoint) -eq 0) {
$nodeModulesChildren = @(Get-ChildItem -LiteralPath $workspaceNodeModules -Force)
$hasOnlyPnpmWorkspaceState = $nodeModulesChildren.Count -eq 1 -and $nodeModulesChildren[0].Name -eq ".pnpm-workspace-state-v1.json"
if ($nodeModulesChildren.Count -ne 0 -and -not $hasOnlyPnpmWorkspaceState) {
throw "workspace node_modules exists and is not a link: $workspaceNodeModules"
}
foreach ($nodeModulesChild in $nodeModulesChildren) {
Remove-Item -LiteralPath $nodeModulesChild.FullName -Force
}
Remove-Item -LiteralPath $workspaceNodeModules -Force
New-Item -ItemType Junction -Path $workspaceNodeModules -Target $env:PNPM_CONFIG_MODULES_DIR | Out-Null
}
} else {
New-Item -ItemType Junction -Path $workspaceNodeModules -Target $env:PNPM_CONFIG_MODULES_DIR | Out-Null
}
$corepackShimDir = Join-Path $nodeBin "node_modules\corepack\shims"
if (Test-Path $corepackShimDir) {
$env:PNPM_HOME = $corepackShimDir

View File

@@ -4,6 +4,7 @@ on:
push:
tags:
- "v*"
- "!v*-alpha.*"
paths-ignore:
- "docs/**"
- "**/*.md"
@@ -38,7 +39,11 @@ jobs:
RELEASE_TAG: ${{ inputs.tag }}
run: |
set -euo pipefail
if [[ ! "${RELEASE_TAG}" =~ ^v[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*(-(alpha|beta)\.[1-9][0-9]*)?$ ]]; then
if [[ "${RELEASE_TAG}" == *"-alpha."* ]]; then
echo "Docker alpha image publishing is disabled."
exit 1
fi
if [[ ! "${RELEASE_TAG}" =~ ^v[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*(-beta\.[1-9][0-9]*)?$ ]]; then
echo "Invalid release tag: ${RELEASE_TAG}"
exit 1
fi
@@ -84,7 +89,7 @@ jobs:
fetch-depth: 0
- name: Set up Docker Builder
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
- name: Login to GitHub Container Registry
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
@@ -156,7 +161,7 @@ jobs:
- name: Build and push amd64 image
id: build
# WARNING: KEEP THE OFFICIAL DOCKER ACTION HERE; DO NOT SWITCH THIS BACK TO BLACKSMITH BLINDLY.
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
with:
context: .
platforms: linux/amd64
@@ -174,7 +179,7 @@ jobs:
id: build-browser
if: steps.tags.outputs.browser != ''
# WARNING: KEEP THE OFFICIAL DOCKER ACTION HERE; DO NOT SWITCH THIS BACK TO BLACKSMITH BLINDLY.
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
with:
context: .
platforms: linux/amd64
@@ -275,7 +280,7 @@ jobs:
fetch-depth: 0
- name: Set up Docker Builder
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
- name: Login to GitHub Container Registry
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
@@ -347,7 +352,7 @@ jobs:
- name: Build and push arm64 image
id: build
# WARNING: KEEP THE OFFICIAL DOCKER ACTION HERE; DO NOT SWITCH THIS BACK TO BLACKSMITH BLINDLY.
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
with:
context: .
platforms: linux/arm64
@@ -365,7 +370,7 @@ jobs:
id: build-browser
if: steps.tags.outputs.browser != ''
# WARNING: KEEP THE OFFICIAL DOCKER ACTION HERE; DO NOT SWITCH THIS BACK TO BLACKSMITH BLINDLY.
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
with:
context: .
platforms: linux/arm64
@@ -557,7 +562,7 @@ jobs:
fetch-depth: 1
- name: Set up Docker Builder
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
- name: Login to GitHub Container Registry
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4

View File

@@ -149,7 +149,7 @@ jobs:
- name: Run Codex docs agent
if: steps.gate.outputs.run_agent == 'true'
uses: openai/codex-action@5c3f4ccdb2b8790f73d6b21751ac00e602aa0c02
uses: openai/codex-action@e0fdf01220eb9a88167c4898839d273e3f2609d1
env:
DOCS_AGENT_BASE_SHA: ${{ steps.gate.outputs.review_base_sha }}
DOCS_AGENT_HEAD_SHA: ${{ steps.gate.outputs.review_head_sha }}

View File

@@ -229,7 +229,7 @@ jobs:
needs: [resolve_target]
if: inputs.rerun_group == 'all'
runs-on: ubuntu-24.04
timeout-minutes: 45
timeout-minutes: 20
permissions:
contents: read
steps:
@@ -245,54 +245,11 @@ jobs:
DOCKER_BUILDKIT: "1"
run: |
set -euo pipefail
timeout --kill-after=30s 35m docker build \
timeout --kill-after=30s 15m docker build \
--target runtime-assets \
--build-arg OPENCLAW_EXTENSIONS="diagnostics-otel,codex" \
.
- name: Build and smoke test final Docker runtime image
env:
DOCKER_BUILDKIT: "1"
TARGET_SHA: ${{ needs.resolve_target.outputs.sha }}
run: |
set -euo pipefail
image_ref="openclaw-release-runtime-smoke:${TARGET_SHA}"
timeout --kill-after=30s 35m docker build \
--build-arg OPENCLAW_EXTENSIONS="diagnostics-otel,codex" \
-t "${image_ref}" \
.
docker run --rm --entrypoint /bin/sh "${image_ref}" -lc '
set -eu
test -f /app/src/agents/templates/HEARTBEAT.md
temp_root="$(mktemp -d)"
trap "rm -rf \"${temp_root}\"" EXIT
mkdir -p "${temp_root}/home" "${temp_root}/cwd"
cd "${temp_root}/cwd"
set +e
HOME="${temp_root}/home" \
USERPROFILE="${temp_root}/home" \
OPENCLAW_HOME="${temp_root}/home" \
OPENCLAW_NO_ONBOARD=1 \
OPENCLAW_SUPPRESS_NOTES=1 \
OPENCLAW_DISABLE_BUNDLED_PLUGINS=1 \
OPENCLAW_DISABLE_BUNDLED_ENTRY_SOURCE_FALLBACK=1 \
AWS_EC2_METADATA_DISABLED=true \
AWS_SHARED_CREDENTIALS_FILE="${temp_root}/home/.aws/credentials" \
AWS_CONFIG_FILE="${temp_root}/home/.aws/config" \
node /app/openclaw.mjs agent --message "workspace bootstrap smoke" --session-id "workspace-bootstrap-smoke" --local --timeout 1 --json \
>"${temp_root}/out.log" 2>&1
status="$?"
set -e
if grep -F "Missing workspace template:" "${temp_root}/out.log"; then
cat "${temp_root}/out.log"
exit 1
fi
test -f "${temp_root}/home/.openclaw/workspace/HEARTBEAT.md"
if [ "${status}" -ne 0 ]; then
cat "${temp_root}/out.log"
fi
'
normal_ci:
name: Run normal full CI
needs: [resolve_target, docker_runtime_assets_preflight]
@@ -380,6 +337,21 @@ jobs:
gh_with_retry api --paginate "repos/${GITHUB_REPOSITORY}/actions/runs/${run_id}/jobs?per_page=100" --jq '.jobs[]'
}
fail_fast_failed_jobs() {
local failed_jobs_json
failed_jobs_json="$(
fetch_child_jobs |
jq -s '[.[] | select(.status == "completed" and .conclusion != "success" and .conclusion != "skipped")]'
)"
if jq -e 'length > 0' <<< "$failed_jobs_json" >/dev/null; then
echo "::error::${workflow} has failed child jobs before the workflow completed; cancelling the remaining matrix."
jq '.[] | {name, conclusion, url: .html_url}' <<< "$failed_jobs_json"
cancel_child
trap - EXIT INT TERM
exit 1
fi
}
cancel_child() {
if [[ -n "${run_id:-}" ]]; then
echo "Cancelling child workflow ${workflow}: ${run_id}" >&2
@@ -395,6 +367,9 @@ jobs:
break
fi
poll_count=$((poll_count + 1))
if (( poll_count % 2 == 0 )); then
fail_fast_failed_jobs
fi
if (( poll_count % 10 == 0 )); then
echo "Still waiting on ${workflow}: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${run_id}"
fetch_child_jobs | jq 'select(.status != "completed") | {name, status, url: .html_url}' || true
@@ -510,6 +485,21 @@ jobs:
gh_with_retry api --paginate "repos/${GITHUB_REPOSITORY}/actions/runs/${run_id}/jobs?per_page=100" --jq '.jobs[]'
}
fail_fast_failed_jobs() {
local failed_jobs_json
failed_jobs_json="$(
fetch_child_jobs |
jq -s '[.[] | select(.status == "completed" and .conclusion != "success" and .conclusion != "skipped")]'
)"
if jq -e 'length > 0' <<< "$failed_jobs_json" >/dev/null; then
echo "::error::${workflow} has failed child jobs before the workflow completed; cancelling the remaining matrix."
jq '.[] | {name, conclusion, url: .html_url}' <<< "$failed_jobs_json"
cancel_child
trap - EXIT INT TERM
exit 1
fi
}
cancel_child() {
if [[ -n "${run_id:-}" ]]; then
echo "Cancelling child workflow ${workflow}: ${run_id}" >&2
@@ -525,6 +515,9 @@ jobs:
break
fi
poll_count=$((poll_count + 1))
if (( poll_count % 2 == 0 )); then
fail_fast_failed_jobs
fi
if (( poll_count % 10 == 0 )); then
echo "Still waiting on ${workflow}: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${run_id}"
fetch_child_jobs | jq 'select(.status != "completed") | {name, status, url: .html_url}' || true
@@ -690,6 +683,24 @@ jobs:
[[ "$saw_advisory" == "1" && "$failed" == "0" ]]
}
fail_fast_failed_jobs() {
local failed_jobs_json
if [[ "$workflow" == "openclaw-release-checks.yml" && "$CHILD_WORKFLOW_REF" =~ ^tideclaw/alpha/[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{4}Z$ ]]; then
return 0
fi
failed_jobs_json="$(
fetch_child_jobs |
jq -s '[.[] | select(.status == "completed" and .conclusion != "success" and .conclusion != "skipped")]'
)"
if jq -e 'length > 0' <<< "$failed_jobs_json" >/dev/null; then
echo "::error::${workflow} has failed child jobs before the workflow completed; cancelling the remaining matrix."
jq '.[] | {name, conclusion, url: .html_url}' <<< "$failed_jobs_json"
cancel_child
trap - EXIT INT TERM
exit 1
fi
}
cancel_child() {
if [[ -n "${run_id:-}" ]]; then
echo "Cancelling child workflow ${workflow}: ${run_id}" >&2
@@ -705,6 +716,9 @@ jobs:
break
fi
poll_count=$((poll_count + 1))
if (( poll_count % 2 == 0 )); then
fail_fast_failed_jobs
fi
if (( poll_count % 10 == 0 )); then
echo "Still waiting on ${workflow}: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${run_id}"
fetch_child_jobs | jq 'select(.status != "completed") | {name, status, url: .html_url}' || true
@@ -962,6 +976,21 @@ jobs:
}
trap cancel_child EXIT INT TERM
fail_fast_failed_jobs() {
local failed_jobs_json
failed_jobs_json="$(
gh_with_retry run view "$run_id" --json jobs \
--jq '[.jobs[] | select(.status == "completed" and .conclusion != "success" and .conclusion != "skipped")]'
)"
if jq -e 'length > 0' <<< "$failed_jobs_json" >/dev/null; then
echo "::error::npm-telegram-beta-e2e.yml has failed child jobs before the workflow completed; cancelling the remaining run."
jq '.[] | {name, conclusion, url}' <<< "$failed_jobs_json"
cancel_child
trap - EXIT INT TERM
exit 1
fi
}
poll_count=0
while true; do
status="$(gh_with_retry run view "$run_id" --json status --jq '.status')"
@@ -969,6 +998,9 @@ jobs:
break
fi
poll_count=$((poll_count + 1))
if (( poll_count % 2 == 0 )); then
fail_fast_failed_jobs
fi
if (( poll_count % 10 == 0 )); then
echo "Still waiting on npm-telegram-beta-e2e.yml: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${run_id}"
gh_with_retry run view "$run_id" --json jobs --jq '.jobs[] | select(.status != "completed") | {name, status, url}' || true

View File

@@ -29,7 +29,7 @@ jobs:
uses: actions/checkout@v6
- name: Login to GHCR
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
with:
registry: ghcr.io
username: ${{ github.actor }}

View File

@@ -445,7 +445,7 @@ jobs:
sudo chown -R codex:codex "$GITHUB_WORKSPACE"
- name: Run Codex Mantis Telegram agent
uses: openai/codex-action@5c3f4ccdb2b8790f73d6b21751ac00e602aa0c02
uses: openai/codex-action@e0fdf01220eb9a88167c4898839d273e3f2609d1
env:
BASELINE_REF: ${{ needs.resolve_request.outputs.baseline_ref }}
BASELINE_SHA: ${{ needs.validate_refs.outputs.baseline_revision }}

View File

@@ -563,7 +563,7 @@ jobs:
needs: validate_selected_ref
if: inputs.include_repo_e2e && inputs.live_suite_filter == ''
continue-on-error: ${{ inputs.advisory }}
runs-on: ${{ inputs.use_github_hosted_runners && 'ubuntu-24.04' || 'blacksmith-8vcpu-ubuntu-2404' }}
runs-on: ${{ inputs.use_github_hosted_runners && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }}
timeout-minutes: ${{ inputs.release_test_profile == 'full' && 90 || 60 }}
env:
OPENCLAW_VITEST_MAX_WORKERS: "2"
@@ -595,7 +595,7 @@ jobs:
needs: validate_selected_ref
if: inputs.include_repo_e2e && (inputs.live_suite_filter == '' || inputs.live_suite_filter == 'openshell-e2e')
continue-on-error: ${{ inputs.advisory }}
runs-on: ${{ inputs.use_github_hosted_runners && 'ubuntu-24.04' || 'blacksmith-8vcpu-ubuntu-2404' }}
runs-on: ${{ inputs.use_github_hosted_runners && 'ubuntu-24.04' || 'blacksmith-32vcpu-ubuntu-2404' }}
timeout-minutes: ${{ matrix.timeout_minutes }}
strategy:
fail-fast: false
@@ -1953,7 +1953,7 @@ jobs:
profiles: stable full
- suite_id: native-live-src-gateway-profiles-minimax
label: Native live gateway profiles MiniMax
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=minimax,minimax-portal OPENCLAW_LIVE_GATEWAY_MODELS=minimax/MiniMax-M2.7,minimax-portal/MiniMax-M2.7 OPENCLAW_LIVE_GATEWAY_MAX_MODELS=2 node .release-harness/scripts/test-live-shard.mjs native-live-src-gateway-profiles
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=minimax,minimax-portal OPENCLAW_LIVE_GATEWAY_MODELS=minimax/MiniMax-M3,minimax-portal/MiniMax-M3 OPENCLAW_LIVE_GATEWAY_MAX_MODELS=2 node .release-harness/scripts/test-live-shard.mjs native-live-src-gateway-profiles
timeout_minutes: 60
profile_env_only: false
profiles: stable full
@@ -2252,7 +2252,7 @@ jobs:
profiles: stable full
- suite_id: live-gateway-minimax-docker
label: Docker live gateway MiniMax
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=minimax,minimax-portal OPENCLAW_LIVE_GATEWAY_MODELS=minimax/MiniMax-M2.7,minimax-portal/MiniMax-M2.7 OPENCLAW_LIVE_GATEWAY_MAX_MODELS=1 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=90000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=180000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 35m bash .release-harness/scripts/test-live-gateway-models-docker.sh
command: OPENCLAW_LIVE_GATEWAY_PROVIDERS=minimax,minimax-portal OPENCLAW_LIVE_GATEWAY_MODELS=minimax/MiniMax-M3,minimax-portal/MiniMax-M3 OPENCLAW_LIVE_GATEWAY_MAX_MODELS=1 OPENCLAW_LIVE_GATEWAY_STEP_TIMEOUT_MS=90000 OPENCLAW_LIVE_GATEWAY_MODEL_TIMEOUT_MS=180000 OPENCLAW_LIVE_DOCKER_REPO_ROOT="$GITHUB_WORKSPACE" timeout --foreground --kill-after=30s 35m bash .release-harness/scripts/test-live-gateway-models-docker.sh
timeout_minutes: 40
profile_env_only: false
profiles: stable full

View File

@@ -798,7 +798,7 @@ jobs:
- name: Build private QA runtime
env:
NODE_OPTIONS: --max-old-space-size=8192
run: pnpm build
run: node scripts/build-all.mjs qaRuntime
- name: Run parity lane
env:
@@ -876,7 +876,7 @@ jobs:
- name: Build private QA runtime
env:
NODE_OPTIONS: --max-old-space-size=8192
run: pnpm build
run: node scripts/build-all.mjs qaRuntime
- name: Generate parity report
run: |
@@ -934,7 +934,7 @@ jobs:
- name: Build private QA runtime
env:
NODE_OPTIONS: --max-old-space-size=8192
run: pnpm build
run: node scripts/build-all.mjs qaRuntime
- name: Run runtime parity lane
id: runtime_parity_lane
@@ -1101,7 +1101,7 @@ jobs:
- name: Build private QA runtime
env:
NODE_OPTIONS: --max-old-space-size=8192
run: pnpm build
run: node scripts/build-all.mjs qaRuntime
- name: Run Matrix live lane
id: run_lane
@@ -1199,7 +1199,7 @@ jobs:
- name: Build private QA runtime
env:
NODE_OPTIONS: --max-old-space-size=8192
run: pnpm build
run: node scripts/build-all.mjs qaRuntime
- name: Run Telegram live lane
id: run_lane
@@ -1295,7 +1295,7 @@ jobs:
- name: Build private QA runtime
env:
NODE_OPTIONS: --max-old-space-size=8192
run: pnpm build
run: node scripts/build-all.mjs qaRuntime
- name: Run Discord live lane
id: run_lane
@@ -1393,7 +1393,7 @@ jobs:
- name: Build private QA runtime
env:
NODE_OPTIONS: --max-old-space-size=8192
run: pnpm build
run: node scripts/build-all.mjs qaRuntime
- name: Run WhatsApp live lane
id: run_lane
@@ -1488,7 +1488,7 @@ jobs:
- name: Build private QA runtime
env:
NODE_OPTIONS: --max-old-space-size=8192
run: pnpm build
run: node scripts/build-all.mjs qaRuntime
- name: Run Slack live lane
id: run_lane

View File

@@ -46,11 +46,12 @@ on:
default: true
type: boolean
release_profile:
description: Release coverage profile used for release evidence summaries
description: Release coverage profile used for release evidence summaries; default reads it from the validation manifest
required: false
default: beta
default: from-validation
type: choice
options:
- from-validation
- beta
- stable
- full
@@ -135,9 +136,9 @@ jobs:
exit 1
fi
case "$RELEASE_PROFILE" in
beta|stable|full) ;;
from-validation|beta|stable|full) ;;
*)
echo "release_profile must be one of: beta, stable, full" >&2
echo "release_profile must be one of: from-validation, beta, stable, full" >&2
exit 1
;;
esac
@@ -259,6 +260,7 @@ jobs:
echo "sha=$release_sha" >> "$GITHUB_OUTPUT"
- name: Validate full release validation manifest
id: full_manifest
if: ${{ inputs.publish_openclaw_npm }}
env:
GH_TOKEN: ${{ github.token }}
@@ -289,7 +291,7 @@ jobs:
echo "Full release validation target SHA mismatch: expected $EXPECTED_SHA, got $target_sha" >&2
exit 1
fi
if [[ "$release_profile" != "$EXPECTED_RELEASE_PROFILE" ]]; then
if [[ "$EXPECTED_RELEASE_PROFILE" != "from-validation" && "$release_profile" != "$EXPECTED_RELEASE_PROFILE" ]]; then
echo "Full release validation profile mismatch: expected $EXPECTED_RELEASE_PROFILE, got $release_profile" >&2
exit 1
fi
@@ -297,6 +299,7 @@ jobs:
echo "Full release validation must run rerun_group=all before npm publish; got $rerun_group" >&2
exit 1
fi
echo "release_profile=$release_profile" >> "$GITHUB_OUTPUT"
- name: Validate release tag is reachable from a trusted release branch
env:
@@ -332,7 +335,7 @@ jobs:
env:
RELEASE_TAG: ${{ inputs.tag }}
TARGET_SHA: ${{ steps.manifest.outputs.sha || steps.ref.outputs.sha }}
RELEASE_PROFILE: ${{ inputs.release_profile }}
RELEASE_PROFILE: ${{ steps.full_manifest.outputs.release_profile || inputs.release_profile }}
FULL_RELEASE_VALIDATION_RUN_ID: ${{ inputs.full_release_validation_run_id }}
run: |
{
@@ -501,7 +504,7 @@ jobs:
wait_for_run() {
local workflow="$1"
local run_id="$2"
local status conclusion url updated_at created_at duration_seconds duration_label last_state
local status conclusion url updated_at created_at duration_seconds duration_label last_state failed_json
last_state=""
while true; do
@@ -510,6 +513,14 @@ jobs:
if [[ "$status" == "completed" ]]; then
break
fi
failed_json="$(gh run view --repo "$GITHUB_REPOSITORY" "$run_id" --json jobs \
--jq '[.jobs[] | select(.status == "completed" and .conclusion != "success" and .conclusion != "skipped")]' || true)"
if [[ -n "${failed_json}" ]] && jq -e 'length > 0' <<< "$failed_json" >/dev/null; then
echo "${workflow} has failed jobs before the workflow completed: https://github.com/${GITHUB_REPOSITORY}/actions/runs/${run_id}" >&2
jq '.[] | {name, conclusion, url}' <<< "$failed_json" >&2 || true
print_failed_run_summary "${run_id}"
return 1
fi
url="$(printf '%s' "$run_json" | jq -r '.url')"
updated_at="$(printf '%s' "$run_json" | jq -r '.updatedAt')"
state="${status}:${updated_at}"

View File

@@ -53,7 +53,7 @@ jobs:
scripts/run-opengrep.sh --sarif --error
- name: Upload SARIF to GitHub Code Scanning
uses: github/codeql-action/upload-sarif@v4
uses: github/codeql-action/upload-sarif@v4.36.1
# Only upload if the scan actually produced a SARIF file.
if: always() && hashFiles('.opengrep-out/precise.sarif') != ''
with:

View File

@@ -44,7 +44,7 @@ jobs:
uses: actions/checkout@v6
with:
ref: ${{ github.sha }}
fetch-depth: 1
fetch-depth: 2
fetch-tags: false
persist-credentials: false
submodules: false
@@ -74,6 +74,7 @@ jobs:
- name: Run opengrep on PR diff
env:
OPENCLAW_OPENGREP_BASE_REF: ${{ github.event.pull_request.base.sha }}...HEAD
OPENCLAW_OPENGREP_MERGE_HEAD_FIRST_PARENT: "1"
# Findings from precise rules block this workflow. Pull requests scan
# changed first-party source paths only so findings stay attributable to
# the PR diff. Test/fixture/QA path exclusions live in `.semgrepignore`
@@ -83,7 +84,7 @@ jobs:
scripts/run-opengrep.sh --changed --sarif --error
- name: Upload SARIF to GitHub Code Scanning
uses: github/codeql-action/upload-sarif@v4
uses: github/codeql-action/upload-sarif@v4.36.1
# Only upload if the scan actually produced a SARIF file.
if: always() && hashFiles('.opengrep-out/precise.sarif') != ''
with:

View File

@@ -818,6 +818,7 @@ jobs:
OPENCLAW_QA_CONVEX_SECRET_CI: ${{ secrets.OPENCLAW_QA_CONVEX_SECRET_CI }}
OPENCLAW_QA_REDACT_PUBLIC_METADATA: "1"
OPENCLAW_QA_SLACK_CAPTURE_CONTENT: "1"
OPENCLAW_QA_TRANSPORT_READY_TIMEOUT_MS: "180000"
INPUT_SCENARIO: ${{ github.event_name == 'workflow_dispatch' && inputs.slack_scenario || '' }}
run: |
set -euo pipefail

View File

@@ -35,7 +35,7 @@ jobs:
submodules: false
- name: Set up Docker Builder
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4
- name: Build minimal sandbox base (USER sandbox)
shell: bash

View File

@@ -129,7 +129,7 @@ jobs:
- name: Run Codex test performance agent
if: steps.gate.outputs.run_agent == 'true'
uses: openai/codex-action@5c3f4ccdb2b8790f73d6b21751ac00e602aa0c02
uses: openai/codex-action@e0fdf01220eb9a88167c4898839d273e3f2609d1
with:
openai-api-key: ${{ secrets.OPENCLAW_TEST_PERF_AGENT_OPENAI_API_KEY || secrets.OPENAI_API_KEY }}
prompt-file: .github/codex/prompts/test-performance-agent.md

View File

@@ -27,7 +27,9 @@ env:
jobs:
tui-pty:
runs-on: ubuntu-24.04
timeout-minutes: 5
timeout-minutes: 8
env:
OPENCLAW_TUI_PTY_INCLUDE_LOCAL: "1"
steps:
- name: Checkout
uses: actions/checkout@v6
@@ -38,4 +40,4 @@ jobs:
install-bun: "false"
- name: Run TUI PTY tests
run: timeout --kill-after=30s 120s node scripts/run-vitest.mjs run --config test/vitest/vitest.tui-pty.config.ts
run: timeout --kill-after=30s 240s node scripts/run-vitest.mjs run --config test/vitest/vitest.tui-pty.config.ts

View File

@@ -0,0 +1,126 @@
name: Windows Node Release
on:
workflow_dispatch:
inputs:
tag:
description: Existing OpenClaw release tag to receive Windows Hub installers, for example v2026.6.1
required: true
type: string
windows_node_tag:
description: openclaw-windows-node release tag to promote, or latest
required: true
default: latest
type: string
permissions:
contents: write
concurrency:
group: windows-node-release-${{ inputs.tag }}
cancel-in-progress: false
jobs:
promote_signed_windows_installers:
name: Promote signed Windows installers
runs-on: windows-latest
timeout-minutes: 30
steps:
- name: Validate inputs
shell: pwsh
env:
RELEASE_TAG: ${{ inputs.tag }}
WINDOWS_NODE_TAG: ${{ inputs.windows_node_tag }}
GH_TOKEN: ${{ github.token }}
run: |
if ($env:RELEASE_TAG -notmatch '^v[0-9]{4}\.[1-9][0-9]*\.[1-9][0-9]*((-(alpha|beta)\.[1-9][0-9]*)|(-[1-9][0-9]*))?$') {
throw "Invalid OpenClaw release tag: $env:RELEASE_TAG"
}
if ($env:WINDOWS_NODE_TAG -ne "latest" -and $env:WINDOWS_NODE_TAG -notmatch '^v[0-9]+\.[0-9]+\.[0-9]+([-.][0-9A-Za-z.-]+)?$') {
throw "Invalid openclaw-windows-node release tag: $env:WINDOWS_NODE_TAG"
}
gh release view $env:RELEASE_TAG --repo $env:GITHUB_REPOSITORY | Out-Null
- name: Download Windows Hub release installers
shell: pwsh
env:
WINDOWS_NODE_TAG: ${{ inputs.windows_node_tag }}
GH_TOKEN: ${{ github.token }}
run: |
New-Item -ItemType Directory -Force -Path dist | Out-Null
$tagArgs = @()
if ($env:WINDOWS_NODE_TAG -ne "latest") {
$tagArgs += $env:WINDOWS_NODE_TAG
}
gh release download @tagArgs `
--repo openclaw/openclaw-windows-node `
--pattern "OpenClawCompanion-Setup-*.exe" `
--dir dist
$expected = @(
"dist/OpenClawCompanion-Setup-x64.exe",
"dist/OpenClawCompanion-Setup-arm64.exe"
)
foreach ($file in $expected) {
if (-not (Test-Path -LiteralPath $file)) {
throw "Missing expected Windows installer: $file"
}
}
- name: Verify Authenticode signatures
shell: pwsh
run: |
Get-ChildItem -LiteralPath dist -Filter "OpenClawCompanion-Setup-*.exe" | ForEach-Object {
$signature = Get-AuthenticodeSignature -LiteralPath $_.FullName
if ($signature.Status -ne "Valid") {
throw "$($_.Name) Authenticode signature was $($signature.Status)."
}
if (-not $signature.SignerCertificate) {
throw "$($_.Name) has no signer certificate."
}
[pscustomobject]@{
File = $_.Name
Signer = $signature.SignerCertificate.Subject
Thumbprint = $signature.SignerCertificate.Thumbprint
} | Format-List
}
- name: Write SHA-256 manifest
shell: pwsh
run: |
Get-ChildItem -LiteralPath dist -Filter "OpenClawCompanion-Setup-*.exe" |
Sort-Object Name |
ForEach-Object {
$hash = Get-FileHash -Algorithm SHA256 -LiteralPath $_.FullName
"$($hash.Hash.ToLowerInvariant()) $($_.Name)"
} | Set-Content -Encoding utf8NoBOM -Path dist/OpenClawCompanion-SHA256SUMS.txt
- name: Upload to OpenClaw release
shell: pwsh
env:
RELEASE_TAG: ${{ inputs.tag }}
GH_TOKEN: ${{ github.token }}
run: |
gh release upload $env:RELEASE_TAG `
dist/OpenClawCompanion-Setup-x64.exe `
dist/OpenClawCompanion-Setup-arm64.exe `
dist/OpenClawCompanion-SHA256SUMS.txt `
--repo $env:GITHUB_REPOSITORY `
--clobber
- name: Summary
shell: pwsh
env:
RELEASE_TAG: ${{ inputs.tag }}
WINDOWS_NODE_TAG: ${{ inputs.windows_node_tag }}
run: |
@"
## Windows Hub installers promoted
OpenClaw release: $env:RELEASE_TAG
Source release: openclaw/openclaw-windows-node@$env:WINDOWS_NODE_TAG
- https://github.com/openclaw/openclaw/releases/download/$env:RELEASE_TAG/OpenClawCompanion-Setup-x64.exe
- https://github.com/openclaw/openclaw/releases/download/$env:RELEASE_TAG/OpenClawCompanion-Setup-arm64.exe
- https://github.com/openclaw/openclaw/releases/download/$env:RELEASE_TAG/OpenClawCompanion-SHA256SUMS.txt
"@ >> $env:GITHUB_STEP_SUMMARY

View File

@@ -82,7 +82,10 @@
"typescript/no-meaningless-void-operator": "error",
"typescript/no-misused-promises": "error",
"typescript/no-inferrable-types": "error",
"typescript/only-throw-error": "error",
"typescript/no-non-null-asserted-nullish-coalescing": "error",
"typescript/prefer-promise-reject-errors": "error",
"typescript/restrict-plus-operands": "error",
"typescript/no-unnecessary-qualifier": "error",
"typescript/no-unnecessary-type-assertion": "error",
"typescript/no-unnecessary-type-arguments": "error",
@@ -109,6 +112,8 @@
"typescript/require-array-sort-compare": "error",
"typescript/restrict-template-expressions": "error",
"typescript/triple-slash-reference": "error",
"typescript/unbound-method": "error",
"typescript/use-unknown-in-catch-callback-variable": "error",
"unicorn/consistent-date-clone": "error",
"unicorn/consistent-empty-array-spread": "error",
"unicorn/consistent-function-scoping": "off",
@@ -128,6 +133,7 @@
"unicorn/no-unnecessary-slice-end": "error",
"unicorn/no-useless-error-capture-stack-trace": "error",
"unicorn/no-useless-promise-resolve-reject": "error",
"unicorn/no-useless-switch-case": "error",
"unicorn/no-zero-fractions": "error",
"unicorn/prefer-date-now": "error",
"unicorn/prefer-dom-node-text-content": "error",

View File

@@ -2,61 +2,191 @@
Docs: https://docs.openclaw.ai
## 2026.5.31
## 2026.6.5
### Highlights
- Agents and CLI-backed runtimes recover more cleanly from interrupted tool calls, stale session bindings, compaction handoffs, and media delivery retries. (#88129, #88136, #88141, #88162, #88182)
- Channels and mobile delivery are steadier across Telegram, WhatsApp, iMessage, Slack, Discord, Microsoft Teams, Google Chat, Google Meet, and iOS realtime Talk. (#88096, #88105, #88183, #88231)
- Provider and plugin requests now bound more timers, retries, OAuth/device-code lifetimes, media downloads, local service probes, and generated-content polling paths before they can hang a run.
- Skills, session metadata, gateway runtime state, plugin metadata, and store writes do less repeated work on hot paths while keeping config and dispatch behavior stable.
- Skills and plugin loading now handle stale disabled snapshots and loader failures more clearly, so channel turns avoid disabled SecretRefs and operators get better recovery guidance. (#79072, #79173) Thanks @zeus1959.
- Workboard, SecretRef plugin manifests, hosted iOS push relay, and external Copilot/Tokenjuice packaging add broader orchestration, integration, and plugin delivery surfaces. (#82326, #87469, #87796, #88107, #88117)
- Release, CI, Docker, E2E, and diagnostics lanes now cap more logs, response bodies, readiness probes, artifact checks, and status polling so failures report bounded proof instead of stalling.
- QQBot now strips model reasoning/thinking scaffolding before native delivery, preventing raw `<thinking>` content from leaking into channel replies. (#89913, #90132) Thanks @openperf.
- MCP tool results now coerce `resource_link`, `resource`, `audio`, malformed image, and future non-text/image blocks at the materialize boundary, preventing Anthropic 400s and poisoned session history after a tool returns richer MCP content. (#90710, #90728) Thanks @RanSHammer and @849261680.
- Anthropic extended-thinking sessions recover after prompt-cache expiry or Gateway restart because stream start events wait for `message_start`, letting pre-generation signature errors trigger the existing recovery retry. (#90667, #90697) Thanks @openperf.
- Parallel is now a bundled `web_search` provider with `PARALLEL_API_KEY` discovery, guarded endpoint handling, cache-safe session ids, onboarding picker support, and docs. (#85158) Thanks @NormallyGaussian.
- Google Vertex ADC users get static catalog rows and runtime model resolution again, while single-provider cooldown recovery and memory adapter status checks are more reliable. (#90506, #90609, #90717, #90816) Thanks @849261680.
- Matrix can preflight voice notes before mention gating, preserve thread reads/replies through Matrix relations pagination, and carry QA coverage for voice and thread flows. (#78016, #90415)
- Auth and plugin install state is more durable: auth profiles now live in SQLite, official npm plugin install records keep their trusted pins, and prerelease fallback integrity checks avoid carrying stale integrity forward. (#89102, #88585)
- macOS node mode no longer silently self-reconnects away from a healthy direct Gateway session, reducing unexpected companion app session churn. (#90668, #90815) Thanks @vrurg.
- Upgrade and service paths are safer: cron legacy JSON stores migrate during doctor preflight, service env placeholders no longer mask state-dir secrets, WhatsApp startup waits are bounded, and disabled WhatsApp accounts tear down on config reload. (#90072, #90208, #90277, #90488, #90486, #87951, #87965) Thanks @MonkeyLeeT, @sallyom, @mcaxtr, and @MukundaKatta.
### Changes
- Docs: add a dedicated Skill Workshop guide covering governed skill creation, reviewable proposals, CLI, Gateway, agent tool behavior, approval policy, support files, and recovery. Thanks @shakkernerd.
- Search/providers: add the Parallel bundled web-search plugin, live provider tests, registration contracts, onboarding/docs wiring, and guarded `api.parallel.ai/v1/search` support. (#85158) Thanks @NormallyGaussian.
- Matrix/channels: add voice-message preflight and thread-aware read/reply behavior, including Matrix QA scenario wiring and docs for voice-message behavior. (#78016, #90415)
- Skills/ClawHub: install ClawHub skills backed by GitHub repositories through the resolved install API, download the pinned GitHub commit, keep install-policy checks, and report install telemetry after success. (#90478) Thanks @Patrick-Erichsen.
- Google Chat/channels: add native approval card actions and click handling so Google Chat approvals use platform-native cards instead of generic message flow.
- Mobile: Android provider/model screens now surface expiring, unavailable, unresolved, and attention states more clearly, while iOS settings and Talk tabs keep diagnostics, gateway rows, attachment labels, and unavailable Talk controls reachable.
- Memory: QMD search can use the new rerank toggle, and memory adapter status uses the resolved default model identity when checking plain status. (#61834)
- Docs/tooling: add Parallel search docs, refresh weather-skill guidance toward `web_fetch`, clarify legacy `openai-codex` auth, document release/test helper scripts, and tighten changed-test routing docs for CI/debugging work. (#90028, #90250) Thanks @fuller-stack-dev.
- Platform maintenance: refresh Android, Swift/macOS, Docker, CodeQL, Buildx, Docker build/push, and Codex Action dependencies for this release train. (#74980, #81757, #86481, #86483, #90601)
### Fixes
- Channel content boundaries: QQBot now strips reasoning/thinking tags before sending, preserving final answers while hiding internal model narration from users. (#89913, #90132) Thanks @openperf.
- Agents/MCP/providers: coerce non-text/image MCP tool-result blocks before they reach provider converters, preserving valid images and turning richer MCP content into text instead of malformed image blocks. (#90710, #90728) Thanks @RanSHammer and @849261680.
- Anthropic/Codex/ACP/agent recovery: defer Anthropic stream start events until `message_start`, strip stale compaction thinking signatures before Anthropic replay, detect unsigned thinking-only stalls, refresh prompt fences after compaction writes, reject empty completion handoffs, preserve parent streaming-off overrides/shared progress commentary, forward heartbeat metadata to context-engine hooks, and cover Codex session/thread migration edge cases. (#90667, #90697, #90163, #90108, #89874, #89505, #90632, #89302, #90729, #90317, #90319) Thanks @openperf, @100yenadmin, and @ooiuuii.
- Provider/model resolution: preserve Google Vertex ADC auth markers in generated catalogs, re-probe a single-provider primary after cooldown, share Codex model visibility, fail closed for unknown model auth, preserve Codex alias availability, keep unresolved profile refs unknown, and avoid resolving auth while listing models. (#90506, #90609, #90717, #90702) Thanks @849261680.
- Gateway/macOS/mobile: avoid duplicate Gateway probe warnings by identity, rate-limit node pairing requests while preserving paired-node reconnects, keep macOS node mode on a healthy direct Gateway session, keep iOS diagnostics and gateway rows reachable, and avoid Linux ARM Gradle resource tasks during Android builds. (#85791, #90147, #90668, #90815) Thanks @giodl73-repo and @vrurg.
- TUI/chat/Workboard/auto-reply: optimistic user messages stay stable across stale history reloads, runId reassignment, and abort windows instead of disappearing, jumping, or lingering as ghost rows; Workboard stale lifecycle bulk updates no longer overwrite newer status/provenance; message-tool sends now count as delivery. (#86205, #89600, #88592, #90123) Thanks @RomneyDa.
- Cron/update/service env: doctor config preflight now migrates legacy cron JSON stores into SQLite before runtime reads, service env planning skips unresolved placeholders that would mask state-dir `.env` values, and session transcript rewrites keep registry markers/discriminants consistent. (#90072, #90208, #90277, #90488) Thanks @MonkeyLeeT and @sallyom.
- Security/config/tooling: guard MCP HTTP redirects, protect global agent config defaults, and keep release/test/tooling proof failures bounded and explicit. (#89732, #90145)
- Channels: WhatsApp restarts when per-account config changes, bounds background startup waits, closes failed sockets, and preserves reconnect behavior; Mattermost slash commands keep their state on `globalThis`; Feishu streaming cards preserve full merged content; voice-call tracks Twilio streams after connect; ClickClack reply tools respect `toolsAllow`. (#87951, #87965, #90486, #68113, #90534, #90181, #90607, #89500) Thanks @MukundaKatta, @mcaxtr, @infoanton, @mushuiyu886, and @sahibzada-allahyar.
- Release/CI/E2E: main CI guard drift, PR merge diff scoping, live Docker credential staging, base-image qualification, installer Docker classification, Playwright dependency install recovery, API-key auth for Codex live Docker lanes, Parallels option terminators, and JSON-mode progress handling are tighter so release proof fails cleaner. (#90532, #90287, #90058) Thanks @RomneyDa, @hxy91819, and @mrunalp.
- Tests/state isolation: provider, media, auth, cron, task, session, sandbox, Gateway, and Codex timeout fixtures now scope more home/state/env data per test, reducing cross-test leakage and making release validation failures less noisy. (#90027, #89974)
## 2026.6.2
### Highlights
- Plugin and skill installs now use an operator install policy instead of the old dangerous-code scanner path, with clearer doctor, CLI, ClawHub, and troubleshooting surfaces for package, archive, source, upload, and marketplace installs. (#89516) Thanks @joshavant.
- Telegram, Feishu, Discord, WhatsApp, and outbound delivery paths got safer around duplicate transcript mirrors, Telegram admin writeback, streamed-final previews, approval allowlists, setup runtime state, poll modifiers, Discord voice errors, and internal progress traces. (#88973, #89626, #89812, #89035, #89814, #89813, #89601) Thanks @pgondhi987, @Petru2224, @zhangguiping-xydt, @codezz, and @takhoffman.
- Chat, Control UI, Skill Workshop, Workboard, Android companion shell, and WebChat flows now preserve visible streaming text, reconcile completed sends, expose ACK timing, add Workboard keyboard movement, harden dialog accessibility, lazy-load usage views, keep current chat toggles working, and improve Android companion-first shell navigation. (#89801, #89777, #89802) Thanks @vincentkoc.
- Security, policy, and config recovery now reject corrupt shell snapshots, unsupported policy keys, unsafe exec approval precheck environments, malformed script limits, and suspicious gateway startup configs while adding data-handling conformance checks. (#89701, #87074, #81488, #87056, #89480) Thanks @RomneyDa, @giodl73-repo, and @mmaps.
- Gateway, agent, Codex, provider, model, and memory paths now recover session write-lock release failures, abandoned Codex app-server startups, stream-to-parent ACP spawns, custom-provider runtime fanout, bundled provider aliases, prompt-cache boundaries, Gemini stop sequences, Kimi cache markers, and watcher pressure warnings. (#89811, #89244) Thanks @RomneyDa and @takhoffman.
- Release, CI, Docker, Crabbox/Testbox, package, and E2E validation lanes now bound more network calls, malformed numeric limits, process groups, cleanup leaks, package hydration paths, Windows installer publishing, release asset verification, and log drains so failures produce bounded proof instead of hanging.
### Changes
- Plugins/security: replace dangerous-code scanner enforcement with operator install policy, install-policy context, doctor checks, install/update CLI wiring, ClawHub metadata paths, and package/archive/source/upload lifecycle coverage. (#89516) Thanks @joshavant.
- Policy: add data-handling conformance checks and reject unsupported policy keys. (#87056, #87074) Thanks @giodl73-repo.
- Telegram/channels: show commentary and reasoning in progress drafts, share progress draft compositors across channel plugins, and keep Telegram polling stop/reset boundaries cheaper and more reliable.
- UI/mobile: add Workboard keyboard movement controls, tighten Workboard card operations, improve Android companion-first shell UX, and document chat ACK timing metadata. (#89802) Thanks @vincentkoc.
- Release metadata: align the root package, publishable plugin manifests, generated shrinkwraps, appcast, iOS, Android, macOS, Matrix plugin changelog, and docs/generated baselines with the 2026.6.2 beta train.
- Release/packaging: promote Windows node installer publishing, require verified Windows release asset links, and document GitHub release-note edits.
- Docs: refresh Windows Hub setup guidance and document Gateway, CLI, and plugin SDK helper contracts.
### Fixes
- Channels/outbound: keep channel sends durable when transcript mirroring fails, stop schema-padded poll modifiers from blocking normal sends, preserve WebChat `sessions_send` handoffs, preserve Discord channel-label suppression while hiding internal agent failure traces, match Discord libopus error shapes, and sanitize Discord tool progress scaffolding. (#89626, #89812, #89601) Thanks @Petru2224, @codezz, and @takhoffman.
- Telegram/Feishu: require admin rights for Telegram target writeback, keep Telegram DM exec approval allowlists working with `ask:off`, prevent Telegram preview duplication across streaming modes, isolate verbose status after streamed finals, cancel clean restart stop timers, slow polling restart storms, and wire Feishu setup runtime setters. (#88973, #89035, #89813, #89814) Thanks @pgondhi987, @zhangguiping-xydt, and @takhoffman.
- Feishu: preserve full streaming card content by sending the merged text on each update instead of only the latest delta, so card readers see complete output when intermediate frames are missed. (#90181) Thanks @mushuiyu886.
- Chat/UI/Gateway: preserve visible chat stream text, clear stale stream buffers before terminal commits, reconcile completed sends, scroll pending sends into view, harden Workboard dialog accessibility, stabilize WebChat prompt-cache affinity, overlap chat catalog startup, render chat history incrementally, lazy-load usage dashboard, and report gateway health auth diagnostics. (#89337) Thanks @RomneyDa.
- Agents/Codex/providers/models: release session write locks when prompt-release fence reads fail, retire abandoned Codex app-server startups, keep stream-to-parent ACP spawns registered, close Codex startup clients on timeout, recover bundled provider aliases, avoid custom-provider runtime fanout, preserve provider prompt-cache boundaries, forward Gemini stop sequences, and strip Kimi-incompatible Anthropic cache markers. (#89811) Thanks @takhoffman.
- Memory/build/update: warn after startup watcher pressure checks, externalize optional Baileys image backends, restore and pin Canvas A2UI compatibility assets, keep plugin repair fetch failures nonblocking, restore Skill Workshop view switching, and keep the current chat toggle active after awaited session switches. (#89244) Thanks @RomneyDa.
- Plugins/auth: keep Hermes migration reports pointed at SQLite auth-profile stores and keep plugin auth-profile reuse tests on the current store path.
- Plugins/CLI: avoid importing the runtime plugin loader only to clear in-process caches after short-lived plugin install, enable, disable, update, and uninstall commands refresh registry metadata.
- Security/config/tooling: reject corrupt shell snapshots, suspicious gateway startup configs, malformed release/test/tooling/Docker/perf numeric limits, oversized audit responses, unsafe exec precheck env, and invalid pending-agent SQLite scaffold denials. (#89701, #89705, #89480, #81488) Thanks @RomneyDa and @mmaps.
- Release/CI/E2E: restore package changelog extraction after the post-2026.6.1 version bump, keep hydrated pnpm modules under `node_modules` for ARM/Linux package lifecycle scripts, keep OpenAI live-cache prerequisites advisory while Anthropic prerequisites stay blocking, retry Windows Parallels background log appends on transient file-lock errors, bound candidate GitHub and cross-OS Discord fetches, harden ARM smoke/browser checks, show Docker build heartbeats, reset Crabbox pnpm hydrate state, and isolate Testbox/Docker/release journey artifacts.
- Release/CI/E2E: keep Crabbox hydrate pnpm stores on the persistent cache volume while still resetting volatile modules, reducing cold installs and runner memory churn.
- Release/CI/E2E: fail secret-provider proof startup immediately when the gateway exits by signal instead of waiting for the readiness timeout.
- Release/CI/E2E: report plugin gateway gauntlet command-log write failures as failed rows instead of crashing the harness from child-process callbacks.
- Release/CI/E2E: abort stalled Kitchen Sink RPC readiness probes as soon as the gateway exits so proof failures return promptly.
- Release/CI/E2E: keep Parallels JSON-mode progress on stderr so macOS, Linux, Windows, and aggregate update smoke summaries stay parseable on stdout.
- Release/CI/E2E: fail Crabbox sparse-sync runs clearly when their temporary full checkout disappears while the child process is running, instead of pretending the child's deleted cwd can be repaired.
- Release/CI/E2E: fail PTY-backed E2E commands when transcript logs cannot be written instead of letting missing proof capture crash around a live child process.
- Release/CI/E2E: fail mock OpenAI request-log write errors with clear HTTP responses instead of leaving provider proof clients waiting on a broken socket.
- Release/CI/E2E: fail Parallels host-command log write errors through the command result path instead of leaving streaming smoke phases unresolved.
## 2026.6.1
### Highlights
- Agents and CLI-backed runtimes recover more cleanly from interrupted tool calls, stale session bindings, compaction handoffs, auth-profile failover, reasoning-tag cleanup, and media delivery retries. (#85798, #87484, #88129, #88136, #88141, #88162, #88182, #88924, #89220) Thanks @RomneyDa, @neeravmakwana, and @omarshahine.
- Channels and mobile delivery are steadier across Telegram, WhatsApp, iMessage, Slack, Discord, Microsoft Teams, Google Chat, Google Meet, QQBot, and iOS realtime Talk. (#88096, #88105, #88183, #88749, #88866, #88948, #88984, #89015, #88231) Thanks @omarshahine, @Jensenwgd, and @sliverp.
- Provider and plugin requests now bound more timers, retries, OAuth/device-code lifetimes, media downloads, local service probes, generated-content polling, provider-catalog failures, reasoning output, and model catalog paths before they can hang a run. (#88480, #88512, #88767, #88781, #88851, #88860, #89343, #89379, #89400) Thanks @vincentkoc, @charles-openclaw, @zz327455573, @849261680, and @XuZehan-iCenter.
- Skills, Skill Workshop, and plugin loading now handle proposal review, stale disabled snapshots, support-file approvals, locale/routing fixes, and loader failures more clearly, so channel turns avoid disabled SecretRefs and operators get better recovery guidance. (#79072, #79173, #88734) Thanks @zeus1959 and @shakkernerd.
- Workboard, SecretRef plugin manifests, hosted iOS push relay, typed presentation command actions, and external Copilot/Tokenjuice packaging add broader orchestration, integration, SDK, and plugin delivery surfaces. (#82326, #87469, #87796, #88107, #88117, #88721, #89336) Thanks @RomneyDa.
- Chat and Control UI startup paths keep sends alive through history loading, stream deltas incrementally, skip markdown work while streaming, keep drafts local while typing, clear the composer after sends, trace first-output latency, cache transcript renders, prioritize first connect, and expose calmer composer controls and notification settings. (#74715, #88772, #88825, #88952, #88960, #88998, #89030, #89106) Thanks @VladyslavLevchuk, @vincentkoc, and @sallyom.
- iMessage monitor state, inbound queues, Discord thread bindings, plugin install ledgers, session metadata, gateway runtime state, plugin metadata, memory watchers, and store writes moved toward SQLite-backed or cached state so restarts and hot paths do less repeated work. (#88794, #88797, #88866, #89075, #89185, #89188, #85351) Thanks @RomneyDa and @NianJiuZst.
- Release, CI, Docker, E2E, plugin install, update, doctor, diagnostics, and security lanes now cap more logs, response bodies, readiness probes, artifact checks, status polling, child workflow waits, docker package cleanup, quiet test stalls, downgrade repair, and health probes so failures report bounded proof instead of stalling. (#84988, #87914, #87952, #88966, #89169, #89701, #89731) Thanks @LibraHo, @Niriakot, @MukundaKatta, and @RomneyDa.
### Changes
- Docs: add a dedicated Skill Workshop guide covering governed skill creation, reviewable proposals, CLI, Gateway, agent tool behavior, approval policy, support files, and recovery; refresh ClawHub cards; and add ClawHub CLI, iMessage SSH-wrapper TCC, Android helper, diff-language, and host-local media-send guidance. (#79658, #88734, #88758, #88865, #89297) Thanks @simplyclever914, @shakkernerd, @vyctorbrzezowski, @TurboTheTurtle, @RomneyDa, and @Wang-Yeah623.
- Skills: let the `skill_workshop` agent tool apply, reject, and quarantine explicit proposals through the guarded review flow. Thanks @shakkernerd.
- Skills: let proposals carry approved support files under standard skill folders, with scanner, hash, and rollback safeguards. Thanks @shakkernerd.
- Skills: let pending proposals be revised in place with versioned, dated proposal frontmatter before approval. Thanks @shakkernerd.
- Skills: add Skill Workshop with pending proposals, CLI/Gateway review actions, rollback metadata, and the `skill_workshop` agent tool. Thanks @shakkernerd.
- Skill Workshop: add the Control UI navigation, styled dashboard, proposal today view, revision dialog, file preview modal, searchable preview files, reusable session handoff, and localized strings.
- Plugins: externalize Tokenjuice as the official `@openclaw/tokenjuice` plugin with npm and ClawHub publish metadata.
- Plugins: externalize the GitHub Copilot agent runtime as the official `@openclaw/copilot` plugin with npm and ClawHub publish metadata.
- iOS: add hosted push relay defaults, realtime Talk playback, and a guarded WebSocket ping path for more reliable mobile sessions. (#88096, #88105, #88231)
- iOS: support native iPad display layouts.
- Android: add installed-app inspection commands, notification picker helpers, and updated-system-app classification.
- Workboard: add orchestration primitives and agent coordination tools for multi-agent planning and run tracking. (#87469)
- Workboard: wire task-backed board runs and show task comments in the edit modal.
- Code mode: add internal namespaces for scoped agent/global sessions and exact namespace tool dispatch. (#88043)
- Code mode: add MCP API files and docs for code-mode integrations.
- Gateway: support Tailscale Serve service names for local service routing.
- Control UI: add a Dreaming-tab agent selector and propagate the selected agent through Dreaming status, diary, and diary actions. (#78748) Thanks @stevenepalmer.
- Control UI: add calmer chat composer controls, local draft typing state, and first-output latency instrumentation for active chat entry. (#88772, #88998) Thanks @vincentkoc.
- Plugins: add a SecretRef provider integration manifest contract and extract shared LLM core packages for provider/plugin reuse. (#82326, #88117)
- Plugin SDK: add typed presentation command actions and the bounded `resolve_exec_env` hook for plugin-provided exec environment contributions. (#88721)
- Plugins: persist the plugin install index in SQLite so installed package lookup survives reloads with less filesystem scanning. (#88794)
- Providers: add MiniMax M3 model support. (#88860)
- Tools/media: allow validated host-local text document media sends while keeping unsafe plain-text media sends blocked. (#79658) Thanks @simplyclever914.
- Doctor: add disk space health checks and stabilize post-upgrade JSON probes.
- Channels: store inbound queues in SQLite and migrate iMessage monitor state to SQLite-backed tracking. (#88797)
- Skills: add the core skills index and centralize skills runtime loading, status, filtering, and prompt formatting.
### Fixes
- Agents/media: keep async image, music, and video generation starts from ending the Codex turn, so mixed requests can continue with summaries or other work while media renders in the background.
- Release/CI/E2E: fail early when Crabbox sparse-sync full checkouts do not have enough local disk, with guidance for moving the sync root.
- Build: render independent CLI startup metadata help snapshots concurrently to cut cold build-all metadata time.
- Plugins: stop timed-out package-boundary prep steps by process group so descendant TypeScript/helper processes do not survive local check cleanup.
- Control UI: serve static assets asynchronously after safe-open checks so large UI files do not block Gateway request handling.
- Scripts/UI: forward direct wrapper SIGHUP shutdown to child processes so terminal hangups do not leave wrapped dev commands running.
- Gateway: return the post-expiration pending-work revision from node drains so reconnecting nodes do not observe stale queue revisions after expired items are pruned.
- Release/CI/E2E: keep temporary full-sync checkouts alive while slow Crabbox leases boot, so sparse worktree runs do not lose their sync source before file-list generation.
- Release/CI/E2E: normalize inherited Linux `C.UTF-8` locale settings before raw AWS macOS Crabbox bootstrap commands, avoiding macOS locale warnings during package-manager hydration.
- Release/CI/E2E: keep gateway watch regression checks from copying large static plugin assets inside the measured idle window.
- Update: keep core updates nonblocking when missing external plugin repair downloads or soft plugin repair warnings would otherwise stall, pin post-core plugin compatibility to the downgraded core version, and still block installed active plugin payload smoke failures. (#84431, #87914, #87952) Thanks @TurboTheTurtle, @Niriakot, and @MukundaKatta.
- Agents/providers: keep streaming tool-call argument parsing record-shaped when providers emit valid non-object JSON such as `null` or arrays.
- Release/CI/E2E: reset incremental log readers when watched log files rotate without shrinking, so same-size replacements do not hide new readiness or RPC lines.
- Talk: preserve explicit `null` payloads on controller-created turn and output-audio lifecycle events.
- Agents/TUI: keep local custom provider runs from loading plugin runtime and auth alias metadata when plugins are disabled.
- Agents/TUI: restore in-flight TUI run switch-back behavior, keep no-policy native hook fallback available, guard vanished workspaces, and keep lightweight isolated subagents lightweight.
- Agents/media: keep async image, music, and video generation starts from ending the Codex turn, avoid duplicate generated-media fallbacks, and let mixed requests continue with summaries or other work while media renders in the background. (#89220) Thanks @omarshahine.
- Agents/Codex: keep public OpenAI API-key profiles from being treated as native Codex app-server auth while preserving persisted Codex OAuth sessions.
- Agents/Codex: stream Codex app-server final-answer partials to live reply previews, preserve ACP metadata in SQLite, prefer real tool results over synthetic repair output, prevent aborted app-server turn handles from lingering, migrate legacy OpenAI Codex `lastGood` auth state, and preserve workspace/session metadata through ACP runtime refactors. (#88405, #88724, #88730) Thanks @vincentkoc.
- Control UI: keep collapsed tool cards labeled with the tool name and action instead of generic output text. Thanks @shakkernerd.
- Agents/Codex: surface Skill Workshop guidance in Codex app-server prompts when `skill_workshop` is available. Thanks @shakkernerd.
- Agents/auth: write auth profiles atomically, add force re-login recovery, preserve workspaces during state-only uninstall, and compact before oversized turns so recovery paths avoid partial state.
- Skill Workshop: restore and localize the Control UI board/today view switcher so review workflows keep their intended layout toggle across locales. Thanks @shakkernerd.
- Agents/auth: write auth profiles atomically, dispatch auth failures by type, add force re-login and exhausted-failover recovery, clear legacy auto fallback pins, preserve workspaces during state-only uninstall, and compact before oversized turns so recovery paths avoid partial state. (#85798, #87484, #89181) Thanks @RomneyDa and @neeravmakwana.
- Skills: skip disabled skill env overrides from stale persisted snapshots so disabled skill `apiKey` SecretRefs cannot abort embedded or channel turns. (#79072, #79173) Thanks @zeus1959.
- Skill Workshop: render the Control UI tab from filtered navigation state and keep filtered fallback routing stable.
- CLI: avoid live catalog validation during `openclaw agents add`, so adding a secondary agent no longer depends on provider catalog availability. (#76284, #88314) Thanks @zhangguiping-xydt.
- CLI: keep `plugins list --json` on the snapshot-only path so plugin sweeps avoid loading the full runtime status graph.
- CLI: harden CLI and plugin edge cases, and keep `plugins list --json` on the snapshot-only path so plugin sweeps avoid loading the full runtime status graph. (#88896)
- CLI/desktop: bridge WSL clipboard operations through the shell, recognize manual-update launchd jobs, and keep machine-readable startup output parseable during progress setup. (#88764, #88689) Thanks @alexzhu0.
- Plugins: make PixVerse external-plugin ClawHub metadata explicit and keep it out of bundled dist builds.
- Plugins: clarify plugin loader failure guidance so missing or incompatible plugin packages point operators at the right repair path.
- Cron: keep SQLite cron migrations compatible with legacy run-log tables, archived job stores, diagnostic cron names, and legacy one-shot delete-after-run behavior. (#88285)
- Plugins: clarify plugin loader failure guidance and treat soft plugin repair warnings as nonfatal so missing or incompatible plugin packages point operators at the right repair path without blocking unrelated work. (#84431) Thanks @TurboTheTurtle.
- Plugins: preserve npm plugin roots after blocked installs, skip plugin-local `openclaw` peer symlinks during rollback snapshots, relink those peers after restore, isolate cached tool runtime siblings, isolate provider catalog projections and web-provider factory failures, and keep private LLM-core declarations bundled so one bad plugin does not poison sibling runtime paths. (#77237, #88767, #88807, #89336) Thanks @vincentkoc and @RomneyDa.
- Cron: keep SQLite cron migrations compatible with legacy run-log tables, archived job stores, diagnostic cron names, single-job run-history names, startup cron retries, and legacy one-shot delete-after-run behavior. (#88285, #88294, #89075) Thanks @kip-claw.
- Cron: keep update delivery validation scoped, harden restart state, and retire MCP runtimes on isolated cron cleanup.
- Auto-reply: guard dispatcher failure-count probes so missing optional counters do not break SDK-typed recovery paths. (#89318) Thanks @Alix-007 and @takhoffman.
- Memory: serialize QMD update/embed writes per store, reduce Linux watcher fan-out, avoid noisy gateway watcher warnings, retry transient FileProvider-backed reads, preserve phase signals on read errors, harden envelope metadata sanitization, reattach Linux native watchers when directories are recreated, and rewrite generated transcript paths on rollover so memory/search state survives concurrent gateway and CLI activity. (#66339, #85931, #89185, #89188, #89246, #85351) Thanks @openperf, @amittell, @RomneyDa, and @NianJiuZst.
- Memory: keep vector-disabled FTS indexes from resolving embedding providers during sync and search.
- Providers: bound generated media downloads from OpenAI, Runway, xAI, MiniMax, BytePlus, DashScope-compatible, FAL, OpenRouter, Google, Vydra, and Comfy providers.
- Providers: resolve Google defaults to `google-generative-ai`, register Vertex static catalog rows and `gemini-3.1-flash-lite`, align Foundry reasoning metadata, skip DeepSeek V4 thinking params on Foundry fallback, use MiniMax account OAuth endpoints, preserve Copilot Claude 1M capabilities, suppress disabled Ollama reasoning output, forward Gemini stop sequences, switch direct Gemini reasoning to native mode, strip provider self-prefixes and Kimi-incompatible Anthropic cache markers, keep OpenAI stop-finished tool calls, and avoid replay ids when the Responses store is disabled. (#88480, #88512, #88781, #89343, #89379, #89400, #76612) Thanks @coder999999999, @BryanTegomoh, @vliuyt, @charles-openclaw, @zz327455573, @849261680, and @XuZehan-iCenter.
- Providers: cap GitHub Copilot OAuth request timeouts before creating abort signals.
- Cron: retry recurring jobs after transient model rate limits before waiting for the next scheduled slot.
- Agents/Codex: keep live session locks during cleanup, recover interrupted CLI tool transcripts, preserve Codex auth and compaction session identity, clear orphan tool state, cap app-server idle timers, and keep media completion delivery retryable. (#88129, #88136, #88141, #88162, #88182)
- Chat/UI: show Gateway chat failures as visible assistant messages in the Control UI instead of only setting an invisible error state.
- Channels: cap Telegram, Discord, WhatsApp, Signal, Feishu, Google Chat, Microsoft Teams, QQBot, Nostr, Zalo, Zalouser, and Nextcloud-style request/retry timers; preserve SMS approval reply routes; and retry WhatsApp QR login 408 timeouts. (#88183)
- Security/config parsing: reject unsafe OAuth/token lifetimes, retry-after delays, inbound timestamps, response body sizes, command timeout config, sandbox observer token TTLs, and gateway WebSocket calls after close.
- Channels: cap Telegram, Discord, WhatsApp, Signal, Feishu, Google Chat, Microsoft Teams, QQBot, Nostr, Zalo, Zalouser, and Nextcloud-style request/retry timers; preserve SMS approval reply routes; keep iMessage typing active during tool work; allow RFC2544 benchmark ranges for QQBot token fetches; and retry WhatsApp QR login 408 timeouts. (#88183, #88948, #88984, #89015) Thanks @omarshahine, @Jensenwgd, and @sliverp.
- Security/config parsing: reject unsafe OAuth/token lifetimes, retry-after delays, inbound timestamps, response body sizes, command timeout config, sandbox observer token TTLs, corrupt shell snapshots, untrusted workspace setup-only channel loads, remote media reference overreads, trajectory export leaks, hooks-token auth reuse, and gateway WebSocket calls after close. (#86953, #87376, #88974, #89354, #89701) Thanks @hxy91819, @coygeek, @pgondhi987, and @RomneyDa.
- Providers/media: cap local service, model, usage, queue, generated media, TTS, music, workflow polling, and provider OAuth request timers across hosted and local providers.
- Release/CI/E2E: bound release candidate reads, beta smoke REST calls, changelog restore, kitchen-sink and bundled plugin readiness probes, secret-provider probes, Vitest routing, and mainline test flakes. (#88127, #88137, #88155, #88160)
- Release/CI/E2E: bound release candidate reads, beta smoke REST calls, plugin npm verification commands, changelog restore, cross-OS process groups, kitchen-sink and bundled plugin readiness probes, secret-provider probes, Telegram credential timeouts, Control UI i18n and CLI startup metadata generation, Vitest routing, dependency guard admin approvals, child workflow failure detection, quiet Node test shard stalls, dist cache restores, Docker base-image/package cleanup, and mainline test flakes. (#84988, #88127, #88137, #88155, #88160, #88966, #89169) Thanks @LibraHo and @RomneyDa.
- Release/CI/E2E: keep Kitchen Sink live plugin MCP probes resolving source-checkout workspace packages and align the live gauntlet with current Kitchen Sink diagnostics.
- Backup: accept root-relative hardlink targets during backup verification. (#89328) Thanks @abnershang.
- Release/CI/E2E: run the secret-provider integration proof through the repo pnpm runner so native macOS and Windows validation use the hydrated package-manager shim.
- Release/CI/E2E: run the Telegram desktop proof gateway through the repo pnpm runner so native macOS proof uses the hydrated package-manager shim.
- Docs/CI: run Mintlify anchor checks through the repo pnpm runner so docs link validation works when pnpm is only available through the hydrated package-manager shim.
- Agents: keep configured fallback model metadata typed so provider params, context-token caps, and media input limits do not break changed-gate typechecks.
- Agents: accept hidden `sessions_send` body aliases before validation while keeping the model-facing `message` schema canonical. (#88229) Thanks @zhangguiping-xydt.
- Chat/UI: preserve startup chat sends during history loading, unblock the initial Control UI chat send, stream chat deltas incrementally, skip markdown parsing while streaming, keep drafts local while typing, guard composer rerenders, cache chat transcript renders, record pending-send paint timing, show the Communication Notifications tab, honor Chromium executable overrides, and detect system Chromium for E2E. (#74715, #88952, #88960, #88998) Thanks @VladyslavLevchuk and @vincentkoc.
- Channels: stop schema-padded poll modifiers from turning normal `send` actions into invalid poll sends. (#89601) Thanks @codezz and @takhoffman.
- Channels: preserve long Feishu streaming replies, recover failed progress draft starts, send visible fallbacks when accepted Feishu turns produce no final reply, preserve external `sessions_send` routes, persist Discord thread bindings in SQLite, tolerate iMessage self-chat timestamp skew, preserve colon-prefixed slash commands in mention parsing, decode Nostr `npub` allowlists correctly, and suppress raw provider errors during channel delivery. (#87896, #88749, #88803, #88866) Thanks @MonkeyLeeT.
- Config/status/doctor: skip unresolved shell references in state-dir dotenv files, resolve gateway auth secrets during deep status audits, surface disabled Codex plugin routes in doctor lint, respect explicit PI runtime policy, report runtime tool-schema and gateway health credential errors, clear recovered embedded-run activity, migrate voice-call call logs through doctor, and keep post-upgrade JSON stable. (#88731, #88761, #88820, #88288, #89731) Thanks @brokemac79, @openperf, and @RomneyDa.
- Gateway/session state: list commands from the Gateway plugin registry, harden MCP loopback tool schemas, hide phantom agent-store rows from `sessions.list`, make task persistence failures explicit, support Tailscale Serve service names, guard Browser/Chrome pending attach aborts, and carry session UUIDs on interactive dispatch events. (#88305) Thanks @rohitjavvadi.
- Gateway/plugins: narrow plugin lookup memoization to the stable plugin/runtime inputs, avoiding repeated lookup work without mixing disabled or filtered plugin state.
- OpenAI/TTS: handle speed directives for OpenAI TTS voices. (#74089)
- CI/Crabbox: keep default runner capacity on the Azure credit-backed on-demand D4 lane with the Azure SSH port and a Git-independent full check job, so broad validation avoids low-priority spot quota stalls, hydrate port mismatches, non-Git hydrated workspaces, and stale AWS region hints.
- CI/Crabbox: route Crabbox wrapper and Testbox workflow edits to their regression tests so changed-test gates do not silently run zero specs.
- CI/workflows: route workflow sanity helper edits to their guard tests and cover composite-action input interpolation checks.
@@ -630,6 +760,7 @@ Docs: https://docs.openclaw.ai
- Gateway/sessions: allow shared-secret bearer callers to read and stream session history without an explicit scope header. (#81815) Thanks @medns.
- Agents/embedded runner: classify HTML auth provider responses as `auth_html` and return a re-authentication hint instead of the CDN-blocked copy that `upstream_html` returns. Cloudflare Access login pages, nginx basic-auth challenges, and gateway login walls all produce HTML auth bodies that were previously misdiagnosed as transient CDN blocks. (#79900) Thanks @martingarramon.
- TUI/streaming watchdog: dismiss the `This response is taking longer than expected` notice as soon as a chat event for the same run arrives, so the message no longer sits next to the recovered response when the run was only briefly silent. Refs #67052, #69081 (closed), prior attempt #69026. Thanks @jpruit20 and @romneyda.
- Agents/auth profiles: replace the bare `No available auth profile for <provider> (all in cooldown or unavailable)` TUI error with plain-language copy that explains what happened in user terms (sign-in expired, provider asking us to slow down, billing issue on the account, etc.) and suggests the matching `openclaw models auth login --provider <provider>` recovery command for sign-in and billing causes, while falling back to the underlying provider error for cases without a clear recovery path. Thanks @romneyda.
- Agents/Pi: tolerate OpenClaw-owned transcript writes while embedded prompts are released for model I/O, keeping long-running Feishu, Slack, Telegram, and cron turns from failing with false session-takeover errors. Fixes #84059. (#84250) Thanks @tianxiaochannel-oss88.
## 2026.5.20

View File

@@ -9,18 +9,18 @@
# Build stages use full bookworm; the runtime image is always bookworm-slim.
ARG OPENCLAW_EXTENSIONS=""
ARG OPENCLAW_BUNDLED_PLUGIN_DIR=extensions
ARG OPENCLAW_NODE_BOOKWORM_IMAGE="node:24-bookworm@sha256:8530f76a96d88820d288761f022e318970dda93d01536919fbc16076b7983e63"
ARG OPENCLAW_NODE_BOOKWORM_SLIM_IMAGE="node:24-bookworm-slim@sha256:242549cd46785b480c832479a730f4f2a20865d61ea2e404fdb2a5c3d3b73ecf"
ARG OPENCLAW_NODE_BOOKWORM_IMAGE="docker.io/library/node:24-bookworm@sha256:8530f76a96d88820d288761f022e318970dda93d01536919fbc16076b7983e63"
ARG OPENCLAW_NODE_BOOKWORM_SLIM_IMAGE="docker.io/library/node:24-bookworm-slim@sha256:242549cd46785b480c832479a730f4f2a20865d61ea2e404fdb2a5c3d3b73ecf"
ARG OPENCLAW_NODE_BOOKWORM_SLIM_DIGEST="sha256:242549cd46785b480c832479a730f4f2a20865d61ea2e404fdb2a5c3d3b73ecf"
# Keep in sync with .github/actions/setup-node-env/action.yml bun-version.
# To update: docker buildx imagetools inspect oven/bun:<version> and use the manifest-list digest.
ARG OPENCLAW_BUN_IMAGE="oven/bun:1.3.13@sha256:87416c977a612a204eb54ab9f3927023c2a3c971f4f345a01da08ea6262ae30e"
# To update: docker buildx imagetools inspect docker.io/oven/bun:<version> and use the manifest-list digest.
ARG OPENCLAW_BUN_IMAGE="docker.io/oven/bun:1.3.13@sha256:87416c977a612a204eb54ab9f3927023c2a3c971f4f345a01da08ea6262ae30e"
# Base images are pinned to SHA256 digests for reproducible builds.
# Dependabot refreshes these blessed digests; release builds consume the
# reviewed base snapshot instead of mutating distro state on every build.
# To update, run: docker buildx imagetools inspect node:24-bookworm and
# node:24-bookworm-slim (or podman) and replace the digests below with the
# To update, run: docker buildx imagetools inspect docker.io/library/node:24-bookworm and
# docker.io/library/node:24-bookworm-slim (or podman) and replace the digests below with the
# current multi-arch manifest list entries.
FROM ${OPENCLAW_NODE_BOOKWORM_IMAGE} AS workspace-deps

View File

@@ -30,7 +30,8 @@ Supported channels include: WhatsApp, Telegram, Slack, Discord, Google Chat, Sig
New install? Start here: [Getting started](https://docs.openclaw.ai/start/getting-started)
Preferred setup: run `openclaw onboard` in your terminal.
OpenClaw Onboard guides you step by step through setting up the gateway, workspace, channels, and skills. It is the recommended CLI setup path and works on **macOS, Linux, and Windows (via WSL2; strongly recommended)**.
OpenClaw Onboard guides you step by step through setting up the gateway, workspace, channels, and skills. It is the recommended CLI setup path and works on **macOS, Linux, and Windows**.
Windows desktop users can start with the native [Windows Hub](https://docs.openclaw.ai/platforms/windows) companion app for setup, tray status, chat, node mode, and local MCP mode.
Works with npm, pnpm, or bun.
## Sponsors
@@ -164,7 +165,7 @@ Run `openclaw doctor` to surface risky/misconfigured DM policies.
- **[Voice Wake](https://docs.openclaw.ai/nodes/voicewake) + [Talk Mode](https://docs.openclaw.ai/nodes/talk)** — wake words on macOS/iOS and continuous voice on Android (ElevenLabs + system TTS fallback).
- **[Live Canvas](https://docs.openclaw.ai/platforms/mac/canvas)** — agent-driven visual workspace with [A2UI](https://docs.openclaw.ai/platforms/mac/canvas#canvas-a2ui).
- **[First-class tools](https://docs.openclaw.ai/tools)** — browser, canvas, nodes, cron, sessions, and Discord/Slack actions.
- **[Companion apps](https://docs.openclaw.ai/platforms/macos)** — macOS menu bar app + iOS/Android [nodes](https://docs.openclaw.ai/nodes).
- **[Companion apps](https://docs.openclaw.ai/platforms)** — Windows Hub, macOS menu bar app, and iOS/Android [nodes](https://docs.openclaw.ai/nodes).
- **[Onboarding](https://docs.openclaw.ai/start/wizard) + [skills](https://docs.openclaw.ai/tools/skills)** — onboarding-driven setup with bundled/managed/workspace skills.
## Security model (important)
@@ -185,7 +186,7 @@ Run `openclaw doctor` to surface risky/misconfigured DM policies.
- New here: [Getting started](https://docs.openclaw.ai/start/getting-started), [Onboarding](https://docs.openclaw.ai/start/wizard), [Updating](https://docs.openclaw.ai/install/updating)
- Channel setup: [Channels index](https://docs.openclaw.ai/channels), [WhatsApp](https://docs.openclaw.ai/channels/whatsapp), [Telegram](https://docs.openclaw.ai/channels/telegram), [Discord](https://docs.openclaw.ai/channels/discord), [Slack](https://docs.openclaw.ai/channels/slack)
- Apps + nodes: [macOS](https://docs.openclaw.ai/platforms/macos), [iOS](https://docs.openclaw.ai/platforms/ios), [Android](https://docs.openclaw.ai/platforms/android), [Nodes](https://docs.openclaw.ai/nodes)
- Apps + nodes: [Windows Hub](https://docs.openclaw.ai/platforms/windows), [macOS](https://docs.openclaw.ai/platforms/macos), [iOS](https://docs.openclaw.ai/platforms/ios), [Android](https://docs.openclaw.ai/platforms/android), [Nodes](https://docs.openclaw.ai/nodes)
- Config + security: [Configuration](https://docs.openclaw.ai/gateway/configuration), [Security](https://docs.openclaw.ai/gateway/security), [Exposure runbook](https://docs.openclaw.ai/gateway/security/exposure-runbook), [Sandboxing](https://docs.openclaw.ai/gateway/sandboxing)
- Remote + web: [Gateway](https://docs.openclaw.ai/gateway), [Remote access](https://docs.openclaw.ai/gateway/remote), [Tailscale](https://docs.openclaw.ai/gateway/tailscale), [Web surfaces](https://docs.openclaw.ai/web)
- Tools + automation: [Tools](https://docs.openclaw.ai/tools), [Skills](https://docs.openclaw.ai/tools/skills), [Cron jobs](https://docs.openclaw.ai/automation/cron-jobs), [Webhooks](https://docs.openclaw.ai/automation/webhook), [Gmail Pub/Sub](https://docs.openclaw.ai/automation/gmail-pubsub)

View File

@@ -2,6 +2,133 @@
<rss xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle" version="2.0">
<channel>
<title>OpenClaw</title>
<item>
<title>2026.6.1</title>
<pubDate>Wed, 03 Jun 2026 21:26:22 +0000</pubDate>
<link>https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml</link>
<sparkle:version>2026060190</sparkle:version>
<sparkle:shortVersionString>2026.6.1</sparkle:shortVersionString>
<sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>
<description><![CDATA[<h2>OpenClaw 2026.6.1</h2>
<h3>Highlights</h3>
<ul>
<li>Agents and CLI-backed runtimes recover more cleanly from interrupted tool calls, stale session bindings, compaction handoffs, and media delivery retries. (#88129, #88136, #88141, #88162, #88182)</li>
<li>Channels and mobile delivery are steadier across Telegram, WhatsApp, iMessage, Slack, Discord, Microsoft Teams, Google Chat, Google Meet, and iOS realtime Talk. (#88096, #88105, #88183, #88231)</li>
<li>Provider and plugin requests now bound more timers, retries, OAuth/device-code lifetimes, media downloads, local service probes, and generated-content polling paths before they can hang a run.</li>
<li>Skills, session metadata, gateway runtime state, plugin metadata, memory watchers, and store writes do less repeated work on hot paths while keeping config, dispatch, and Linux file-watch behavior stable. (#89185, #89188, #85351) Thanks @RomneyDa and @NianJiuZst.</li>
<li>Skills and plugin loading now handle stale disabled snapshots and loader failures more clearly, so channel turns avoid disabled SecretRefs and operators get better recovery guidance. (#79072, #79173) Thanks @zeus1959.</li>
<li>Workboard, SecretRef plugin manifests, hosted iOS push relay, and external Copilot/Tokenjuice packaging add broader orchestration, integration, and plugin delivery surfaces. (#82326, #87469, #87796, #88107, #88117)</li>
<li>Skill Workshop now has a fuller Control UI flow with proposal lists, today actions, revision handoff, searchable file previews, review states, locale coverage, and reusable session routing.</li>
<li>Chat and Control UI startup paths keep sends alive through history loading, stream deltas incrementally, skip markdown work while streaming, keep drafts local while typing, clear the composer after sends, trace first-output latency, prioritize first connect, and expose calmer composer controls. (#88772, #88825, #88998, #89030, #89106) Thanks @vincentkoc and @sallyom.</li>
<li>Provider coverage and model metadata now include MiniMax M3, account OAuth endpoints, Google/Vertex catalog fixes, OpenRouter SQLite model caching, Copilot Claude 1M capabilities, Foundry reasoning alignment, and OpenAI response replay guards. (#88480, #88512, #88851, #88860)</li>
<li>iMessage monitor state, inbound queues, and plugin install ledgers moved toward SQLite-backed state so restarts and local monitors recover with less duplicate filesystem scanning. (#88794, #88797)</li>
<li>Release, CI, Docker, E2E, plugin install, and diagnostics lanes now cap more logs, response bodies, readiness probes, artifact checks, status polling, child workflow waits, docker package cleanup, quiet test stalls, and rollback snapshots so failures report bounded proof instead of stalling. (#88966) Thanks @RomneyDa.</li>
</ul>
<h3>Changes</h3>
<ul>
<li>Docs: add a dedicated Skill Workshop guide covering governed skill creation, reviewable proposals, CLI, Gateway, agent tool behavior, approval policy, support files, and recovery, and refresh the ClawHub showcase cards. (#88734) Thanks @shakkernerd and @vyctorbrzezowski.</li>
<li>Skills: let the <code>skill_workshop</code> agent tool apply, reject, and quarantine explicit proposals through the guarded review flow. Thanks @shakkernerd.</li>
<li>Skills: let proposals carry approved support files under standard skill folders, with scanner, hash, and rollback safeguards. Thanks @shakkernerd.</li>
<li>Skills: let pending proposals be revised in place with versioned, dated proposal frontmatter before approval. Thanks @shakkernerd.</li>
<li>Skills: add Skill Workshop with pending proposals, CLI/Gateway review actions, rollback metadata, and the <code>skill_workshop</code> agent tool. Thanks @shakkernerd.</li>
<li>Skill Workshop: add the Control UI navigation, styled dashboard, proposal today view, revision dialog, file preview modal, searchable preview files, reusable session handoff, and localized strings.</li>
<li>Plugins: externalize Tokenjuice as the official <code>@openclaw/tokenjuice</code> plugin with npm and ClawHub publish metadata.</li>
<li>Plugins: externalize the GitHub Copilot agent runtime as the official <code>@openclaw/copilot</code> plugin with npm and ClawHub publish metadata.</li>
<li>iOS: add hosted push relay defaults, realtime Talk playback, and a guarded WebSocket ping path for more reliable mobile sessions. (#88096, #88105, #88231)</li>
<li>iOS: support native iPad display layouts.</li>
<li>Workboard: add orchestration primitives and agent coordination tools for multi-agent planning and run tracking. (#87469)</li>
<li>Workboard: wire task-backed board runs and show task comments in the edit modal.</li>
<li>Code mode: add internal namespaces for scoped agent/global sessions and exact namespace tool dispatch. (#88043)</li>
<li>Code mode: add MCP API files and docs for code-mode integrations.</li>
<li>Control UI: add a Dreaming-tab agent selector and propagate the selected agent through Dreaming status, diary, and diary actions. (#78748) Thanks @stevenepalmer.</li>
<li>Control UI: add calmer chat composer controls, local draft typing state, and first-output latency instrumentation for active chat entry. (#88772, #88998) Thanks @vincentkoc.</li>
<li>Plugins: add a SecretRef provider integration manifest contract and extract shared LLM core packages for provider/plugin reuse. (#82326, #88117)</li>
<li>Plugins: persist the plugin install index in SQLite so installed package lookup survives reloads with less filesystem scanning. (#88794)</li>
<li>Providers: add MiniMax M3 model support. (#88860)</li>
<li>Doctor: add disk space health checks and stabilize post-upgrade JSON probes.</li>
<li>Channels: store inbound queues in SQLite and migrate iMessage monitor state to SQLite-backed tracking. (#88797)</li>
<li>Skills: add the core skills index and centralize skills runtime loading, status, filtering, and prompt formatting.</li>
</ul>
<h3>Fixes</h3>
<ul>
<li>Release/CI/E2E: fail early when Crabbox sparse-sync full checkouts do not have enough local disk, with guidance for moving the sync root.</li>
<li>Build: render independent CLI startup metadata help snapshots concurrently to cut cold build-all metadata time.</li>
<li>Plugins: stop timed-out package-boundary prep steps by process group so descendant TypeScript/helper processes do not survive local check cleanup.</li>
<li>Control UI: serve static assets asynchronously after safe-open checks so large UI files do not block Gateway request handling.</li>
<li>Scripts/UI: forward direct wrapper SIGHUP shutdown to child processes so terminal hangups do not leave wrapped dev commands running.</li>
<li>Gateway: return the post-expiration pending-work revision from node drains so reconnecting nodes do not observe stale queue revisions after expired items are pruned.</li>
<li>Release/CI/E2E: keep temporary full-sync checkouts alive while slow Crabbox leases boot, so sparse worktree runs do not lose their sync source before file-list generation.</li>
<li>Release/CI/E2E: normalize inherited Linux <code>C.UTF-8</code> locale settings before raw AWS macOS Crabbox bootstrap commands, avoiding macOS locale warnings during package-manager hydration.</li>
<li>Release/CI/E2E: keep gateway watch regression checks from copying large static plugin assets inside the measured idle window.</li>
<li>Update: keep core updates nonblocking when a missing external plugin repair download stalls, while still blocking installed active plugin payload smoke failures.</li>
<li>Agents/providers: keep streaming tool-call argument parsing record-shaped when providers emit valid non-object JSON such as <code>null</code> or arrays.</li>
<li>Release/CI/E2E: reset incremental log readers when watched log files rotate without shrinking, so same-size replacements do not hide new readiness or RPC lines.</li>
<li>Talk: preserve explicit <code>null</code> payloads on controller-created turn and output-audio lifecycle events.</li>
<li>Agents/TUI: keep local custom provider runs from loading plugin runtime and auth alias metadata when plugins are disabled.</li>
<li>Agents/TUI: restore in-flight TUI run switch-back behavior, keep no-policy native hook fallback available, guard vanished workspaces, and keep lightweight isolated subagents lightweight.</li>
<li>Agents/media: keep async image, music, and video generation starts from ending the Codex turn, so mixed requests can continue with summaries or other work while media renders in the background.</li>
<li>Agents/Codex: keep public OpenAI API-key profiles from being treated as native Codex app-server auth while preserving persisted Codex OAuth sessions.</li>
<li>Agents/Codex: stream Codex app-server final-answer partials to live reply previews, preserve ACP metadata in SQLite, prefer real tool results over synthetic repair output, prevent aborted app-server turn handles from lingering, migrate legacy OpenAI Codex <code>lastGood</code> auth state, and preserve workspace/session metadata through ACP runtime refactors. (#88405, #88724, #88730) Thanks @vincentkoc.</li>
<li>Control UI: keep collapsed tool cards labeled with the tool name and action instead of generic output text. Thanks @shakkernerd.</li>
<li>Agents/Codex: surface Skill Workshop guidance in Codex app-server prompts when <code>skill_workshop</code> is available. Thanks @shakkernerd.</li>
<li>Skill Workshop: restore and localize the Control UI board/today view switcher so review workflows keep their intended layout toggle across locales. Thanks @shakkernerd.</li>
<li>Agents/auth: write auth profiles atomically, dispatch auth failures by type, add force re-login recovery, preserve workspaces during state-only uninstall, and compact before oversized turns so recovery paths avoid partial state. (#89181) Thanks @RomneyDa.</li>
<li>Skills: skip disabled skill env overrides from stale persisted snapshots so disabled skill <code>apiKey</code> SecretRefs cannot abort embedded or channel turns. (#79072, #79173) Thanks @zeus1959.</li>
<li>Skill Workshop: render the Control UI tab from filtered navigation state and keep filtered fallback routing stable.</li>
<li>CLI: avoid live catalog validation during <code>openclaw agents add</code>, so adding a secondary agent no longer depends on provider catalog availability. (#76284, #88314) Thanks @zhangguiping-xydt.</li>
<li>CLI: keep <code>plugins list --json</code> on the snapshot-only path so plugin sweeps avoid loading the full runtime status graph.</li>
<li>CLI/desktop: bridge WSL clipboard operations through the shell, recognize manual-update launchd jobs, and keep machine-readable startup output parseable during progress setup. (#88764, #88689) Thanks @alexzhu0.</li>
<li>Plugins: make PixVerse external-plugin ClawHub metadata explicit and keep it out of bundled dist builds.</li>
<li>Plugins: clarify plugin loader failure guidance so missing or incompatible plugin packages point operators at the right repair path.</li>
<li>Plugins: preserve npm plugin roots after blocked installs, skip plugin-local <code>openclaw</code> peer symlinks during rollback snapshots, relink those peers after restore, isolate cached tool runtime siblings, and isolate web-provider factory failures so one bad plugin does not poison sibling runtime paths. (#77237, #88807)</li>
<li>Cron: keep SQLite cron migrations compatible with legacy run-log tables, archived job stores, diagnostic cron names, and legacy one-shot delete-after-run behavior. (#88285)</li>
<li>Cron: keep update delivery validation scoped, harden restart state, and retire MCP runtimes on isolated cron cleanup.</li>
<li>Memory: serialize QMD update/embed writes per store, reduce Linux watcher fan-out, retry transient FileProvider-backed reads, preserve phase signals on read errors, harden envelope metadata sanitization, reattach Linux native watchers when directories are recreated, and rewrite generated transcript paths on rollover so memory/search state survives concurrent gateway and CLI activity. (#66339, #85931, #89185, #89188, #85351) Thanks @openperf, @amittell, @RomneyDa, and @NianJiuZst.</li>
<li>Memory: keep vector-disabled FTS indexes from resolving embedding providers during sync and search.</li>
<li>Providers: bound generated media downloads from OpenAI, Runway, xAI, MiniMax, BytePlus, DashScope-compatible, FAL, OpenRouter, Google, Vydra, and Comfy providers.</li>
<li>Providers: resolve Google defaults to <code>google-generative-ai</code>, register Vertex static catalog rows, align Foundry reasoning metadata, skip DeepSeek V4 thinking params on Foundry fallback, use MiniMax account OAuth endpoints, preserve Copilot Claude 1M capabilities, suppress disabled Ollama reasoning output, forward Gemini stop sequences, strip Kimi-incompatible Anthropic cache markers, keep OpenAI stop-finished tool calls, and avoid replay ids when the Responses store is disabled. (#88480, #88512, #76612) Thanks @coder999999999, @BryanTegomoh, and @vliuyt.</li>
<li>Providers: cap GitHub Copilot OAuth request timeouts before creating abort signals.</li>
<li>Cron: retry recurring jobs after transient model rate limits before waiting for the next scheduled slot.</li>
<li>Agents/Codex: keep live session locks during cleanup, recover interrupted CLI tool transcripts, preserve Codex auth and compaction session identity, clear orphan tool state, cap app-server idle timers, and keep media completion delivery retryable. (#88129, #88136, #88141, #88162, #88182)</li>
<li>Chat/UI: show Gateway chat failures as visible assistant messages in the Control UI instead of only setting an invisible error state.</li>
<li>Channels: cap Telegram, Discord, WhatsApp, Signal, Feishu, Google Chat, Microsoft Teams, QQBot, Nostr, Zalo, Zalouser, and Nextcloud-style request/retry timers; preserve SMS approval reply routes; and retry WhatsApp QR login 408 timeouts. (#88183)</li>
<li>Security/config parsing: reject unsafe OAuth/token lifetimes, retry-after delays, inbound timestamps, response body sizes, command timeout config, sandbox observer token TTLs, and gateway WebSocket calls after close.</li>
<li>Providers/media: cap local service, model, usage, queue, generated media, TTS, music, workflow polling, and provider OAuth request timers across hosted and local providers.</li>
<li>Release/CI/E2E: bound release candidate reads, beta smoke REST calls, plugin npm verification commands, changelog restore, cross-OS process groups, kitchen-sink and bundled plugin readiness probes, secret-provider probes, Telegram credential timeouts, Control UI i18n and CLI startup metadata generation, Vitest routing, dependency guard admin approvals, child workflow failure detection, quiet Node test shard stalls, docker package cleanup, and mainline test flakes. (#88127, #88137, #88155, #88160, #88966) Thanks @RomneyDa.</li>
<li>Release/CI/E2E: keep Kitchen Sink live plugin MCP probes resolving source-checkout workspace packages and align the live gauntlet with current Kitchen Sink diagnostics.</li>
<li>Release/CI/E2E: run the secret-provider integration proof through the repo pnpm runner so native macOS and Windows validation use the hydrated package-manager shim.</li>
<li>Release/CI/E2E: run the Telegram desktop proof gateway through the repo pnpm runner so native macOS proof uses the hydrated package-manager shim.</li>
<li>Docs/CI: run Mintlify anchor checks through the repo pnpm runner so docs link validation works when pnpm is only available through the hydrated package-manager shim.</li>
<li>Agents: keep configured fallback model metadata typed so provider params, context-token caps, and media input limits do not break changed-gate typechecks.</li>
<li>Agents: accept hidden <code>sessions_send</code> body aliases before validation while keeping the model-facing <code>message</code> schema canonical. (#88229) Thanks @zhangguiping-xydt.</li>
<li>Chat/UI: preserve startup chat sends during history loading, unblock the initial Control UI chat send, stream chat deltas incrementally, skip markdown parsing while streaming, keep drafts local while typing, guard composer rerenders, honor Chromium executable overrides, and detect system Chromium for E2E. (#88998) Thanks @vincentkoc.</li>
<li>Channels: stop schema-padded poll modifiers from turning normal <code>send</code> actions into invalid poll sends. (#89601) Thanks @codezz.</li>
<li>Channels: preserve long Feishu streaming replies, send visible fallbacks when accepted Feishu turns produce no final reply, tolerate iMessage self-chat timestamp skew, preserve colon-prefixed slash commands in mention parsing, decode Nostr <code>npub</code> allowlists correctly, and suppress raw provider errors during channel delivery. (#87896)</li>
<li>Config/status/doctor: skip unresolved shell references in state-dir dotenv files, resolve gateway auth secrets during deep status audits, respect explicit PI runtime policy, report runtime tool-schema errors, and keep post-upgrade JSON stable. (#88288)</li>
<li>Gateway/session state: list commands from the Gateway plugin registry, harden MCP loopback tool schemas, hide phantom agent-store rows from <code>sessions.list</code>, make task persistence failures explicit, and carry session UUIDs on interactive dispatch events.</li>
<li>Gateway/plugins: narrow plugin lookup memoization to the stable plugin/runtime inputs, avoiding repeated lookup work without mixing disabled or filtered plugin state.</li>
<li>OpenAI/TTS: handle speed directives for OpenAI TTS voices. (#74089)</li>
<li>CI/Crabbox: keep default runner capacity on the Azure credit-backed on-demand D4 lane with the Azure SSH port and a Git-independent full check job, so broad validation avoids low-priority spot quota stalls, hydrate port mismatches, non-Git hydrated workspaces, and stale AWS region hints.</li>
<li>CI/Crabbox: route Crabbox wrapper and Testbox workflow edits to their regression tests so changed-test gates do not silently run zero specs.</li>
<li>CI/workflows: route workflow sanity helper edits to their guard tests and cover composite-action input interpolation checks.</li>
<li>CI/tooling: route CI scope, dependency, changelog, and docs helper edits to their owner tests instead of silently skipping changed-test coverage.</li>
<li>CI/tooling: route package, release, and install helper edits to their owner tests so changed-test gates cover publish and installer script changes.</li>
<li>CI/tooling: route shared script library edits through their owner tests so lock, process, safety, and scan helpers do not skip changed-test coverage.</li>
<li>CI/tooling: skip expensive import-graph scans once a changed diff already requires broad fallback, keeping local changed-test planning fast while still collecting explicit owner tests.</li>
<li>CI/tooling: route script edits through conventional owner tests when matching <code>test/scripts</code> or <code>src/scripts</code> coverage already exists.</li>
<li>CI/tooling: honor option terminators in the memory FD repro script so follow-on arguments are not reparsed.</li>
<li>Release/CI/E2E: assert plugin lifecycle runtime inspect output instead of only capturing it.</li>
<li>Release/CI/E2E: make gateway-network prove the advertised health RPC and retry early WebSocket closes without burning full open timeouts.</li>
<li>Release/CI/E2E: honor option terminators across release, Parallels smoke, plugin gauntlet, and extension-memory scripts.</li>
<li>Release/CI/E2E: fail plugin gateway gauntlet QA chunks when the requested suite summary is missing or invalid.</li>
<li>Performance: prebuild QA runtime probes with generated plugin assets but without CLI startup metadata.</li>
<li>Performance: skip declaration bundling for runtime-only CLI startup and gateway watch build profiles.</li>
<li>Performance: reuse prepared provider handles, strict tool schemas, gateway runtime metadata, session maintenance config, plugin metadata, bundled skill allowlists, package-local plugin artifacts, single-entry store writes, and validated/serialized session prompt blobs.</li>
</ul>
<p><a href="https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md">View full changelog</a></p>
]]></description>
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.6.1/OpenClaw-2026.6.1.zip" length="55062100" type="application/octet-stream" sparkle:edSignature="PVp8E2HBCvikB/0LCr36lFEyHPAzoFA2ScT6LW27FlzvP+m4r1AEuVN2UrtgWlpkGSsn4Eav0kPJe32u4ObNBw=="/>
</item>
<item>
<title>2026.5.28</title>
<pubDate>Sat, 30 May 2026 21:21:09 +0000</pubDate>
@@ -113,214 +240,5 @@
]]></description>
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.5.27/OpenClaw-2026.5.27.zip" length="54488811" type="application/octet-stream" sparkle:edSignature="c5w2T1UO6vpPs70hyYH93cIyWEOd5sl5z2NkhU53E+XQBSd+jAr+xd0qf3KzWbeX2mfXYMQmnx+VMls3L22EDg=="/>
</item>
<item>
<title>2026.5.26</title>
<pubDate>Wed, 27 May 2026 12:24:26 +0000</pubDate>
<link>https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml</link>
<sparkle:version>2026052690</sparkle:version>
<sparkle:shortVersionString>2026.5.26</sparkle:shortVersionString>
<sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>
<description><![CDATA[<h2>OpenClaw 2026.5.26</h2>
<h3>Highlights</h3>
<ul>
<li>Faster Gateway and replies: startup avoids repeated plugin, channel, session, usage-cost, warning, scheduled-service, and filesystem scans; visible replies separate user-facing sends from slower follow-up work; Gateway runtime/session caches churn less under load.</li>
<li>Transcripts are core: transcript-backed meeting summaries, source-provider chunks, cleaned user turns, media provenance, Codex mirrors, WebChat replies, and CLI/TUI replay now use one more reliable transcript path.</li>
<li>More channels are production-ready: Telegram keeps typing/progress context and forum topics, iMessage handles attachment roots, remote media staging, and duplicate local Messages sources, WhatsApp restores group/media behavior, Discord improves voice playback and model picking, and Signal/iMessage/WhatsApp get reaction approvals.</li>
<li>Better voice and Talk: realtime Talk runs can be inspected, steered, cancelled, or followed up from Web UI and Discord voice; wake-name handling is more tolerant without letting ambient speech trigger agents.</li>
<li>Safer content boundaries: Browser snapshot reads honor SSRF policy, system-event text cannot spoof nested prompt markers, fetched file text is wrapped as external content, ClickClack inbound sender allowlists run before agent dispatch, stale device tokens are rejected, and serialized tool-call text is scrubbed from replies.</li>
<li>Providers, Codex, and local models are steadier: named auth profiles, OpenAI sampling params, Codex app-server resume/timeout/usage-limit recovery, dynamic tool-schema guards, xAI usage-limit surfacing, Ollama top-p normalization, and local approval resolution reduce provider-specific dead ends.</li>
<li>More reliable install/update/release paths: Alpine installs, trusted runtime fallback roots, stable update channels, Docker/package timeouts, Windows Scheduled Tasks, Windows/macOS proof lanes, Testbox/Crabbox delegation, plugin publish checks, and macOS runner bootstraps all got hardened.</li>
<li>Better observability: Activity tab, gateway secret-prep traces, tool/model stream progress, explicit fast-mode status, systemd Gateway hygiene, OpenTelemetry LLM spans, release performance evidence, and richer telemetry signals make failures easier to inspect.</li>
</ul>
<h3>Changes</h3>
<ul>
<li>Transcripts: add core transcript capture and source-provider support for transcript-backed meeting summaries, including the renamed Transcripts docs, CLI surface, source-provider chunks, and cleaned user-turn persistence.</li>
<li>Auth: add named model login profiles and supported credential migration for Hermes, OpenCode, and Codex auth profiles, with explicit opt-out and non-interactive controls. (#85667) Thanks @fuller-stack-dev.</li>
<li>Diagnostics: trace gateway secret preparation, classify skill/tool usage, surface model stream progress, add OpenTelemetry LLM content spans, and expose alertable telemetry for blocked tools, failover, stale sessions, liveness, oversized payloads, and webhook ingress. (#83019, #80370, #86191)</li>
<li>Channels: add Signal reaction approvals, iMessage thumb approval reactions, and WhatsApp thumb approval reaction support so mobile approval flows work without textual <code>/approve</code> commands. (#85894, #85952, #85477)</li>
<li>Agents/API: forward OpenAI sampling params through the Gateway and expose estimated context-budget status for active agent runs. (#84094)</li>
<li>TUI/status: queue prompts submitted while an agent is busy and show explicit fast-mode state plus richer systemd Gateway hygiene in status output. (#86722, #87115, #86976)</li>
<li>Exec approvals: hide durable approval actions that are unavailable for the current prompt and keep approval runtime tokens local-only so stale prompts cannot offer misleading controls. (#86270, #86359)</li>
<li>Plugin SDK: add reaction approval helpers and keep diagnostic event root exports discoverable across function-name and alias-bound module graphs. (#86735, #87084)</li>
<li>Android/iOS: add the Android pair-new-gateway action and improve mobile Talk mode surfaces, including iOS realtime Talk mode and Android offline voice/gateway recovery. (#86798, #86355) Thanks @ngutman.</li>
<li>Performance: cache plugin metadata snapshots, package realpaths, stable gateway metadata, model cost indexes, channel resolution, usage-cost indexes, and session/auth hot-path facts so common Gateway and reply paths do less rediscovery. (#84649, #85843, #86517, #86678)</li>
<li>Voice: expose shared realtime turn-context tracking through the realtime voice SDK and reuse it for Discord speaker attribution and wake-name context recovery.</li>
<li>Voice: reuse shared realtime output activity tracking in Google Meet command and node audio bridges, including recent-output checks for local barge-in detection.</li>
<li>Voice: expose shared realtime output activity tracking through the realtime voice SDK and reuse it for Discord playback activity and barge-in decisions.</li>
<li>Voice: expose shared realtime consult question matching, speakable-result extraction, and alias-aware forced-consult coordination through the realtime voice SDK, then reuse it in Gateway Talk, Voice Call, and Discord voice paths.</li>
<li>Voice: share activation-name matching and consult-transcript screening through the realtime voice SDK so Discord, browser voice, and meeting surfaces can reuse one implementation.</li>
<li>Cron: default <code>cron.maxConcurrentRuns</code> to 8 so scheduled automations and their isolated agent turns can make progress in parallel without explicit configuration.</li>
<li>QA-Lab: add <code>qa coverage --match <query></code> so focused proof selection can discover matching scenarios from existing metadata before running live or remote lanes.</li>
<li>Discord/model picker: surface an alpha-bucket select (e.g. <code>AG (12) · HN (18) · OZ (5)</code>) when the provider list or a provider's model list exceeds 25 items, so configs with <code>provider/*</code> wildcards stay one click from the right page instead of paginating through prev/next; falls back to numeric chunks when every item shares the same first letter.</li>
<li>Control UI: add an ephemeral Activity tab for sanitized live tool activity summaries without persisting raw telemetry. Fixes #12831. Thanks @BunsDev.</li>
<li>Build: include <code>ui:build</code> in the <code>full</code> and <code>ciArtifacts</code> profiles of <code>scripts/build-all.mjs</code> so <code>pnpm build</code> always rebuilds <code>dist/control-ui</code> after <code>tsdown</code> cleans <code>dist</code>, removing the second-command requirement and the missing-asset failure mode for source/runtime installs and CI artifact uploads. (#85206)</li>
<li>iOS: improve Talk mode with direct realtime voice sessions, compact toolbar status, and responsive voice waveform feedback. (#86355) Thanks @ngutman.</li>
<li>Media: replace the Sharp image backend with Rastermill for metadata, resizing, EXIF orientation, and PNG alpha-preserving optimization so OpenClaw no longer installs Sharp or the WhatsApp Jimp fallback for image processing. (#86437)</li>
<li>Codex: update the bundled Codex CLI to 0.134.0 and keep native compaction disabled for budget-triggered app-server turns so OpenClaw owns the recovery boundary. (#86772)</li>
</ul>
<h3>Fixes</h3>
<ul>
<li>Memory/security: reject prompt-like text submitted through the explicit <code>memory_store</code> tool before embedding or storage, matching the existing auto-capture prompt-injection filter. (#87142)</li>
<li>Gateway/security: enable the default auth rate limiter for remote non-browser and HTTP gateway auth failures when <code>gateway.auth.rateLimit</code> is unset, while preserving the loopback exemption. (#87148)</li>
<li>Security/content boundaries: validate Browser snapshot tab URLs against SSRF policy before ChromeMCP or direct CDP reads, sanitize queued system-event text so untrusted plugin/channel labels cannot spoof nested prompt markers, wrap fetched file text and metadata as external content, apply ClickClack <code>allowFrom</code> sender allowlists before agent dispatch, reject RPCs from invalidated device-token clients during rotation, require staged sandbox media refs, and scrub serialized tool-call text from replies. (#78526, #87094, #87062, #83741, #70707, #86924) Thanks @zsxsoft, @ttzero25, and @mmaps.</li>
<li>Transcripts/user turns: persist CLI, WebChat, media, follow-up, hook, and Codex-mirror user turns to the admitted session target; keep cleaned transcript text, inline image routing, provenance metadata, replay hooks, and fallback paths idempotent when runtimes fail or restart.</li>
<li>TUI/status/onboarding/UI: queue busy TUI prompts instead of dropping them, preserve the configured default model during onboarding, show failed tool results as errors, show config-open failures in Control UI, keep status JSON plugin scans healthy, preserve xAI usage-limit errors locally, and expose explicit fast-mode/systemd state. (#86722, #87000, #85786, #87108, #87001, #86614, #87115, #86976)</li>
<li>Plugin commands/SDK: preserve plugin LLM command auth, bind native plugin command dispatch to the host agent's LLM auth, keep <code>onDiagnosticEvent</code> exports discoverable through <code>Function.name</code>, stabilize diagnostic event root aliases, correlate pathless read diagnostics, suppress transient runner failures in channel command paths, and repair local approval resolution. (#85936, #87084, #86977, #87069, #86771)</li>
<li>Codex/providers: keep WebChat delivery hints out of user prompts, avoid false queued-terminal idle timeouts, share the native hook relay registry, quarantine unsupported dynamic tool schemas, preserve Claude resumed-session system prompts, normalize greedy Ollama <code>top_p</code>, preserve per-agent thinking defaults for ingress runs, and avoid native compaction takeover on budget-triggered Codex turns. (#87096, #73950, #87049, #86689, #86772)</li>
<li>Gateway/perf/release: reuse startup-warning metadata and prepared auth stores, avoid cloning live-switch and lifecycle session caches on read paths, defer warning and scheduled-service fallback imports, trim Gateway session/startup/runtime CPU churn, skip duplicate turn session touches, stop chat timeout fallback cascades, drop stale subagent announce history, bound benchmark/watch/kitchen-sink teardown waits, bound macOS/package/onboarding/plugin smoke commands, bound install finalization probes, resolve Parallels npm-update commands from guest <code>PATH</code>, and bootstrap raw AWS macOS Node/pnpm commands through <code>/usr/bin/env</code>. (#86997)</li>
<li>Reply/perf: reduce visible reply delivery latency by preserving Telegram typing/progress context, lazy-loading slash-command startup metadata, avoiding hot-path model hydration, flag-gating Codex profiler timing, deferring context compaction maintenance, and tracking delivery timing. (#86989, #86990, #86991, #86992, #86993, #86994) Thanks @keshavbotagent.</li>
<li>Reply/source delivery: keep TUI, Control UI, media, TTS, transcript, and Codex source-reply finals live without duplicate terminal events or stale replay artifacts.</li>
<li>Agents/replay: repair legacy tool results before replay, preserve <code>sessions_spawn</code> transcript payloads, restore current guard checks, stage sandboxed workspace media, and keep duplicate transcripts tool display metadata from reappearing. (#82203, #86934, #87025) Thanks @martingarramon, @vincentkoc, and @joshavant.</li>
<li>Agents/sessions: handle active-fallback failures in <code>sessions_send</code> so fallback routing reports the real failure and does not leave callers with an ambiguous dropped send. (#86638)</li>
<li>Agents/hooks/subagents: enforce default hook agent allowlists, recover failed subagent lifecycle completions, and keep node task lifecycle cleanup from closing the Gateway listener. (#86101)</li>
<li>Codex: project newer OpenClaw chat history into resumed app-server threads and keep Codex turn timeouts inside the Codex runtime boundary so timeouts do not poison shared app-server clients or fall through to unrelated provider fallback. (#86677, #86476) Thanks @TurboTheTurtle and @pashpashpash.</li>
<li>Config/doctor/update: narrow profiled tool-section doctor repair, keep runtime-injected legacy web-search provider config out of user-authored config validation, and keep prerelease tags excluded from stable updater resolution. (#87030, #86818, #86559) Thanks @joshavant, @luoyanglang, and @stevenepalmer.</li>
<li>CLI/Windows: add a Windows-only stack-size respawn for stack-heavy startup paths, default CLI logs to local timestamps, and validate timeout/banner TTY state more strictly. (#87031, #85387) Thanks @giodl73-repo and @vincentkoc.</li>
<li>Locking/security: require owner identity proof before stale plugin lock removal, memoize session lock owner arguments, and avoid writing default exec approval stores unless policy state actually changed. (#86814, #86964) Thanks @Alix-007 and @vincentkoc.</li>
<li>Install/release: bound Docker package build, inventory, pack, and tarball preparation with process-group timeouts; pin shrinkwrap patch drift to the pnpm lock; harden macOS restart and dSYM packaging; and run release Docker/live timeout wrappers in the foreground so child processes cannot wedge gates.</li>
<li>Telegram/network: treat <code>ENETDOWN</code> as a transient pre-connect network failure so Telegram sends, gateway unhandled-rejection handling, and cron network retries follow the same recovery path as sibling network outages. (#86762) Thanks @TurboTheTurtle.</li>
<li>Telegram: preserve inbound text entities, overlapping DM replies, account topic cache sidecars, outbound reply context, targeted bot-command mentions, durable group retry targets, forum topic names, and native progress callbacks. (#83873, #85361, #85555, #85656, #85709, #86299, #86553) Thanks @SebTardif, @luoyanglang, and @neeravmakwana.</li>
<li>iMessage: read image attachments from local Messages attachment roots, dedupe duplicate local Messages-source accounts, seed direct DM history, fix image/group media attachment commands, advance catchup cursors after live handling, and keep slash-command acknowledgements in the source conversation. (#82642, #85475, #86569, #86705, #86706, #86770) Thanks @homer-byte, @TurboTheTurtle, @swang430, and @OmarShahine.</li>
<li>WhatsApp/QQ/Twitch/IRC/Slack: restore WhatsApp ack identity and group-drop warnings, make QQ Bot media respect <code>OPENCLAW_HOME</code>, serialize Twitch auth disconnects, store IRC channel routes canonically, and keep Slack downloaded files out of reply media. (#83833, #85309, #85777, #85794, #85906, #86318, #86697) Thanks @sliverp, @neeravmakwana, and @Kailigithub.</li>
<li>Discord/voice: improve voice playback and wake replies, bucket large model picker menus, merge media captions into one message, route metadata through configured proxies, restore numeric channel sends, suppress self-reply echoes, and tighten wake matching without breaking fuzzy wake phrases. (#80227, #86238, #86487, #86571, #86595, #86601)</li>
<li>Codex: preserve native web-search metadata, keep oversized native thread reuse, bridge CLI API-key auth into the app server, preserve sandbox bootstrap path style, recover context-window prompt errors, honor yolo approval policy, disable native thread personality, and route compaction through Codex auth. (#85378, #85542, #85891, #85909, #86408)</li>
<li>Agents/runtime: enforce session lock max-hold reclaim, release embedded-attempt locks on all exits, treat aborted subagent runs as terminal, avoid runtime model hydration on hot paths, disclose scoped session list counts, derive overflow budgets from provider errors, and keep fallback errors scoped to the active model candidate. (#70473, #85764, #86014, #86134, #86427, #86944) Thanks @openperf, @fuller-stack-dev, @zhangguiping-xydt, and @ferminquant.</li>
<li>Config/update/doctor: retry config recovery after failed backup restore, skip shell env fallback on Windows, exclude prerelease tags from the stable git channel, support deep config edits, warn instead of aborting on unreadable cron stores, prune stale bundled plugin paths, and avoid duplicate restart prompts when the Gateway is already healthy. (#85739, #85787, #86060, #86260, #86384, #86533) Thanks @liaoyl830.</li>
<li>Install/release: support Alpine CLI installs and runtime floors, prefer trusted startup argv runtime fallback roots, reject stale CLI node runtimes, avoid npm <code>min-release-age</code> installer failures, bound npm/package/Docker install phases, restore config parent ownership in Docker, seed Docker lockfile package tarballs before prune, make release/plugin prerelease checks fail closed instead of hanging or false-greening, and use host-visible Crabbox local work roots for Docker-backed proof. (#85491)</li>
<li>Windows daemon: keep Scheduled Task gateway launches running on battery power and avoid workgroup-machine prompts for a domain user during task installation. (#59299)</li>
<li>Security: avoid printing Gateway tokens in Docker, validate plugin model-pattern regexes safely, escape transcript metadata field names, harden session allowlist glob matching, audit Claude permission overrides under YOLO, and require explicit allow for ACP auto approvals. (#85849, #85934, #86046, #86557)</li>
<li>Media/images: replace Sharp with Rastermill, keep EXIF normalization best-effort, normalize HEIC/HEIF before image descriptions, route Codex image API keys through OpenAI, preserve image compression metadata, and auto-scale live tool result caps. (#85776, #86037, #86437, #86857, #86923)</li>
<li>Memory: prevent semantic vector indexes from silently degrading when embeddings are unavailable, stop doctor OOMs on large session stores, preserve sidecar hooks/artifacts, write fallback dream diaries, use CJK-aware dreaming dedupe, and avoid per-file watcher FD fan-out. (#80613, #82928, #85060, #85704, #85967, #86701) Thanks @brokemac79, @openperf, and @yaaboo-gif.</li>
<li>Agents/sessions: include visibility metadata on restricted <code>sessions_list</code> results so scoped counts are clearly reported without widening access or exposing hidden-session counts. (#86944) Thanks @ferminquant.</li>
<li>Gateway/DNS: validate wide-area discovery domains before deriving zone paths or writing zone files, so invalid <code>discovery.wideArea.domain</code> and <code>dns setup --domain</code> values fail with a DNS-name diagnostic instead of falling through to unrelated configuration errors. Thanks @mmaps.</li>
<li>Agents/BTW: route fallback side-question streams through the embedded stream resolver so Anthropic-compatible MiniMax requests use the same capped transport as normal chat. (#86312) Thanks @neeravmakwana.</li>
<li>Telegram: treat <code>/command@TargetBot</code> bot-command entities as explicit mentions for the addressed bot so <code>requireMention</code> groups no longer drop targeted commands or captions. Fixes #84462. (#86553) Thanks @luoyanglang.</li>
<li>CI: bound Docker/Bash E2E tarball npm installs with <code>OPENCLAW_E2E_NPM_INSTALL_TIMEOUT</code> so package, onboarding, plugin, and upgrade lanes fail instead of hanging on a stuck npm install.</li>
<li>CI: fail Parallels npm-update smoke jobs after the guest command timeout and cleanup backstop instead of only logging a timeout line.</li>
<li>CI: bound kitchen-sink RPC HTTP probes so stalled gateway readiness or response bodies fail and retry instead of wedging the walker.</li>
<li>CI: keep <code>OPENCLAW_TESTBOX=1 pnpm check:changed</code> delegating to Blacksmith Testbox through Crabbox without forwarding local Testbox or worker env into the remote command.</li>
<li>CI: send KILL after the TERM grace period for manual checkout fetch timeouts so stuck Testbox and workflow checkout retries cannot hang behind a wedged <code>git fetch</code>.</li>
<li>CI: send KILL after the TERM grace period for Bun global install smoke command timeouts so trapped <code>openclaw</code> child processes cannot wedge the scheduled install smoke.</li>
<li>iMessage: thread current channel/account inbound attachment roots into the image tool so iMessage-saved attachments under <code>~/Library/Messages/Attachments</code> (including the wildcard <code>/Users/*/Library/Messages/Attachments</code> root) are read through the existing inbound path policy instead of being rejected as <code>path-not-allowed</code>. Literal <code>localRoots</code> stays workspace-scoped. Fixes #30170. (#86569)</li>
<li>QQ Bot: respect <code>OPENCLAW_HOME</code> for outbound media path resolution so <code><qqmedia></code> sends no longer silently fail when <code>HOME</code> and <code>OPENCLAW_HOME</code> differ (Docker / multi-user hosts). Persisted QQ Bot data (sessions, known users, refs) stays anchored on the OS home for upgrade compatibility. Fixes #83562. Thanks @sliverp.</li>
<li>Update: report the primary malformed <code>openclaw.extensions</code> payload error without adding a duplicate missing-main diagnostic. (#86596) Thanks @ferminquant.</li>
<li>Control UI: keep host-local Markdown file paths inert while preserving app-relative links. (#86620) Thanks @BryanTegomoh.</li>
<li>Gateway: dampen repeated unauthenticated device-required probes per URL while preserving explicit-auth and paired recovery paths. (#86575) Thanks @ferminquant.</li>
<li>IRC: store inbound channel routes with the canonical <code>channel:#name</code> target and join transient channel sends before writing. (#85906) Thanks @Kailigithub.</li>
<li>Usage: surface unknown all-zero model pricing as missing cost entries instead of a confident <code>$0</code> total. (#85882) Thanks @MichaelZelbel.</li>
<li>Agents/Codex: honor yolo app-server approval policy only for the full <code>never</code> plus <code>danger-full-access</code> case. (#85909) Thanks @earlvanze.</li>
<li>Gateway/Gmail: clear Gmail watcher renewal intervals on re-entry so hot reloads do not leak lifecycle timers. (#82947) Thanks @SebTardif.</li>
<li>Logging: exit cleanly on broken stdout/stderr pipes without masking existing failure exit codes. (#80059) Thanks @pavelzak.</li>
<li>Gateway/security: escape transcript metadata field names while extracting oversized session line prefixes. (#85934) Thanks @SebTardif.</li>
<li>Plugins/security: validate manifest model pattern regexes with the safe-regex compiler so unsafe patterns are ignored before matching. (#86046) Thanks @SebTardif.</li>
<li>Discord: route gateway metadata REST lookups through the configured Discord proxy so proxied accounts do not fall back to direct <code>discord.com</code> connections before opening the WebSocket. Fixes #80227. Thanks @Clivilwalker.</li>
<li>Agents/media: hydrate current-turn image attachments from filename-derived MIME types so active vision can see generated or forwarded images whose source omitted an image content type. (#84812) Thanks @marchpure.</li>
<li>Agents/fs: point workspace-only scratch-path guidance at in-workspace temp directories while keeping host-root writes rejected by the tool guard. (#86501) Thanks @tianxiaochannel-oss88.</li>
<li>Agents/media: keep async cron media completions scoped to their run session while preserving direct delivery for stale generated-media success and failure notifications. (#86529) Thanks @ai-hpc.</li>
<li>Gateway: emit plugin <code>session_end</code>/<code>session_start</code> hooks when <code>agent.send</code> rotates or replaces a session id, keeping hook lifecycle state aligned with <code>sessions.changed</code> notifications. Fixes #83507. (#85875) Thanks @brokemac79.</li>
<li>OpenShell/SSH: reject malformed generated exec commands before sandbox/session setup so unresolved workflow placeholders fail fast instead of reaching the remote shell. Fixes #72373. Thanks @brokemac79.</li>
<li>Google: stop normalizing <code>gemini-3.1-flash-lite</code> to the retired preview endpoint and update Flash Lite alias guidance to the GA model id. Fixes #86151. (#86240) Thanks @SebTardif.</li>
<li>Installer: make Alpine apk installs cover Git, verify the Node runtime floor, try <code>nodejs-current</code>, and report Alpine version guidance when repositories only provide older Node packages.</li>
<li>Agents/status: prefer the active Claude CLI OAuth auth label over an unused Anthropic env API-key label for equivalent runtime aliases. Fixes #80184. (#86570) Thanks @brokemac79.</li>
<li>Agents/media: send direct fallback for generated media still missing after an active requester wake fails. (#85489) Thanks @fuller-stack-dev.</li>
<li>Agents: derive overflow compaction budgets from provider-reported and synthetic over-budget token counts so confirmed context overflows compact before retrying. (#70473) Thanks @fuller-stack-dev.</li>
<li>Agents/Codex: recover Codex context-window prompt errors through overflow compaction and surface reset guidance when recovery is exhausted. (#85542) Thanks @fuller-stack-dev.</li>
<li>Agents/Codex: allow Codex app-server runs to bootstrap from <code>CODEX_API_KEY</code> or <code>OPENAI_API_KEY</code> when no Codex auth profile is configured.</li>
<li>Agents/Codex: keep selected Codex runtime routing on OpenAI-Codex while preserving direct OpenAI API-key compaction fallback. (#86408) Thanks @funmerlin and @VACInc.</li>
<li>Agent transcript: include OpenClaw agent session logs when finding local transcript candidates.</li>
<li>Crabbox: bootstrap raw AWS macOS shell commands wrapped in absolute <code>time</code> paths so RSS probes can run Node and pnpm on fresh macOS runners.</li>
<li>Crabbox: bootstrap raw AWS macOS shell commands even when setup statements precede Node or pnpm usage.</li>
<li>TUI/local: skip unnecessary secret resolution, gateway model catalog loading, bootstrap, and skill scans in explicit local-model runs so startup reaches the model request faster.</li>
<li>Sessions/doctor: load large session stores without clone amplification during read-only doctor checks and reclaim stale <code>sessions.json.*.tmp</code> sidecars. Fixes #56827. Thanks @openperf.</li>
<li>Tests: clean successful plugin gateway gauntlet isolated temp roots while keeping an explicit preservation switch for failed/debug runs.</li>
<li>Plugins/perf: reuse derived plugin metadata snapshots for the lifetime of the process so reply-time skill setup no longer rescans plugin metadata on every turn.</li>
<li>Discord/OpenAI voice: keep wake-name master consults using the current speaker context after ignored ambient transcripts and shorten the default capture silence grace.</li>
<li>Doctor: skip redundant Gateway restart prompts when a recent supervisor restart leaves the Gateway healthy. Fixes #86518. (#86533) Thanks @liaoyl830.</li>
<li>Cron: restore suspended cron lanes to the configured/default concurrency instead of falling back to one after quota or circuit-breaker auto-resume.</li>
<li>Gateway: keep session-only Control UI tool-start mirrors flowing during diagnostic queue pressure instead of silently dropping non-terminal tool updates.</li>
<li>Agents/memory: return optional not-found context for missing date-only daily memory reads instead of logging benign first-run <code>ENOENT</code> failures. Fixes #82928. Thanks @galiniliev.</li>
<li>Discord: merge streamed text captions into following media block replies so captions and attachments send as one message. (#86487) Thanks @neeravmakwana.</li>
<li>Gateway: avoid sending duplicate tool-event frames to Control UI connections that are subscribed by both run and session.</li>
<li>Discord/OpenAI voice: accept broader edge-position fuzzy wake-name transcripts while keeping ambient speech gated.</li>
<li>Discord/OpenAI voice: accept longer leading wake-name mistranscripts such as "Open Club" for OpenClaw.</li>
<li>Agents/OpenAI-compatible: stop ModelStudio-compatible chat requests before sending system/tool-only payloads that have no usable user or assistant turn. (#86177) Thanks @TurboTheTurtle.</li>
<li>Gateway/plugins: reuse plugin package realpath checks while building installed plugin indexes so startup avoids repeated filesystem resolution work.</li>
<li>Kilo Gateway: send string <code>stop</code> sequences as arrays so Kilo accepts OpenAI-compatible chat completions. (#86461) Thanks @SebTardif.</li>
<li>Discord/OpenAI voice: accept leading fuzzy wake-name transcripts such as "Monty" or "Moti" for a Molty agent while keeping ambient speech gated.</li>
<li>Media understanding: convert HEIC and HEIF images to JPEG before image description providers run so iPhone photos work in direct and configured image-description flows. (#86037)</li>
<li>Agents: release embedded-attempt session locks from outer teardown so post-prompt exceptions cannot wedge later requests behind <code>SessionWriteLockTimeoutError</code>. Fixes #86014. Thanks @openperf.</li>
<li>Discord/OpenAI voice: rotate Realtime sessions at provider max duration without logging the expected session-expiry event as an error.</li>
<li>Sessions: skip metadata-only entries during QMD-slugified session lookup so one incomplete row does not block transcript hit resolution. (#86327) Thanks @abnershang.</li>
<li>Agents/media: derive bundled plugin local-media trust from plugin tool metadata instead of importing the full plugin registry on subscription paths. (#84409) Thanks @samzong.</li>
<li>Image tool: keep config-backed custom-provider API keys usable for auto-discovered vision models, including deferred image-tool execution without env keys or auth profiles. (#85733)</li>
<li>Memory/local embeddings: run local GGUF embeddings in an isolated worker sidecar and degrade to configured fallback or keyword search on worker failure so native embedding crashes do not take down the Gateway. (#85348) Thanks @osolmaz.</li>
<li>Gateway: clear the runtime config snapshot before <code>SIGUSR1</code> in-process restarts so config changes survive the next gateway loop. (#86388) Thanks @XuZehan-iCenter.</li>
<li>Models: show OAuth delegation markers as configured <code>models.json</code> auth while keeping runtime route usability checks strict. (#86378) Thanks @rohitjavvadi.</li>
<li>Cron: seed active scheduled and manual cron task rows with a progress summary so status surfaces do not look blank while jobs run. (#86313) Thanks @ferminquant.</li>
<li>Cron: preserve unsupported persisted cron payload rows during routine store writes while keeping those rows non-runnable. Fixes #84922. (#86415) Thanks @IWhatsskill.</li>
<li>Updater: exclude prerelease git tags from stable channel resolution so source updates do not check out newer alpha/rc/preview/canary tags. (#86260) Thanks @stevenepalmer.</li>
<li>Security/Audit: flag webhook <code>hooks.token</code> reuse of active Gateway password auth in <code>openclaw security audit</code> while keeping password-mode startup compatibility. (#84338) Thanks @coygeek.</li>
<li>QQBot: derive the outbound reply watchdog from configured agent and provider timeouts so slow local model replies are not cut off at five minutes. Fixes #85267. (#85271) Thanks @SymbolStar.</li>
<li>Agents/heartbeat: stop heartbeat turns after the first valid <code>heartbeat_respond</code> so repeated response loops do not burn tokens. (#86357) Thanks @udaymanish6.</li>
<li>Tasks: keep retained lost tasks out of default status health counts, explain their cleanup window during maintenance, and prune lost task records after 24 hours instead of the general 7-day terminal retention.</li>
<li>Memory-core: keep REM dreaming focused on live light-staged memories and mark staged entries as considered so old recall history no longer dominates fresh candidates. (#86302) Thanks @SebTardif.</li>
<li>Memory: abort sync instead of downgrading an existing semantic vector index to FTS-only when the configured embedding provider is temporarily unavailable. (#85704) Thanks @yaaboo-gif.</li>
<li>Telegram: propagate forum topic names through the account-scoped topic cache for native command context and topic create/edit actions. (#86299) Thanks @SebTardif.</li>
<li>Slack: keep downloaded read-only files out of reply media so Slack file reads do not echo files back to the conversation. (#86318) Thanks @neeravmakwana.</li>
<li>Cron: accept leading-plus relative durations such as <code>+5m</code> for one-shot <code>--at</code> schedules. (#86341) Thanks @mushuiyu886.</li>
<li>Agents/media: preserve async-started media tool metadata so background generation starts no longer surface generic incomplete-turn warnings while replay stays unsafe. (#85933) Thanks @fuller-stack-dev.</li>
<li>Docker E2E: dedupe scheduler lane resources so npm/service package lanes are not over-counted and serialized unnecessarily.</li>
<li>QA/diagnostics: add a collector-backed OpenTelemetry smoke lane, make the OTLP payload leak check scenario-aware, and keep source QA builds from failing on optional dependency imports resolved through pnpm's temp module path.</li>
<li>Crabbox: bootstrap Git metadata for sparse remote changed gates so raw synced workspaces can run <code>pnpm check:changed</code> from the intended diff.</li>
<li>xAI/LM Studio: avoid buffering ordinary bracketed or <code>final</code> prose until stream completion while watching for plain-text tool-call fallbacks.</li>
<li>Doctor: warn and continue when the cron job store exists but cannot be read so later health checks still run. Fixes #86102. (#86384) Thanks @1052326311.</li>
<li>Discord: suppress a bot's previous reply body and referenced media from prompt context when a user replies to that bot message, while keeping reply metadata for routing. (#86238) Thanks @fuller-stack-dev.</li>
<li>Discord: restore bare numeric channel IDs for outbound message-tool sends while keeping explicit DM targets unambiguous. (#86571) Thanks @joshavant.</li>
<li>Docker E2E: avoid rebuilding the Control UI twice while preparing the shared OpenClaw package tarball for package-backed scenario runs.</li>
<li>Tests: avoid rebuilding the Control UI twice during the installer Docker smoke now that <code>pnpm build</code> includes <code>ui:build</code>.</li>
<li>Tests: give QA config mutation RPCs enough native Windows budget to finish gateway config writes and restart settle after hot scenario runs.</li>
<li>Tests: keep the gateway restart-inflight QA scenario focused on restart recovery on native Windows by allowing expected embedded prompt handoff errors and using the Windows-safe timeout budget.</li>
<li>QA-Lab: make the synthetic OpenAI provider honor generic <code>reply exactly:</code> directives after required kickoff reads so restart-recovery scenarios do not fall through to generic repo-summary prose.</li>
<li>Gateway: abort active <code>agent</code> RPC runs during forced restart shutdown so stale in-process turns cannot keep writing a session after the Gateway lifecycle restarts.</li>
<li>Crabbox: sync clean sparse worktrees through a temporary full checkout even when reusing an existing lease so tracked build-time files are not omitted.</li>
<li>Build: route <code>scripts/ui.js</code> through the shared pnpm runner and keep Control UI chunking helpers in sparse-included source so native Windows Corepack builds can produce <code>dist/control-ui</code>.</li>
<li>Tests: give the memory fallback QA scenario enough turn budget to exercise native Windows gateway runs instead of failing on the client timeout while the mock agent is still dispatching.</li>
<li>Tests: collect QA gateway CPU/RSS metrics on native Windows and give the channel baseline enough turn budget to report slow gateway runs instead of timing out before proof.</li>
<li>Install/update: bypass npm <code>min-release-age</code> policies with <code>--min-release-age=0</code> instead of <code>--before</code> so hosted installers keep working on npm versions that reject the combined config. (#84749) Thanks @TeodoroRodrigo.</li>
<li>Diagnostics: reclaim wedged session lanes when stale active-run bookkeeping blocks queued work despite no forward progress. Fixes #85639. Thanks @openperf.</li>
<li>WebChat: keep message-tool replies visible in the chat while still summarizing internal tool results for the model. Fixes #86347. Thanks @shakkernerd.</li>
<li>Gateway/perf: fail startup benchmark samples when the Gateway process exits before benchmark teardown, including signal deaths after readiness probes.</li>
<li>Gateway/perf: fail restart benchmark samples when the Gateway exits before benchmark teardown, including clean exits and signal deaths after successful restart probes.</li>
<li>Agents/tests: keep model catalog visibility on static selection helpers so catalog visibility checks avoid the broad model-selection barrel import.</li>
<li>Agents/commitments: serialize commitment store load-modify-save writes so concurrent heartbeat and CLI updates no longer lose dismissal, sent, or attempt state. (#81153) Thanks @ai-hpc.</li>
<li>xAI/LM Studio: promote plain-text tool-call fallbacks into structured tool calls and strip leaked internal tool syntax before user-facing delivery. (#86222) Thanks @fuller-stack-dev.</li>
<li>CLI: suppress benign self-update version-skew warnings during package post-update finalization.</li>
<li>Gateway/perf: tighten restart and startup benchmark failure handling so long profiling runs, failed probes, and fresh Linux runners no longer produce false passing or <code>n/a</code> results.</li>
<li>Checks: keep intentional Knip unused-file findings optional so full CI and sparse proof workspaces stay aligned.</li>
<li>Docker: restore writable <code>~/.config</code> in runtime images. Fixes #85968. Thanks @hkoessler and @Bartok9.</li>
<li>Plugin SDK: keep legacy root diagnostic subscriptions connected when built plugin SDK aliases resolve diagnostic helpers through a separate module graph.</li>
<li>Diagnostics: export alertable OTel and Prometheus signals for blocked tools, model failover, stale sessions, liveness warnings, oversized payloads, and webhook ingress while fixing shared OTLP endpoints with query strings.</li>
<li>Tests: normalize macOS canonical temp paths in exec allowlists, fs-safe trash assertions, installed plugin matching, Telegram topic-name stores, and built ACPX MCP server expectations so native macOS proof runners cover the intended behavior.</li>
<li>Codex/app-server: preserve message-tool-only source reply delivery mode on active runs so sub-agent completion wakeups can steer the active Codex turn instead of being rejected. (#86287) Thanks @ferminquant.</li>
<li>Tests: sample the Windows kitchen-sink RPC gateway directly and serialize RSS probes so native runs keep the memory guard active.</li>
<li>Tests: normalize bundled plugin lifecycle probe paths and state-root lookup so native Windows release sweeps accept valid packaged plugin installs.</li>
<li>Agents/Claude CLI: route live native Bash permission requests through OpenClaw exec policy so Claude turns no longer stall on <code>control_request</code>, and document that OpenClaw exec policy is authoritative. Fixes #80819. (#86330, from #81971) Thanks @guthirry and @sallyom.</li>
<li>Security audit: warn when YOLO OpenClaw exec policy overrides a restrictive raw Claude <code>--permission-mode</code> for managed live sessions. (#86557) Thanks @sallyom.</li>
<li>Config: keep benign legacy metadata write anomalies out of default doctor and config command output while preserving explicit anomaly logging for diagnostics.</li>
<li>Codex: log when implicit app-server <code>never</code> approvals are promoted for OpenClaw tool policy, including whether the trigger was a <code>before_tool_call</code> hook or trusted tool policy.</li>
<li>Codex harness: make subscription usage-limit errors without reset times explain that OpenClaw cannot determine the reset and point users to wait until Codex is available, use another Codex account, or switch to another configured model/provider. Thanks @amknight.</li>
<li>Google Vertex: support production ADC modes such as Workload Identity Federation, service-account credentials, and metadata-server ADC for the native Vertex transport. (#83971) Thanks @damianFelixPago.</li>
<li>Telegram: route normal <code>[telegram][diag]</code> polling diagnostics through <code>runtime.log</code> while keeping non-diag warnings and persistence failures on <code>runtime.error</code>, so healthy polling startup no longer looks like an error. Fixes #82957. (#82958) Thanks @galiniliev.</li>
<li>Providers/Ollama: strip inline Kimi cloud reasoning prefixes from streamed and final visible replies while keeping ordinary Kimi answers append-only. (#86286) Thanks @jason-allen-oneal.</li>
</ul>
<ul>
<li>Gateway: require Talk secret authority before setup-code handoff can include Talk secrets. (#85690) Thanks @ngutman.</li>
<li>Agents: keep fallback error reporting scoped to the active model candidate so stale prior-provider quota/auth text is not reported for later fallback attempts. (#86134) Thanks @zhangguiping-xydt.</li>
<li>iMessage: dedupe watcher startup when <code>channels.imessage.accounts</code> lists both <code>default</code> and a named account that point at the same local Messages source, so the gateway no longer spawns two <code>imsg rpc</code> processes or doubles inbound replies; the dedupe is scoped to watcher startup, leaving duplicate accounts addressable for outbound sends, status, and capability listings, and <code>openclaw doctor</code> flags the redundant account with a rebinding hint. Fixes #65141. (#86705) Thanks @swang430.</li>
</ul>
<p><a href="https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md">View full changelog</a></p>
]]></description>
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.5.26/OpenClaw-2026.5.26.zip" length="54484748" type="application/octet-stream" sparkle:edSignature="y4WXG7JT8ktJ+K7YDgllY7u5Z9BSKR/SwGiwEh0gikOJ/SWqwcQd+z2tWa2zgwvCJKWsAUFwJs1ATor880SUBg=="/>
</item>
</channel>
</rss>

View File

@@ -218,6 +218,7 @@ Current OpenClaw Android implication:
- Google Play build excludes SMS send/search, Call Log search, and recent-photo access unless the product is intentionally positioned and approved under the relevant policy exception.
- The repo now ships this split as Android product flavors:
- `play`: removes `READ_SMS`, `SEND_SMS`, `READ_CALL_LOG`, `READ_MEDIA_IMAGES`, `READ_MEDIA_VISUAL_USER_SELECTED`, and `READ_EXTERNAL_STORAGE`; hides SMS, Call Log, and Photos surfaces in onboarding, settings, and advertised node capabilities.
- Installed-app listing is user controlled. `device.apps` is advertised only after the user enables **Settings > Phone Capabilities > Installed Apps**. The command defaults to launcher-visible apps and does not require `QUERY_ALL_PACKAGES`.
- `thirdParty`: keeps the full permission set and the existing SMS / Call Log / Photos functionality.
Policy links:
@@ -252,9 +253,9 @@ Pre-req checklist:
4) Open the app **Screen** tab and keep it active during the run (canvas/A2UI commands require the canvas WebView attached there).
5) Grant runtime permissions for capabilities you expect to pass (camera/mic/location/notification listener/location, etc.).
6) No interactive system dialogs should be pending before test start.
7) Canvas host is enabled and reachable from the device (do not run gateway with `OPENCLAW_SKIP_CANVAS_HOST=1`; startup logs should include `canvas host mounted at .../__openclaw__/`).
7) Canvas host is enabled and reachable from the device for remote Canvas checks (do not run gateway with `OPENCLAW_SKIP_CANVAS_HOST=1`; startup logs should include `canvas host mounted at .../__openclaw__/`).
8) Local operator test client pairing is approved. If first run fails with `pairing required`, preview the latest pending request, approve the printed request ID, then rerun:
9) For A2UI checks, keep the app on **Screen** tab; the node now auto-refreshes canvas capability once on first A2UI reachability failure (TTL-safe retry).
9) For A2UI checks, keep the app on **Screen** tab; the node uses its bundled app-owned A2UI page for message application.
```bash
openclaw devices list
@@ -286,8 +287,8 @@ Common failure quick-fixes:
- `pairing required` before tests start:
- list pending requests (`openclaw devices list`), then approve with the exact ID (`openclaw devices approve <requestId>`) and rerun.
- `A2UI host not reachable` / `A2UI_HOST_NOT_CONFIGURED`:
- ensure the Canvas plugin host is running and reachable, keep the app on the **Screen** tab. The app refreshes the Canvas plugin surface URL once before failing; if it still fails, reconnect app and rerun.
- `A2UI host not reachable` / `A2UI_HOST_UNAVAILABLE`:
- keep the app foregrounded on the **Screen** tab and rerun. A2UI commands use the bundled app-owned A2UI page; the Gateway Canvas host is still needed for remote Canvas checks, but not for A2UI message application.
- `NODE_BACKGROUND_UNAVAILABLE: canvas unavailable`:
- app is not effectively ready for canvas commands; keep app foregrounded and **Screen** tab active.

View File

@@ -65,8 +65,8 @@ android {
applicationId = "ai.openclaw.app"
minSdk = 31
targetSdk = 36
versionCode = 2026053101
versionName = "2026.5.31"
versionCode = 2026060201
versionName = "2026.6.2"
ndk {
// Support all major ABIs — native libs are tiny (~47 KB per ABI)
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")

View File

@@ -2,18 +2,35 @@ package ai.openclaw.app
import ai.openclaw.app.ui.OpenClawTheme
import ai.openclaw.app.ui.RootScreen
import android.content.Intent
import android.os.Bundle
import android.view.WindowManager
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.runtime.withFrameNanos
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
import androidx.core.view.WindowCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
/**
* Main Android activity that owns Compose UI attachment and runtime UI wiring.
@@ -21,18 +38,81 @@ import kotlinx.coroutines.launch
class MainActivity : ComponentActivity() {
private val viewModel: MainViewModel by viewModels()
private lateinit var permissionRequester: PermissionRequester
private var initializedViewModel: MainViewModel? = null
private var didAttachRuntimeUi = false
private var didStartNodeService = false
private var didStartViewModelCollectors = false
private var foreground = false
private var pendingIntent: Intent? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
handleAssistantIntent(intent)
pendingIntent = intent
WindowCompat.setDecorFitsSystemWindows(window, false)
permissionRequester = PermissionRequester(this)
setContent {
var activeViewModel by remember { mutableStateOf<MainViewModel?>(null) }
LaunchedEffect(Unit) {
withFrameNanos { }
withContext(Dispatchers.Default) {
(application as NodeApp).prefs
}
val readyViewModel = viewModel
activateViewModel(readyViewModel)
activeViewModel = readyViewModel
}
OpenClawTheme {
activeViewModel?.let { RootScreen(viewModel = it) } ?: StartupSurface()
}
}
}
override fun onStart() {
super.onStart()
foreground = true
initializedViewModel?.setForeground(true)
}
override fun onStop() {
foreground = false
initializedViewModel?.setForeground(false)
super.onStop()
}
override fun onNewIntent(intent: android.content.Intent) {
super.onNewIntent(intent)
setIntent(intent)
pendingIntent = intent
initializedViewModel?.let { handleAssistantIntent(viewModel = it, intent = intent) }
}
/**
* Wires MainViewModel only after Activity first draw and background prefs warm-up.
*/
private fun activateViewModel(readyViewModel: MainViewModel) {
if (initializedViewModel != null) return
initializedViewModel = readyViewModel
readyViewModel.setForeground(foreground)
startViewModelCollectors(readyViewModel)
pendingIntent?.let { initialIntent ->
handleAssistantIntent(viewModel = readyViewModel, intent = initialIntent)
pendingIntent = null
}
}
/**
* Starts lifecycle collectors after ViewModel construction so they cannot force early startup.
*/
private fun startViewModelCollectors(readyViewModel: MainViewModel) {
if (didStartViewModelCollectors) return
didStartViewModelCollectors = true
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.preventSleep.collect { enabled ->
readyViewModel.preventSleep.collect { enabled ->
if (enabled) {
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
} else {
@@ -44,10 +124,10 @@ class MainActivity : ComponentActivity() {
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.runtimeInitialized.collect { ready ->
readyViewModel.runtimeInitialized.collect { ready ->
if (!ready || didAttachRuntimeUi) return@collect
// Runtime UI helpers need an Activity owner, so attach once after NodeRuntime is ready.
viewModel.attachRuntimeUi(owner = this@MainActivity, permissionRequester = permissionRequester)
readyViewModel.attachRuntimeUi(owner = this@MainActivity, permissionRequester = permissionRequester)
didAttachRuntimeUi = true
if (!didStartNodeService) {
NodeForegroundService.start(this@MainActivity)
@@ -56,36 +136,15 @@ class MainActivity : ComponentActivity() {
}
}
}
setContent {
OpenClawTheme {
Surface(modifier = Modifier) {
RootScreen(viewModel = viewModel)
}
}
}
}
override fun onStart() {
super.onStart()
viewModel.setForeground(true)
}
override fun onStop() {
viewModel.setForeground(false)
super.onStop()
}
override fun onNewIntent(intent: android.content.Intent) {
super.onNewIntent(intent)
setIntent(intent)
handleAssistantIntent(intent)
}
/**
* Routes assistant/app-action intents into ViewModel state without recreating the activity.
*/
private fun handleAssistantIntent(intent: android.content.Intent?) {
private fun handleAssistantIntent(
viewModel: MainViewModel,
intent: Intent?,
) {
parseHomeDestinationIntent(intent)?.let { destination ->
viewModel.requestHomeDestination(destination)
return
@@ -94,3 +153,23 @@ class MainActivity : ComponentActivity() {
viewModel.handleAssistantLaunch(request)
}
}
@Composable
private fun StartupSurface() {
Surface(
modifier = Modifier.fillMaxSize(),
color = Color.Black,
contentColor = Color.White,
) {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center,
) {
Text(
text = "OPENCLAW",
fontSize = 22.sp,
fontWeight = FontWeight.Medium,
)
}
}
}

View File

@@ -4,6 +4,8 @@ import ai.openclaw.app.chat.ChatMessage
import ai.openclaw.app.chat.ChatPendingToolCall
import ai.openclaw.app.chat.ChatSessionEntry
import ai.openclaw.app.chat.OutgoingAttachment
import ai.openclaw.app.gateway.DeviceAuthStore
import ai.openclaw.app.gateway.DeviceIdentityStore
import ai.openclaw.app.gateway.GatewayEndpoint
import ai.openclaw.app.gateway.GatewayUpdateAvailableSummary
import ai.openclaw.app.node.CameraCaptureManager
@@ -14,6 +16,7 @@ import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
@@ -21,6 +24,7 @@ import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
/**
* UI-facing bridge that exposes NodeRuntime and preference state as Compose-friendly StateFlows.
@@ -32,7 +36,11 @@ class MainViewModel(
private val nodeApp = app as NodeApp
private val prefs = nodeApp.prefs
private val runtimeRef = MutableStateFlow<NodeRuntime?>(null)
private var foreground = true
@Volatile private var foreground = false
@Volatile private var runtimeStartupQueued = false
private val _requestedHomeDestination = MutableStateFlow<HomeDestination?>(null)
val requestedHomeDestination: StateFlow<HomeDestination?> = _requestedHomeDestination
private val _startOnboardingAtGatewaySetup = MutableStateFlow(false)
@@ -53,6 +61,19 @@ class MainViewModel(
return runtime
}
/**
* Starts the node runtime off the main thread so fresh installs can render
* the shell before encrypted prefs, device identity, and gateway setup warm up.
*/
private fun queueRuntimeStartup() {
if (runtimeRef.value != null || runtimeStartupQueued) return
runtimeStartupQueued = true
viewModelScope.launch(Dispatchers.Default) {
runCatching { ensureRuntime() }
runtimeStartupQueued = false
}
}
/**
* Adapts a runtime StateFlow to a stable ViewModel StateFlow before runtime startup.
*/
@@ -91,6 +112,7 @@ class MainViewModel(
val isConnected: StateFlow<Boolean> = runtimeState(initial = false) { it.isConnected }
val isNodeConnected: StateFlow<Boolean> = runtimeState(initial = false) { it.nodeConnected }
val statusText: StateFlow<String> = runtimeState(initial = "Offline") { it.statusText }
val gatewayConnectionProblem: StateFlow<GatewayConnectionProblem?> = runtimeState(initial = null) { it.gatewayConnectionProblem }
val serverName: StateFlow<String?> = runtimeState(initial = null) { it.serverName }
val remoteAddress: StateFlow<String?> = runtimeState(initial = null) { it.remoteAddress }
val gatewayVersion: StateFlow<String?> = runtimeState(initial = null) { it.gatewayVersion }
@@ -148,6 +170,7 @@ class MainViewModel(
val gatewayBootstrapToken: StateFlow<String> = prefs.gatewayBootstrapToken
val onboardingCompleted: StateFlow<Boolean> = prefs.onboardingCompleted
val canvasDebugStatusEnabled: StateFlow<Boolean> = prefs.canvasDebugStatusEnabled
val installedAppsSharingEnabled: StateFlow<Boolean> = prefs.installedAppsSharingEnabled
val speakerEnabled: StateFlow<Boolean> = prefs.speakerEnabled
val voiceCaptureMode: StateFlow<VoiceCaptureMode> = runtimeState(initial = VoiceCaptureMode.Off) { it.voiceCaptureMode }
val micEnabled: StateFlow<Boolean> = runtimeState(initial = false) { it.micEnabled }
@@ -179,12 +202,6 @@ class MainViewModel(
val chatSessions: StateFlow<List<ChatSessionEntry>> = runtimeState(initial = emptyList()) { it.chatSessions }
val pendingRunCount: StateFlow<Int> = runtimeState(initial = 0) { it.pendingRunCount }
init {
if (prefs.onboardingCompleted.value) {
ensureRuntime()
}
}
val canvas: CanvasController
get() = ensureRuntime().canvas
@@ -212,13 +229,10 @@ class MainViewModel(
*/
fun setForeground(value: Boolean) {
foreground = value
val runtime =
if (value && prefs.onboardingCompleted.value) {
ensureRuntime()
} else {
runtimeRef.value
}
runtime?.setForeground(value)
if (value && prefs.onboardingCompleted.value) {
queueRuntimeStartup()
}
runtimeRef.value?.setForeground(value)
}
fun setDisplayName(value: String) {
@@ -269,9 +283,51 @@ class MainViewModel(
prefs.setGatewayPassword(value)
}
/** Clears setup credentials through the runtime so active gateway sessions drop stale auth state. */
fun resetGatewaySetupAuth() {
ensureRuntime().resetGatewaySetupAuth()
/** Clears setup credentials without starting the runtime just to discard first-run pairing auth. */
private fun resetGatewaySetupAuth() {
runtimeRef.value?.resetGatewaySetupAuth() ?: resetGatewaySetupAuthWithoutRuntime()
}
private fun resetGatewaySetupAuthWithoutRuntime() {
prefs.clearGatewaySetupAuth()
val deviceId = DeviceIdentityStore(nodeApp).loadOrCreate().deviceId
val deviceAuthStore = DeviceAuthStore(prefs)
deviceAuthStore.clearToken(deviceId, "node")
deviceAuthStore.clearToken(deviceId, "operator")
}
fun saveGatewayConfigAndConnect(
host: String,
port: Int,
tls: Boolean,
token: String,
bootstrapToken: String,
password: String,
resetSetupAuth: Boolean,
) {
// Gateway pairing touches encrypted prefs, identity files, and sockets; keep
// the whole sequence off the Compose thread so retries cannot trigger ANRs.
viewModelScope.launch(Dispatchers.Default) {
if (resetSetupAuth) {
resetGatewaySetupAuth()
}
prefs.setManualEnabled(true)
prefs.setManualHost(host)
prefs.setManualPort(port)
prefs.setManualTls(tls)
prefs.setGatewayBootstrapToken(bootstrapToken)
prefs.setGatewayToken(token)
prefs.setGatewayPassword(password)
ensureRuntime()
.connect(
GatewayEndpoint.manual(host = host, port = port),
NodeRuntime.GatewayConnectAuth(
token = token.ifEmpty { null },
bootstrapToken = bootstrapToken.ifEmpty { null },
password = password.ifEmpty { null },
),
)
}
}
/** Marks onboarding complete and starts the runtime before UI observes connected-state flows. */
@@ -284,10 +340,12 @@ class MainViewModel(
/** Re-enters gateway setup after disconnecting and clearing one-time setup credentials. */
fun pairNewGateway() {
runtimeRef.value?.disconnect()
resetGatewaySetupAuth()
_startOnboardingAtGatewaySetup.value = true
prefs.setOnboardingCompleted(false)
viewModelScope.launch(Dispatchers.Default) {
runtimeRef.value?.disconnect()
resetGatewaySetupAuth()
prefs.setOnboardingCompleted(false)
_startOnboardingAtGatewaySetup.value = true
}
}
/** Acknowledges the one-shot request that opens onboarding at the gateway setup step. */
@@ -299,6 +357,10 @@ class MainViewModel(
prefs.setCanvasDebugStatusEnabled(value)
}
fun setInstalledAppsSharingEnabled(value: Boolean) {
ensureRuntime().setInstalledAppsSharingEnabled(value)
}
fun setNotificationForwardingEnabled(value: Boolean) {
ensureRuntime().setNotificationForwardingEnabled(value)
}
@@ -379,13 +441,25 @@ class MainViewModel(
}
fun refreshGatewayConnection() {
ensureRuntime().refreshGatewayConnection()
viewModelScope.launch(Dispatchers.Default) {
ensureRuntime().refreshGatewayConnection()
}
}
fun startGatewayDiscovery() {
queueRuntimeStartup()
}
fun connect(endpoint: GatewayEndpoint) {
ensureRuntime().connect(endpoint)
}
fun connectInBackground(endpoint: GatewayEndpoint) {
viewModelScope.launch(Dispatchers.Default) {
ensureRuntime().connect(endpoint)
}
}
fun connect(
endpoint: GatewayEndpoint,
token: String?,

View File

@@ -78,6 +78,25 @@ import java.util.concurrent.atomic.AtomicLong
/**
* Process runtime that owns gateway sessions, node command handlers, capture managers, and UI-facing state.
*/
data class GatewayConnectionProblem(
val code: String?,
val message: String,
val reason: String?,
val requestId: String?,
val recommendedNextStep: String?,
val pauseReconnect: Boolean,
val retryable: Boolean,
) {
val isPairingRequired: Boolean = code == "PAIRING_REQUIRED"
val canAutoRetry: Boolean =
isPairingRequired &&
(
retryable ||
!pauseReconnect ||
recommendedNextStep == "wait_then_retry"
)
}
class NodeRuntime(
context: Context,
val prefs: SecurePrefs = SecurePrefs(context.applicationContext),
@@ -189,8 +208,6 @@ class NodeRuntime(
A2UIHandler(
canvas = canvas,
json = json,
getNodeCanvasHostUrl = { nodeSession.currentCanvasHostUrl() },
getOperatorCanvasHostUrl = { operatorSession.currentCanvasHostUrl() },
)
private val connectionManager: ConnectionManager =
@@ -207,6 +224,7 @@ class NodeRuntime(
callLogAvailable = { SensitiveFeatureConfig.callLogEnabled },
photosAvailable = { SensitiveFeatureConfig.photosEnabled },
hasRecordAudioPermission = { hasRecordAudioPermission() },
installedAppsSharingEnabled = { installedAppsSharingEnabled.value },
manualTls = { manualTls.value },
)
@@ -245,6 +263,7 @@ class NodeRuntime(
smsTelephonyAvailable = { sms.hasTelephonyFeature() },
callLogAvailable = { SensitiveFeatureConfig.callLogEnabled },
photosAvailable = { SensitiveFeatureConfig.photosEnabled },
installedAppsSharingEnabled = { installedAppsSharingEnabled.value },
debugBuild = { BuildConfig.DEBUG },
onCanvasA2uiPush = {
_canvasA2uiHydrated.value = true
@@ -252,7 +271,6 @@ class NodeRuntime(
_canvasRehydrateErrorText.value = null
},
onCanvasA2uiReset = { _canvasA2uiHydrated.value = false },
refreshCanvasHostUrl = { nodeSession.refreshCanvasHostUrl() },
motionActivityAvailable = { motionHandler.isActivityAvailable() },
motionPedometerAvailable = { motionHandler.isPedometerAvailable() },
)
@@ -286,6 +304,8 @@ class NodeRuntime(
private val _statusText = MutableStateFlow("Offline")
val statusText: StateFlow<String> = _statusText.asStateFlow()
private val _gatewayConnectionProblem = MutableStateFlow<GatewayConnectionProblem?>(null)
val gatewayConnectionProblem: StateFlow<GatewayConnectionProblem?> = _gatewayConnectionProblem.asStateFlow()
private val _pendingGatewayTrust = MutableStateFlow<GatewayTrustPrompt?>(null)
val pendingGatewayTrust: StateFlow<GatewayTrustPrompt?> = _pendingGatewayTrust.asStateFlow()
@@ -411,6 +431,7 @@ class NodeRuntime(
identityStore = identityStore,
deviceAuthStore = deviceAuthStore,
onConnected = { hello ->
_gatewayConnectionProblem.value = null
operatorConnected = true
operatorStatusText = "Connected"
_serverName.value = hello.serverName
@@ -458,6 +479,7 @@ class NodeRuntime(
updateStatus()
micCapture.onGatewayConnectionChanged(false)
},
onConnectFailure = ::handleGatewayConnectFailure,
onEvent = { event, payloadJson ->
handleGatewayEvent(event, payloadJson)
},
@@ -469,6 +491,7 @@ class NodeRuntime(
identityStore = identityStore,
deviceAuthStore = deviceAuthStore,
onConnected = {
_gatewayConnectionProblem.value = null
_nodeConnected.value = true
nodeStatusText = "Connected"
didAutoRequestCanvasRehydrate = false
@@ -494,6 +517,7 @@ class NodeRuntime(
updateStatus()
showLocalCanvasOnDisconnect()
},
onConnectFailure = ::handleGatewayConnectFailure,
onEvent = { _, _ -> },
onInvoke = { req ->
invokeDispatcher.handleInvoke(req.command, req.paramsJson)
@@ -688,6 +712,23 @@ class NodeRuntime(
updateHomeCanvasState()
}
private fun handleGatewayConnectFailure(
error: GatewaySession.ErrorShape,
pauseReconnect: Boolean,
) {
val details = error.details
_gatewayConnectionProblem.value =
GatewayConnectionProblem(
code = details?.code ?: error.code,
message = error.message,
reason = details?.reason,
requestId = details?.requestId,
recommendedNextStep = details?.recommendedNextStep,
pauseReconnect = pauseReconnect || details?.pauseReconnect == true,
retryable = details?.retryable == true,
)
}
private fun resolveMainSessionKey(): String {
val trimmed = _mainSessionKey.value.trim()
return if (trimmed.isEmpty()) "main" else trimmed
@@ -866,6 +907,7 @@ class NodeRuntime(
val lastDiscoveredStableId: StateFlow<String> = prefs.lastDiscoveredStableId
val canvasDebugStatusEnabled: StateFlow<Boolean> = prefs.canvasDebugStatusEnabled
val installedAppsSharingEnabled: StateFlow<Boolean> = prefs.installedAppsSharingEnabled
val notificationForwardingEnabled: StateFlow<Boolean> = prefs.notificationForwardingEnabled
val notificationForwardingMode: StateFlow<NotificationPackageFilterMode> =
prefs.notificationForwardingMode
@@ -1077,6 +1119,12 @@ class NodeRuntime(
prefs.setCanvasDebugStatusEnabled(value)
}
fun setInstalledAppsSharingEnabled(value: Boolean) {
if (prefs.installedAppsSharingEnabled.value == value) return
prefs.setInstalledAppsSharingEnabled(value)
refreshNodeSurfaceAfterSharingChange()
}
fun setNotificationForwardingEnabled(value: Boolean) {
prefs.setNotificationForwardingEnabled(value)
}
@@ -1404,16 +1452,24 @@ class NodeRuntime(
}
fun refreshGatewayConnection() {
val endpoint =
connectedEndpoint ?: run {
_statusText.value = "Failed: no cached gateway endpoint"
return
}
val endpoint = connectedEndpoint
if (endpoint == null) {
resolvePreferredGatewayEndpoint()?.let(::connect)
?: run {
_statusText.value = "Failed: no saved gateway endpoint"
}
return
}
operatorStatusText = "Connecting…"
updateStatus()
connectWithAuth(endpoint = endpoint, auth = resolveGatewayConnectAuth(), reconnect = true)
}
private fun refreshNodeSurfaceAfterSharingChange() {
val endpoint = connectedEndpoint ?: return
connectWithAuth(endpoint = endpoint, auth = resolveGatewayConnectAuth(), reconnect = true)
}
private fun connectWithAuth(
endpoint: GatewayEndpoint,
auth: GatewayConnectAuth,
@@ -1513,6 +1569,7 @@ class NodeRuntime(
connectAttemptId: Long,
) {
if (!isCurrentConnectAttempt(connectAttemptId)) return
_gatewayConnectionProblem.value = null
connectedEndpoint = endpoint
operatorStatusText = "Connecting…"
nodeStatusText = "Connecting…"
@@ -1609,6 +1666,7 @@ class NodeRuntime(
stopActiveVoiceSession()
connectedEndpoint = null
activeGatewayAuth = null
_gatewayConnectionProblem.value = null
_pendingGatewayTrust.value = null
operatorSession.disconnect()
nodeSession.disconnect()
@@ -1847,7 +1905,7 @@ class NodeRuntime(
return
}
try {
val modelsRes = operatorSession.request("models.list", """{"view":"all"}""")
val modelsRes = operatorSession.request("models.list", "{}")
val modelsRoot = json.parseToJsonElement(modelsRes).asObjectOrNull()
_modelCatalog.value = parseGatewayModels(modelsRoot?.get("models") as? JsonArray)
@@ -2074,6 +2132,7 @@ class NodeRuntime(
id = id,
name = obj["name"].asStringOrNull()?.trim()?.takeIf { it.isNotEmpty() } ?: id,
provider = provider,
available = obj.optionalBoolean("available"),
supportsVision = "image" in inputTypes,
supportsAudio = "audio" in inputTypes,
supportsDocuments = "document" in inputTypes,
@@ -2690,6 +2749,7 @@ data class GatewayModelSummary(
val id: String,
val name: String,
val provider: String,
val available: Boolean?,
val supportsVision: Boolean,
val supportsAudio: Boolean,
val supportsDocuments: Boolean,
@@ -2872,6 +2932,15 @@ private fun JsonObject?.double(key: String): Double? = (this?.get(key) as? JsonP
private fun JsonObject?.boolean(key: String): Boolean = (this?.get(key) as? JsonPrimitive)?.content?.trim() == "true"
private fun JsonObject?.optionalBoolean(key: String): Boolean? =
(this?.get(key) as? JsonPrimitive)?.content?.trim()?.lowercase()?.let { value ->
when (value) {
"true" -> true
"false" -> false
else -> null
}
}
internal fun cronJobLastRunStatus(state: JsonObject?): String? =
state
.cronStatus("lastStatus")

View File

@@ -53,6 +53,7 @@ class PermissionRequester internal constructor(
private val mutex = Mutex()
private val requestSlotsLock = Any()
private val mainHandler = Handler(Looper.getMainLooper())
// ActivityResult launchers cannot be registered after start; pre-register a small pool for nested UI flows.
private val launchers = List(4) { createPermissionRequestSlot(launcherFactory) }

View File

@@ -40,11 +40,13 @@ class SecurePrefs(
private const val notificationsForwardingMaxEventsPerMinuteKey =
"notifications.forwarding.maxEventsPerMinute"
private const val notificationsForwardingSessionKeyKey = "notifications.forwarding.sessionKey"
private const val installedAppsSharingEnabledKey = "device.apps.sharing.enabled"
private const val voiceMicEnabledKey = "voice.micEnabled"
}
private val appContext = context.applicationContext
private val json = Json { ignoreUnknownKeys = true }
// Non-secret UI/runtime preferences stay readable for migration and backup behavior.
private val plainPrefs: SharedPreferences =
appContext.getSharedPreferences(plainPrefsName, Context.MODE_PRIVATE)
@@ -114,6 +116,10 @@ class SecurePrefs(
MutableStateFlow(plainPrefs.getBoolean("canvas.debugStatusEnabled", false))
val canvasDebugStatusEnabled: StateFlow<Boolean> = _canvasDebugStatusEnabled
private val _installedAppsSharingEnabled =
MutableStateFlow(plainPrefs.getBoolean(installedAppsSharingEnabledKey, false))
val installedAppsSharingEnabled: StateFlow<Boolean> = _installedAppsSharingEnabled
private val _notificationForwardingEnabled =
MutableStateFlow(plainPrefs.getBoolean(notificationsForwardingEnabledKey, defaultNotificationForwardingEnabled))
val notificationForwardingEnabled: StateFlow<Boolean> = _notificationForwardingEnabled
@@ -252,6 +258,11 @@ class SecurePrefs(
_canvasDebugStatusEnabled.value = value
}
fun setInstalledAppsSharingEnabled(value: Boolean) {
plainPrefs.edit { putBoolean(installedAppsSharingEnabledKey, value) }
_installedAppsSharingEnabled.value = value
}
internal fun getNotificationForwardingPolicy(appPackageName: String): NotificationForwardingPolicy {
val modeRaw = plainPrefs.getString(notificationsForwardingModeKey, null)
val mode = NotificationPackageFilterMode.fromRawValue(modeRaw)

View File

@@ -61,9 +61,11 @@ class ChatController(
private val pendingRuns = mutableSetOf<String>()
private val pendingRunTimeoutJobs = ConcurrentHashMap<String, Job>()
// Preserve sent messages locally until chat.history includes the gateway-confirmed copy.
private val optimisticMessagesByRunId = LinkedHashMap<String, ChatMessage>()
private val pendingRunTimeoutMs = 120_000L
// Drops stale history responses after session switches or refresh races.
private val historyLoadGeneration = AtomicLong(0)
@@ -225,6 +227,7 @@ class ChatController(
role = "user",
content = userContent,
timestampMs = System.currentTimeMillis(),
idempotencyKey = "$runId:user",
)
optimisticMessagesByRunId[runId] = optimisticMessage
_messages.value = _messages.value + optimisticMessage
@@ -350,6 +353,7 @@ class ChatController(
)
if (!isCurrentHistoryLoad(sessionKey, _sessionKey.value, generation, historyLoadGeneration.get())) return
val history = parseHistory(historyJson, sessionKey = sessionKey, previousMessages = _messages.value)
prunePersistedOptimisticMessages(history.messages)
_messages.value = mergeOptimisticMessages(incoming = history.messages, optimistic = optimisticMessagesByRunId.values)
_sessionId.value = history.sessionId
_historyLoading.value = false
@@ -422,10 +426,8 @@ class ChatController(
}
if (runId != null) {
clearPendingRun(runId)
optimisticMessagesByRunId.remove(runId)
} else {
clearPendingRuns()
optimisticMessagesByRunId.clear()
clearPendingRuns(clearOptimisticMessages = false)
}
pendingToolCallsById.clear()
publishPendingToolCalls()
@@ -455,6 +457,7 @@ class ChatController(
sessionKey = currentSessionKey,
previousMessages = _messages.value,
)
prunePersistedOptimisticMessages(history.messages)
_messages.value = mergeOptimisticMessages(incoming = history.messages, optimistic = optimisticMessagesByRunId.values)
_sessionId.value = history.sessionId
history.thinkingLevel
@@ -561,12 +564,14 @@ class ChatController(
}
}
private fun clearPendingRuns() {
private fun clearPendingRuns(clearOptimisticMessages: Boolean = true) {
for ((_, job) in pendingRunTimeoutJobs) {
job.cancel()
}
pendingRunTimeoutJobs.clear()
optimisticMessagesByRunId.clear()
if (clearOptimisticMessages) {
optimisticMessagesByRunId.clear()
}
synchronized(pendingRuns) {
pendingRuns.clear()
_pendingRunCount.value = 0
@@ -578,6 +583,15 @@ class ChatController(
_messages.value = _messages.value.filterNot { it.id == message.id }
}
private fun prunePersistedOptimisticMessages(incoming: List<ChatMessage>) {
val retained =
retainUnmatchedOptimisticMessages(
incoming = incoming,
optimistic = optimisticMessagesByRunId.values,
).toSet()
optimisticMessagesByRunId.entries.removeAll { entry -> entry.value !in retained }
}
private fun parseHistory(
historyJson: String,
sessionKey: String,
@@ -592,13 +606,14 @@ class ChatController(
array.mapNotNull { item ->
val obj = item.asObjectOrNull() ?: return@mapNotNull null
val role = obj["role"].asStringOrNull() ?: return@mapNotNull null
val content = obj["content"].asArrayOrNull()?.mapNotNull(::parseChatMessageContent) ?: emptyList()
val content = parseChatMessageContents(obj)
val ts = obj["timestamp"].asLongOrNull()
ChatMessage(
id = UUID.randomUUID().toString(),
role = role,
content = content,
timestampMs = ts,
idempotencyKey = obj["idempotencyKey"].asStringOrNull(),
)
}
@@ -674,6 +689,19 @@ internal fun parseChatMessageContent(el: JsonElement): ChatMessageContent? {
}
}
internal fun parseChatMessageContents(obj: JsonObject): List<ChatMessageContent> {
obj["content"].asArrayOrNull()?.let { content ->
return content.mapNotNull(::parseChatMessageContent)
}
obj["content"].asStringOrNull()?.let { text ->
return listOf(ChatMessageContent(type = "text", text = text))
}
obj["text"].asStringOrNull()?.let { text ->
return listOf(ChatMessageContent(type = "text", text = text))
}
return emptyList()
}
internal data class MainSessionState(
val currentSessionKey: String,
val appliedMainSessionKey: String,
@@ -732,29 +760,41 @@ internal fun mergeOptimisticMessages(
): List<ChatMessage> {
if (optimistic.isEmpty()) return incoming
val unmatchedIncoming = incoming.toMutableList()
val missingOptimistic =
optimistic.filter { message ->
val matchIndex =
unmatchedIncoming.indexOfFirst { incomingMessage ->
incomingMessageConsumesOptimistic(incomingMessage, message)
}
if (matchIndex >= 0) {
unmatchedIncoming.removeAt(matchIndex)
false
} else {
true
}
}
val missingOptimistic = retainUnmatchedOptimisticMessages(incoming = incoming, optimistic = optimistic)
if (missingOptimistic.isEmpty()) return incoming
return (incoming + missingOptimistic).sortedWith(compareBy<ChatMessage> { it.timestampMs ?: Long.MAX_VALUE }.thenBy { it.id })
}
internal fun retainUnmatchedOptimisticMessages(
incoming: List<ChatMessage>,
optimistic: Collection<ChatMessage>,
): List<ChatMessage> {
if (optimistic.isEmpty()) return emptyList()
val unmatchedIncoming = incoming.toMutableList()
return optimistic.filter { message ->
val matchIndex =
unmatchedIncoming.indexOfFirst { incomingMessage ->
incomingMessageConsumesOptimistic(incomingMessage, message)
}
if (matchIndex >= 0) {
unmatchedIncoming.removeAt(matchIndex)
false
} else {
true
}
}
}
/**
* Message identity used only for refresh reconciliation; it avoids exposing gateway ids as UI keys.
*/
internal fun messageIdentityKey(message: ChatMessage): String? {
val idempotencyKey = message.idempotencyKey?.trim().orEmpty()
if (idempotencyKey.isNotEmpty()) {
return listOf(message.role.trim().lowercase(), idempotencyKey).joinToString(separator = "|")
}
val contentKey = messageContentIdentityKey(message) ?: return null
val timestamp = message.timestampMs?.toString().orEmpty()
if (timestamp.isEmpty() && contentKey.isEmpty()) return null
@@ -767,6 +807,10 @@ private fun incomingMessageConsumesOptimistic(
incoming: ChatMessage,
optimistic: ChatMessage,
): Boolean {
val optimisticIdempotencyKey = optimistic.idempotencyKey?.trim().orEmpty()
if (optimisticIdempotencyKey.isNotEmpty()) {
return incoming.idempotencyKey?.trim() == optimisticIdempotencyKey
}
if (optimisticMessageIdentityKey(incoming) != optimisticMessageIdentityKey(optimistic)) return false
val incomingTimestamp = incoming.timestampMs ?: return false
val optimisticTimestamp = optimistic.timestampMs ?: return true

View File

@@ -8,6 +8,7 @@ data class ChatMessage(
val role: String,
val content: List<ChatMessageContent>,
val timestampMs: Long?,
val idempotencyKey: String? = null,
)
/**

View File

@@ -66,10 +66,12 @@ class GatewayDiscovery(
private val localById = ConcurrentHashMap<String, GatewayEndpoint>()
private val unicastById = ConcurrentHashMap<String, GatewayEndpoint>()
private val _gateways = MutableStateFlow<List<GatewayEndpoint>>(emptyList())
/** Current discovered gateway list, merged from local DNS-SD and optional wide-area DNS-SD. */
val gateways: StateFlow<List<GatewayEndpoint>> = _gateways.asStateFlow()
private val _statusText = MutableStateFlow("Searching…")
/** Short diagnostic text shown by connect UI while discovery is running. */
val statusText: StateFlow<String> = _statusText.asStateFlow()

View File

@@ -77,6 +77,8 @@ data class GatewayConnectErrorDetails(
val recommendedNextStep: String?,
val pauseReconnect: Boolean? = null,
val reason: String? = null,
val requestId: String? = null,
val retryable: Boolean = false,
)
/**
@@ -120,6 +122,7 @@ class GatewaySession(
private val deviceAuthStore: DeviceAuthTokenStore,
private val onConnected: (GatewayHelloSummary) -> Unit,
private val onDisconnected: (message: String) -> Unit,
private val onConnectFailure: (error: ErrorShape, pauseReconnect: Boolean) -> Unit = { _, _ -> },
private val onEvent: (event: String, payloadJson: String?) -> Unit,
private val onInvoke: (suspend (InvokeRequest) -> InvokeResult)? = null,
private val onTlsFingerprint: ((stableId: String, fingerprint: String) -> Unit)? = null,
@@ -127,6 +130,7 @@ class GatewaySession(
private companion object {
// Keep connect timeout above observed gateway unauthorized close on lower-end devices.
private const val CONNECT_RPC_TIMEOUT_MS = 12_000L
private val PAIRING_REQUEST_ID_PATTERN = Regex("^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$")
}
/**
@@ -923,6 +927,8 @@ class GatewaySession(
recommendedNextStep = it["recommendedNextStep"].asStringOrNull(),
pauseReconnect = it["pauseReconnect"].asBooleanOrNull(),
reason = it["reason"].asStringOrNull(),
requestId = normalizePairingRequestId(it["requestId"].asStringOrNull()),
retryable = it["retryable"].asBooleanOrNull() == true,
)
}
ErrorShape(code, msg, details)
@@ -948,6 +954,11 @@ class GatewaySession(
onEvent(event, payloadJson)
}
private fun normalizePairingRequestId(requestId: String?): String? {
val trimmed = requestId?.trim()?.takeIf { it.isNotEmpty() } ?: return null
return trimmed.takeIf { PAIRING_REQUEST_ID_PATTERN.matches(it) }
}
private suspend fun awaitConnectNonce(): String =
try {
withTimeout(2_000) { connectNonceDeferred.await() }
@@ -1061,10 +1072,14 @@ class GatewaySession(
} catch (err: Throwable) {
attempt += 1
onDisconnected("Gateway error: ${err.message ?: err::class.java.simpleName}")
if (
err is GatewayConnectFailure &&
shouldPauseReconnectAfterAuthFailure(err.gatewayError)
) {
val gatewayConnectFailure = err as? GatewayConnectFailure
val pauseForAuthFailure =
gatewayConnectFailure
?.let { shouldPauseReconnectAfterAuthFailure(it.gatewayError) } == true
if (gatewayConnectFailure != null) {
onConnectFailure(gatewayConnectFailure.gatewayError, pauseForAuthFailure)
}
if (pauseForAuthFailure) {
reconnectPausedForAuthFailure = true
continue
}

View File

@@ -12,47 +12,30 @@ import kotlinx.serialization.json.JsonPrimitive
class A2UIHandler(
private val canvas: CanvasController,
private val json: Json,
private val getNodeCanvasHostUrl: () -> String?,
private val getOperatorCanvasHostUrl: () -> String?,
) {
fun isTrustedCanvasActionUrl(rawUrl: String?): Boolean =
CanvasActionTrust.isTrustedCanvasActionUrl(
rawUrl = rawUrl,
trustedA2uiUrls = listOfNotNull(resolveA2uiHostUrl()),
)
fun isTrustedCanvasActionUrl(rawUrl: String?): Boolean = CanvasActionTrust.isTrustedCanvasActionUrl(rawUrl)
fun resolveA2uiHostUrl(): String? {
val nodeRaw = getNodeCanvasHostUrl()?.trim().orEmpty()
val operatorRaw = getOperatorCanvasHostUrl()?.trim().orEmpty()
// Prefer node-advertised canvas host; operator URL is a fallback for older hello payloads.
val raw = if (nodeRaw.isNotBlank()) nodeRaw else operatorRaw
if (raw.isBlank()) return null
val base = raw.trimEnd('/')
return "$base/__openclaw__/a2ui/?platform=android"
}
suspend fun ensureA2uiReady(a2uiUrl: String): Boolean {
try {
val already = canvas.eval(a2uiReadyCheckJS)
if (already == "true") return true
} catch (_: Throwable) {
// ignore
suspend fun ensureA2uiReady(): Boolean {
if (canvas.currentUrl()?.trim() == CanvasActionTrust.localA2uiAssetUrl && isA2uiReady()) {
return true
}
canvas.navigate(a2uiUrl)
// A2UI host bootstraps asynchronously after navigation; poll briefly before failing the command.
canvas.showLocalA2ui()
// The bundled A2UI host bootstraps asynchronously after navigation; poll briefly before failing the command.
repeat(50) {
try {
val ready = canvas.eval(a2uiReadyCheckJS)
if (ready == "true") return true
} catch (_: Throwable) {
// ignore
}
if (isA2uiReady()) return true
delay(120)
}
return false
}
private suspend fun isA2uiReady(): Boolean =
try {
canvas.eval(a2uiReadyCheckJS) == "true"
} catch (_: Throwable) {
false
}
fun decodeA2uiMessages(
command: String,
paramsJson: String?,

View File

@@ -1,7 +1,5 @@
package ai.openclaw.app.node
import java.net.URI
/**
* Trust helper for WebView-originated canvas/A2UI actions.
*/
@@ -9,62 +7,15 @@ object CanvasActionTrust {
/** Local canvas scaffold is the only trusted file URL. */
const val scaffoldAssetUrl: String = "file:///android_asset/CanvasScaffold/scaffold.html"
/** Accepts local scaffold or exact remote A2UI URLs advertised by the gateway. */
fun isTrustedCanvasActionUrl(
rawUrl: String?,
trustedA2uiUrls: List<String>,
): Boolean {
/** Local bundled A2UI is the only action-capable A2UI host. */
const val localA2uiAssetUrl: String = "file:///android_asset/CanvasA2UI/index.html"
/** Accepts only app-owned bundled pages. Remote WebView content is render-only. */
fun isTrustedCanvasActionUrl(rawUrl: String?): Boolean {
val candidate = rawUrl?.trim().orEmpty()
if (candidate.isEmpty()) return false
if (candidate == scaffoldAssetUrl) return true
val candidateUri = parseUri(candidate) ?: return false
if (candidateUri.scheme.equals("file", ignoreCase = true)) {
return false
}
val normalizedCandidate = normalizeTrustedRemoteA2uiUri(candidateUri) ?: return false
return trustedA2uiUrls.any { trusted ->
matchesTrustedRemoteA2uiUrlExact(normalizedCandidate, trusted)
}
if (candidate == localA2uiAssetUrl) return true
return false
}
private fun matchesTrustedRemoteA2uiUrlExact(
candidateUri: URI,
trustedUrl: String,
): Boolean {
// Gateway-advertised URLs are capabilities. Treat malformed entries as
// absent instead of broadening trust to same-origin or prefix matches.
val trustedUri = parseUri(trustedUrl) ?: return false
val normalizedTrusted = normalizeTrustedRemoteA2uiUri(trustedUri) ?: return false
return candidateUri == normalizedTrusted
}
/** Normalizes only the URL parts allowed to vary across trusted remote A2UI URLs. */
private fun normalizeTrustedRemoteA2uiUri(uri: URI): URI? {
// Keep Android trust normalization aligned with iOS ScreenController:
// exact remote URL match, scheme/host normalized, fragment ignored.
val scheme = uri.scheme?.lowercase() ?: return null
if (scheme != "http" && scheme != "https") return null
val host =
uri.host
?.trim()
?.takeIf { it.isNotEmpty() }
?.lowercase() ?: return null
return try {
URI(scheme, uri.userInfo, host, uri.port, uri.rawPath, uri.rawQuery, null)
} catch (_: Throwable) {
null
}
}
/** Parses untrusted WebView/gateway URL text without throwing into UI event handlers. */
private fun parseUri(raw: String): URI? =
try {
URI(raw)
} catch (_: Throwable) {
null
}
}

View File

@@ -48,7 +48,8 @@ class CanvasController {
private val _currentUrl = MutableStateFlow<String?>(null)
val currentUrl: StateFlow<String?> = _currentUrl.asStateFlow()
private val scaffoldAssetUrl = "file:///android_asset/CanvasScaffold/scaffold.html"
private val scaffoldAssetUrl = CanvasActionTrust.scaffoldAssetUrl
private val localA2uiAssetUrl = CanvasActionTrust.localA2uiAssetUrl
private fun clampJpegQuality(quality: Double?): Int {
val q = (quality ?: 0.82).coerceIn(0.1, 1.0)
@@ -87,6 +88,13 @@ class CanvasController {
reload()
}
/** Shows the app-owned A2UI renderer that is allowed to dispatch native actions. */
fun showLocalA2ui() {
this.url = localA2uiAssetUrl
_currentUrl.value = localA2uiAssetUrl
reload()
}
fun currentUrl(): String? = url
fun isDefaultCanvas(): Boolean = url == null

View File

@@ -28,6 +28,7 @@ class ConnectionManager(
private val callLogAvailable: () -> Boolean,
private val photosAvailable: () -> Boolean,
private val hasRecordAudioPermission: () -> Boolean,
private val installedAppsSharingEnabled: () -> Boolean,
private val manualTls: () -> Boolean,
) {
companion object {
@@ -115,6 +116,7 @@ class ConnectionManager(
voiceWakeEnabled = voiceWakeMode() != VoiceWakeMode.Off && hasRecordAudioPermission(),
motionActivityAvailable = motionActivityAvailable(),
motionPedometerAvailable = motionPedometerAvailable(),
installedAppsSharingEnabled = installedAppsSharingEnabled(),
debugBuild = BuildConfig.DEBUG,
)

View File

@@ -8,6 +8,7 @@ import android.app.ActivityManager
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
@@ -24,16 +25,120 @@ import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.json.put
import java.util.Locale
private const val DEFAULT_DEVICE_APPS_LIMIT = 100
private const val MAX_DEVICE_APPS_LIMIT = 200
private const val DEVICE_APPS_SYSTEM_FLAGS =
ApplicationInfo.FLAG_SYSTEM or ApplicationInfo.FLAG_UPDATED_SYSTEM_APP
internal fun isSystemDeviceApp(appInfo: ApplicationInfo): Boolean = (appInfo.flags and DEVICE_APPS_SYSTEM_FLAGS) != 0
internal data class DeviceAppEntry(
val label: String,
val packageName: String,
val system: Boolean,
val enabled: Boolean,
val launchable: Boolean,
)
internal interface DeviceAppSource {
fun listApps(includeNonLaunchable: Boolean): List<DeviceAppEntry>
}
private class AndroidDeviceAppSource(
private val appContext: Context,
) : DeviceAppSource {
override fun listApps(includeNonLaunchable: Boolean): List<DeviceAppEntry> {
val packageManager = appContext.packageManager
val launcherIntent = Intent(Intent.ACTION_MAIN).apply { addCategory(Intent.CATEGORY_LAUNCHER) }
val launchablePackages =
packageManager
.queryIntentActivities(launcherIntent, PackageManager.MATCH_ALL)
.asSequence()
.mapNotNull {
it.activityInfo
?.packageName
?.trim()
?.takeIf(String::isNotEmpty)
}.toSet()
val appInfos =
if (includeNonLaunchable) {
packageManager.getInstalledApplications(PackageManager.MATCH_ALL)
} else {
launchablePackages.mapNotNull { packageName ->
runCatching { packageManager.getApplicationInfo(packageName, 0) }.getOrNull()
}
}
return appInfos
.asSequence()
.mapNotNull { appInfo ->
appInfo.packageName
?.trim()
?.takeIf(String::isNotEmpty)
?.let { packageName ->
val label = packageManager.getApplicationLabel(appInfo).toString().trim()
DeviceAppEntry(
label = label.ifEmpty { packageName },
packageName = packageName,
system = isSystemDeviceApp(appInfo),
enabled = appInfo.enabled,
launchable = packageName in launchablePackages,
)
}
}.distinctBy { it.packageName }
.sortedWith(compareBy<DeviceAppEntry> { it.label.lowercase() }.thenBy { it.packageName })
.toList()
}
}
private data class DeviceAppsRequest(
val includeSystem: Boolean,
val includeDisabled: Boolean,
val includeNonLaunchable: Boolean,
val query: String?,
val limit: Int,
)
/**
* Gateway device command adapter for Android status, info, permission, and health snapshots.
*/
class DeviceHandler(
class DeviceHandler private constructor(
private val appContext: Context,
private val smsEnabled: Boolean = SensitiveFeatureConfig.smsEnabled,
private val callLogEnabled: Boolean = SensitiveFeatureConfig.callLogEnabled,
private val photosEnabled: Boolean = SensitiveFeatureConfig.photosEnabled,
private val appSource: DeviceAppSource = AndroidDeviceAppSource(appContext),
) {
constructor(
appContext: Context,
smsEnabled: Boolean = SensitiveFeatureConfig.smsEnabled,
callLogEnabled: Boolean = SensitiveFeatureConfig.callLogEnabled,
photosEnabled: Boolean = SensitiveFeatureConfig.photosEnabled,
) : this(
appContext = appContext,
smsEnabled = smsEnabled,
callLogEnabled = callLogEnabled,
photosEnabled = photosEnabled,
appSource = AndroidDeviceAppSource(appContext),
)
companion object {
internal fun forTesting(
appContext: Context,
appSource: DeviceAppSource,
smsEnabled: Boolean = SensitiveFeatureConfig.smsEnabled,
callLogEnabled: Boolean = SensitiveFeatureConfig.callLogEnabled,
photosEnabled: Boolean = SensitiveFeatureConfig.photosEnabled,
): DeviceHandler =
DeviceHandler(
appContext = appContext,
smsEnabled = smsEnabled,
callLogEnabled = callLogEnabled,
photosEnabled = photosEnabled,
appSource = appSource,
)
/**
* SMS is available only when the feature flag, telephony hardware, and at least one SMS permission align.
*/
@@ -74,6 +179,48 @@ class DeviceHandler(
/** Returns coarse device health for memory, power, thermal, battery, and security patch state. */
fun handleDeviceHealth(_paramsJson: String?): GatewaySession.InvokeResult = GatewaySession.InvokeResult.ok(healthPayloadJson())
fun handleDeviceApps(paramsJson: String?): GatewaySession.InvokeResult {
val request = parseDeviceAppsRequest(paramsJson)
val matchingApps =
appSource
.listApps(includeNonLaunchable = request.includeNonLaunchable)
.asSequence()
.filter { request.includeSystem || !it.system }
.filter { request.includeDisabled || it.enabled }
.filter { app ->
val query = request.query ?: return@filter true
app.label.contains(query, ignoreCase = true) || app.packageName.contains(query, ignoreCase = true)
}.toList()
val limitedApps = matchingApps.take(request.limit)
return GatewaySession.InvokeResult.ok(
buildJsonObject {
put("count", JsonPrimitive(limitedApps.size))
put("totalMatched", JsonPrimitive(matchingApps.size))
put("truncated", JsonPrimitive(matchingApps.size > limitedApps.size))
put("visibility", JsonPrimitive(if (request.includeNonLaunchable) "android-visible" else "launcher"))
put("includeSystem", JsonPrimitive(request.includeSystem))
put("includeDisabled", JsonPrimitive(request.includeDisabled))
put(
"apps",
buildJsonArray {
for (app in limitedApps) {
add(
buildJsonObject {
put("label", JsonPrimitive(app.label))
put("packageName", JsonPrimitive(app.packageName))
put("system", JsonPrimitive(app.system))
put("enabled", JsonPrimitive(app.enabled))
put("launchable", JsonPrimitive(app.launchable))
},
)
}
},
)
}.toString(),
)
}
private fun statusPayloadJson(): String {
val battery = readBatterySnapshot()
val powerManager = appContext.getSystemService(PowerManager::class.java)
@@ -365,6 +512,24 @@ class DeviceHandler(
}.toString()
}
private fun parseDeviceAppsRequest(paramsJson: String?): DeviceAppsRequest {
val params = parseJsonParamsObject(paramsJson)
val includeSystem = parseJsonBooleanFlag(params, "includeSystem") ?: false
val includeDisabled = parseJsonBooleanFlag(params, "includeDisabled") ?: false
val includeNonLaunchable = parseJsonBooleanFlag(params, "includeNonLaunchable") ?: false
val query = parseJsonString(params, "query")?.trim()?.takeIf { it.isNotEmpty() }
val limit =
(parseJsonInt(params, "limit") ?: DEFAULT_DEVICE_APPS_LIMIT)
.coerceIn(1, MAX_DEVICE_APPS_LIMIT)
return DeviceAppsRequest(
includeSystem = includeSystem,
includeDisabled = includeDisabled,
includeNonLaunchable = includeNonLaunchable,
query = query,
limit = limit,
)
}
private fun readBatterySnapshot(): BatterySnapshot {
// ACTION_BATTERY_CHANGED is sticky; registerReceiver(null, ...) reads the last system snapshot.
val intent = appContext.registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))

View File

@@ -28,6 +28,7 @@ data class NodeRuntimeFlags(
val voiceWakeEnabled: Boolean,
val motionActivityAvailable: Boolean,
val motionPedometerAvailable: Boolean,
val installedAppsSharingEnabled: Boolean,
val debugBuild: Boolean,
)
@@ -43,6 +44,7 @@ enum class InvokeCommandAvailability {
PhotosAvailable,
MotionActivityAvailable,
MotionPedometerAvailable,
InstalledAppsSharingEnabled,
DebugBuild,
}
@@ -193,6 +195,10 @@ object InvokeCommandRegistry {
InvokeCommandSpec(
name = OpenClawDeviceCommand.Health.rawValue,
),
InvokeCommandSpec(
name = OpenClawDeviceCommand.Apps.rawValue,
availability = InvokeCommandAvailability.InstalledAppsSharingEnabled,
),
InvokeCommandSpec(
name = OpenClawNotificationsCommand.List.rawValue,
),
@@ -281,6 +287,7 @@ object InvokeCommandRegistry {
InvokeCommandAvailability.PhotosAvailable -> flags.photosAvailable
InvokeCommandAvailability.MotionActivityAvailable -> flags.motionActivityAvailable
InvokeCommandAvailability.MotionPedometerAvailable -> flags.motionPedometerAvailable
InvokeCommandAvailability.InstalledAppsSharingEnabled -> flags.installedAppsSharingEnabled
InvokeCommandAvailability.DebugBuild -> flags.debugBuild
}
}.map { it.name }

View File

@@ -85,10 +85,10 @@ class InvokeDispatcher(
private val smsTelephonyAvailable: () -> Boolean,
private val callLogAvailable: () -> Boolean,
private val photosAvailable: () -> Boolean,
private val installedAppsSharingEnabled: () -> Boolean,
private val debugBuild: () -> Boolean,
private val onCanvasA2uiPush: () -> Unit,
private val onCanvasA2uiReset: () -> Unit,
private val refreshCanvasHostUrl: suspend () -> String?,
private val motionActivityAvailable: () -> Boolean,
private val motionPedometerAvailable: () -> Boolean,
) {
@@ -193,6 +193,7 @@ class InvokeDispatcher(
OpenClawDeviceCommand.Info.rawValue -> deviceHandler.handleDeviceInfo(paramsJson)
OpenClawDeviceCommand.Permissions.rawValue -> deviceHandler.handleDevicePermissions(paramsJson)
OpenClawDeviceCommand.Health.rawValue -> deviceHandler.handleDeviceHealth(paramsJson)
OpenClawDeviceCommand.Apps.rawValue -> deviceHandler.handleDeviceApps(paramsJson)
// Notifications command
OpenClawNotificationsCommand.List.rawValue -> notificationsHandler.handleNotificationsList(paramsJson)
@@ -240,24 +241,11 @@ class InvokeDispatcher(
}
private suspend fun withReadyA2ui(block: suspend () -> GatewaySession.InvokeResult): GatewaySession.InvokeResult {
var a2uiUrl =
a2uiHandler.resolveA2uiHostUrl()
?: refreshCanvasHostUrl().let { a2uiHandler.resolveA2uiHostUrl() }
?: return GatewaySession.InvokeResult.error(
code = "A2UI_HOST_NOT_CONFIGURED",
message = "A2UI_HOST_NOT_CONFIGURED: gateway did not advertise canvas host",
)
val readyOnFirstCheck = a2uiHandler.ensureA2uiReady(a2uiUrl)
if (!readyOnFirstCheck) {
// Gateway canvas host metadata can lag reconnects; refresh once before failing the command.
refreshCanvasHostUrl()
a2uiUrl = a2uiHandler.resolveA2uiHostUrl() ?: a2uiUrl
if (!a2uiHandler.ensureA2uiReady(a2uiUrl)) {
return GatewaySession.InvokeResult.error(
code = "A2UI_HOST_UNAVAILABLE",
message = "A2UI_HOST_UNAVAILABLE: A2UI host not reachable",
)
}
if (!a2uiHandler.ensureA2uiReady()) {
return GatewaySession.InvokeResult.error(
code = "A2UI_HOST_UNAVAILABLE",
message = "A2UI_HOST_UNAVAILABLE: bundled A2UI host not reachable",
)
}
return block()
}
@@ -348,6 +336,15 @@ class InvokeDispatcher(
message = "PHOTOS_UNAVAILABLE: photos not available on this build",
)
}
InvokeCommandAvailability.InstalledAppsSharingEnabled ->
if (installedAppsSharingEnabled()) {
null
} else {
GatewaySession.InvokeResult.error(
code = "INSTALLED_APPS_SHARING_DISABLED",
message = "INSTALLED_APPS_SHARING_DISABLED: enable Installed Apps in Settings",
)
}
InvokeCommandAvailability.DebugBuild ->
if (debugBuild()) {
null

View File

@@ -112,6 +112,7 @@ enum class OpenClawDeviceCommand(
Info("device.info"),
Permissions("device.permissions"),
Health("device.health"),
Apps("device.apps"),
;
companion object {

View File

@@ -152,9 +152,8 @@ fun CanvasScreen(
}
}
// The listener accepts any WebView origin at registration time because
// gateway A2UI URLs are dynamic; CanvasActionTrust validates the live URL
// before forwarding each message.
// The listener accepts any WebView origin at registration time; native
// dispatch still requires the live URL to be an app-owned bundled page.
val bridge =
CanvasA2UIActionBridge(
isTrustedPage = { viewModel.isTrustedCanvasActionUrl(currentPageUrlRef.get()) },

View File

@@ -297,17 +297,15 @@ private fun CommandSectionLabel(title: String) {
}
}
/** Builds provider quick-action metadata from current gateway/catalog state. */
private fun providerCommandSubtitle(
internal fun providerCommandSubtitle(
isConnected: Boolean,
providers: List<GatewayModelProviderSummary>,
models: List<GatewayModelSummary>,
): String {
if (!isConnected) return "Connect Gateway to load models"
val readyProviderCount = providers.count { modelProviderReady(it.status) }
if (!isConnected) return "Connect Gateway to view providers"
val readyProviderCount = providerRows(providers = providers, models = models).count { it.ready }
if (readyProviderCount > 0) return "$readyProviderCount providers ready"
if (models.isNotEmpty()) return "${models.size} models available"
return "Configure model access"
return "No ready providers"
}
/** Falls back to the canonical main-session label when gateway display names are blank. */

View File

@@ -1,7 +1,7 @@
package ai.openclaw.app.ui
import ai.openclaw.app.GatewayConnectionProblem
import ai.openclaw.app.MainViewModel
import ai.openclaw.app.gateway.GatewayEndpoint
import ai.openclaw.app.ui.mobileCardSurface
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.BorderStroke
@@ -66,6 +66,7 @@ private enum class ConnectInputMode {
fun ConnectTabScreen(viewModel: MainViewModel) {
val context = LocalContext.current
val statusText by viewModel.statusText.collectAsState()
val gatewayConnectionProblem by viewModel.gatewayConnectionProblem.collectAsState()
val isConnected by viewModel.isConnected.collectAsState()
val remoteAddress by viewModel.remoteAddress.collectAsState()
val manualHost by viewModel.manualHost.collectAsState()
@@ -147,13 +148,10 @@ fun ConnectTabScreen(viewModel: MainViewModel) {
}
}
val showDiagnostics = !isConnected && gatewayStatusHasDiagnostics(statusText)
val pairingRequired = !isConnected && gatewayStatusLooksLikePairing(statusText)
val statusLabel = gatewayStatusForDisplay(statusText)
PairingAutoRetryEffect(enabled = pairingRequired) {
viewModel.refreshGatewayConnection()
}
val showDiagnostics = !isConnected && (gatewayConnectionProblem != null || gatewayStatusHasDiagnostics(statusText))
val pairingRequired = !isConnected && (gatewayConnectionProblem?.isPairingRequired == true || gatewayStatusLooksLikePairing(statusText))
val pairingInstruction = gatewayPairingInstruction(gatewayConnectionProblem)
val statusLabel = gatewayStatusForDisplay(gatewayConnectionProblem?.message ?: statusText)
Column(
modifier = Modifier.verticalScroll(rememberScrollState()).padding(horizontal = 20.dp, vertical = 16.dp),
@@ -291,27 +289,14 @@ fun ConnectTabScreen(viewModel: MainViewModel) {
}
validationText = null
if (inputMode == ConnectInputMode.SetupCode) {
// Setup-code auth should replace old bootstrap/shared credentials;
// manual reconnects keep existing typed credentials.
viewModel.resetGatewaySetupAuth()
}
viewModel.setManualEnabled(true)
viewModel.setManualHost(config.host)
viewModel.setManualPort(config.port)
viewModel.setManualTls(config.tls)
viewModel.setGatewayBootstrapToken(config.bootstrapToken)
if (config.token.isNotBlank()) {
viewModel.setGatewayToken(config.token)
} else if (config.bootstrapToken.isNotBlank()) {
viewModel.setGatewayToken("")
}
viewModel.setGatewayPassword(config.password)
viewModel.connect(
GatewayEndpoint.manual(host = config.host, port = config.port),
token = config.token.ifEmpty { null },
bootstrapToken = config.bootstrapToken.ifEmpty { null },
password = config.password.ifEmpty { null },
viewModel.saveGatewayConfigAndConnect(
host = config.host,
port = config.port,
tls = config.tls,
token = config.token,
bootstrapToken = config.bootstrapToken,
password = config.password,
resetSetupAuth = inputMode == ConnectInputMode.SetupCode,
)
},
modifier = Modifier.fillMaxWidth().height(52.dp),
@@ -341,7 +326,7 @@ fun ConnectTabScreen(viewModel: MainViewModel) {
Text(statusLabel, style = mobileBody.copy(fontFamily = FontFamily.Monospace), color = mobileText)
if (pairingRequired) {
Text(
"Approve this phone on the gateway. OpenClaw retries automatically while this screen stays open.",
pairingInstruction,
style = mobileCallout,
color = mobileTextSecondary,
)
@@ -590,6 +575,13 @@ fun ConnectTabScreen(viewModel: MainViewModel) {
}
}
private fun gatewayPairingInstruction(problem: GatewayConnectionProblem?): String =
if (problem?.canAutoRetry == true) {
"Approve this phone on the gateway. OpenClaw will reconnect automatically."
} else {
"Approve this phone on the gateway, then retry the connection."
}
@Composable
private fun MethodChip(
label: String,

View File

@@ -1,53 +0,0 @@
package ai.openclaw.app.ui
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.compose.LocalLifecycleOwner
import kotlinx.coroutines.delay
internal const val PAIRING_INITIAL_AUTO_RETRY_MS = 1_500L
internal const val PAIRING_AUTO_RETRY_MS = 4_000L
/** Retries pairing-only gateway refreshes while the screen is visible and started. */
@Composable
internal fun PairingAutoRetryEffect(
enabled: Boolean,
onRetry: () -> Unit,
) {
val lifecycleOwner = LocalLifecycleOwner.current
var lifecycleStarted by
remember(lifecycleOwner) {
mutableStateOf(lifecycleOwner.lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED))
}
DisposableEffect(lifecycleOwner) {
val observer =
LifecycleEventObserver { _, _ ->
lifecycleStarted = lifecycleOwner.lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)
}
lifecycleOwner.lifecycle.addObserver(observer)
onDispose {
lifecycleOwner.lifecycle.removeObserver(observer)
}
}
LaunchedEffect(enabled, lifecycleStarted) {
if (!enabled || !lifecycleStarted) {
return@LaunchedEffect
}
// Give the gateway a short settling window before the first retry so an
// approval response is not immediately chased by a redundant reconnect.
delay(PAIRING_INITIAL_AUTO_RETRY_MS)
while (true) {
onRetry()
delay(PAIRING_AUTO_RETRY_MS)
}
}
}

View File

@@ -0,0 +1,82 @@
package ai.openclaw.app.ui
import ai.openclaw.app.node.DeviceNotificationListenerService
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
/** App entry shown in the notification-forwarding package picker. */
data class InstalledApp(
val label: String,
val packageName: String,
val isSystemApp: Boolean,
)
/** Reads launcher, recent-notification, and configured packages for the picker. */
internal fun queryInstalledApps(
context: Context,
configuredPackages: Set<String>,
): List<InstalledApp> {
val packageManager = context.packageManager
val launcherIntent = Intent(Intent.ACTION_MAIN).apply { addCategory(Intent.CATEGORY_LAUNCHER) }
val launcherPackages =
packageManager
.queryIntentActivities(launcherIntent, PackageManager.MATCH_ALL)
.asSequence()
.mapNotNull {
it.activityInfo
?.packageName
?.trim()
?.takeIf(String::isNotEmpty)
}.toMutableSet()
val recentNotificationPackages =
DeviceNotificationListenerService
.recentPackages(context)
.asSequence()
.map { it.trim() }
.filter { it.isNotEmpty() }
.toList()
val candidatePackages =
resolveNotificationCandidatePackages(
launcherPackages = launcherPackages,
recentPackages = recentNotificationPackages,
configuredPackages = configuredPackages,
appPackageName = context.packageName,
)
return candidatePackages
.asSequence()
.mapNotNull { packageName ->
runCatching {
val appInfo = packageManager.getApplicationInfo(packageName, 0)
val label = packageManager.getApplicationLabel(appInfo).toString().trim()
InstalledApp(
label = if (label.isEmpty()) packageName else label,
packageName = packageName,
isSystemApp = (appInfo.flags and android.content.pm.ApplicationInfo.FLAG_SYSTEM) != 0,
)
}.getOrNull()
}.sortedWith(compareBy<InstalledApp> { it.label.lowercase() }.thenBy { it.packageName })
.toList()
}
/** Merges package sources while excluding OpenClaw from its own forwarding filter. */
internal fun resolveNotificationCandidatePackages(
launcherPackages: Set<String>,
recentPackages: List<String>,
configuredPackages: Set<String>,
appPackageName: String,
): Set<String> {
val blockedPackage = appPackageName.trim()
return sequenceOf(
configuredPackages.asSequence(),
launcherPackages.asSequence(),
recentPackages.asSequence(),
).flatten()
.map { it.trim() }
.filter { it.isNotEmpty() && it != blockedPackage }
.toSet()
}

View File

@@ -1,11 +1,13 @@
package ai.openclaw.app.ui
import ai.openclaw.app.GatewayConnectionProblem
import ai.openclaw.app.LocationMode
import ai.openclaw.app.MainViewModel
import ai.openclaw.app.R
import ai.openclaw.app.SensitiveFeatureConfig
import ai.openclaw.app.gateway.GatewayEndpoint
import ai.openclaw.app.node.DeviceNotificationListenerService
import ai.openclaw.app.ui.design.ClawDesignTheme
import ai.openclaw.app.ui.design.ClawErrorState
import ai.openclaw.app.ui.design.ClawListItem
import ai.openclaw.app.ui.design.ClawPanel
import ai.openclaw.app.ui.design.ClawPrimaryButton
@@ -30,24 +32,30 @@ import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
@@ -87,10 +95,13 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
@@ -122,11 +133,14 @@ fun OnboardingFlow(
ClawDesignTheme {
val context = LocalContext.current
val statusText by viewModel.statusText.collectAsState()
val gatewayConnectionProblem by viewModel.gatewayConnectionProblem.collectAsState()
val isConnected by viewModel.isConnected.collectAsState()
val isNodeConnected by viewModel.isNodeConnected.collectAsState()
val runtimeInitialized by viewModel.runtimeInitialized.collectAsState()
val serverName by viewModel.serverName.collectAsState()
val remoteAddress by viewModel.remoteAddress.collectAsState()
val gateways by viewModel.gateways.collectAsState()
val discoveryStatusText by viewModel.discoveryStatusText.collectAsState()
val savedToken by viewModel.gatewayToken.collectAsState()
val pendingTrust by viewModel.pendingGatewayTrust.collectAsState()
val startAtGatewaySetup by viewModel.startOnboardingAtGatewaySetup.collectAsState()
@@ -141,6 +155,7 @@ fun OnboardingFlow(
var password by rememberSaveable { mutableStateOf("") }
var setupError by rememberSaveable { mutableStateOf<String?>(null) }
var attemptedConnect by rememberSaveable { mutableStateOf(false) }
var attemptedGatewayName by rememberSaveable { mutableStateOf<String?>(null) }
var connectAttemptStartedAtMs by rememberSaveable { mutableLongStateOf(0L) }
var recoveryNowMs by remember { mutableLongStateOf(SystemClock.elapsedRealtime()) }
@@ -162,6 +177,12 @@ fun OnboardingFlow(
}
}
LaunchedEffect(step) {
if (step == OnboardingStep.Gateway) {
viewModel.startGatewayDiscovery()
}
}
LaunchedEffect(ready, attemptedConnect) {
if (attemptedConnect && ready) {
step = OnboardingStep.Permissions
@@ -216,6 +237,8 @@ fun OnboardingFlow(
token = token,
password = password,
nearbyGatewayName = gateways.firstOrNull()?.name,
discoveryStatusText = discoveryStatusText,
discoveryStarted = runtimeInitialized,
error = setupError,
onBack = { step = OnboardingStep.Welcome },
onScan = {
@@ -252,8 +275,10 @@ fun OnboardingFlow(
onPasswordChange = { password = it },
onUseNearby = {
val endpoint = gateways.firstOrNull() ?: return@GatewaySetupScreen
attemptedGatewayName = endpoint.name
attemptedConnect = true
viewModel.connect(endpoint)
connectAttemptStartedAtMs = SystemClock.elapsedRealtime()
viewModel.connectInBackground(endpoint)
step = OnboardingStep.Recovery
},
onPair = {
@@ -272,23 +297,17 @@ fun OnboardingFlow(
}
setupError = null
attemptedGatewayName = null
attemptedConnect = true
connectAttemptStartedAtMs = SystemClock.elapsedRealtime()
// Setup-code pairing replaces any stale shared credentials before
// the bootstrap token is stored for the first authenticated connect.
viewModel.resetGatewaySetupAuth()
viewModel.setManualEnabled(true)
viewModel.setManualHost(config.host)
viewModel.setManualPort(config.port)
viewModel.setManualTls(config.tls)
viewModel.setGatewayBootstrapToken(config.bootstrapToken)
viewModel.setGatewayToken(config.token)
viewModel.setGatewayPassword(config.password)
viewModel.connect(
GatewayEndpoint.manual(host = config.host, port = config.port),
token = config.token.ifEmpty { null },
bootstrapToken = config.bootstrapToken.ifEmpty { null },
password = config.password.ifEmpty { null },
viewModel.saveGatewayConfigAndConnect(
host = config.host,
port = config.port,
tls = config.tls,
token = config.token,
bootstrapToken = config.bootstrapToken,
password = config.password,
resetSetupAuth = true,
)
step = OnboardingStep.Recovery
},
@@ -298,11 +317,11 @@ fun OnboardingFlow(
modifier = modifier,
statusText = statusText,
serverName = serverName,
attemptedGatewayName = attemptedGatewayName,
remoteAddress = remoteAddress,
ready = ready,
attemptedConnect = attemptedConnect,
gatewayConnectionProblem = gatewayConnectionProblem,
connectSettling = recoveryNowMs - connectAttemptStartedAtMs < GATEWAY_CONNECT_SETTLING_MS,
onAutoRetry = viewModel::refreshGatewayConnection,
onBack = { step = OnboardingStep.Gateway },
onRetry = {
attemptedConnect = true
@@ -316,11 +335,14 @@ fun OnboardingFlow(
token = token,
password = password,
) ?: return@GatewayRecoveryScreen
viewModel.connect(
GatewayEndpoint.manual(host = config.host, port = config.port),
token = config.token.ifEmpty { null },
bootstrapToken = config.bootstrapToken.ifEmpty { null },
password = config.password.ifEmpty { null },
viewModel.saveGatewayConfigAndConnect(
host = config.host,
port = config.port,
tls = config.tls,
token = config.token,
bootstrapToken = config.bootstrapToken,
password = config.password,
resetSetupAuth = false,
)
},
onEdit = { step = OnboardingStep.Gateway },
@@ -345,20 +367,39 @@ private fun WelcomeScreen(
onConnect: () -> Unit,
modifier: Modifier = Modifier,
) {
ClawScaffold(modifier = modifier, contentPadding = PaddingValues(horizontal = 24.dp, vertical = 18.dp)) {
val welcomeBackground =
Brush.verticalGradient(
colors =
listOf(
Color(0xFFFF4D4D),
Color(0xFFD73332),
Color(0xFF991B1B),
Color(0xFF260707),
),
)
Box(
modifier =
modifier
.fillMaxSize()
.background(welcomeBackground)
.windowInsetsPadding(WindowInsets.safeDrawing)
.padding(horizontal = 24.dp, vertical = 18.dp),
) {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Spacer(modifier = Modifier.height(96.dp))
Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(18.dp)) {
WelcomeLogo()
Text(
text = "OPENCLAW",
style = ClawTheme.type.display.copy(fontSize = 34.sp, lineHeight = 38.sp, fontWeight = FontWeight.Black),
color = ClawTheme.colors.text,
)
Text(
text = "Your AI command center.\nPrivate. Local. Under your control.",
text = "Your personal AI assistant.\nExfoliate! Exfoliate!",
style = ClawTheme.type.section,
color = ClawTheme.colors.text,
textAlign = TextAlign.Center,
@@ -369,19 +410,26 @@ private fun WelcomeScreen(
Spacer(modifier = Modifier.height(30.dp))
Column(modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(10.dp)) {
HeroPrimaryAction(title = "Connect Gateway", onClick = onConnect)
OutlinedAction(title = "Enter setup code", icon = Icons.AutoMirrored.Filled.KeyboardArrowRight, onClick = onConnect)
Surface(onClick = onConnect, color = Color.Transparent, contentColor = ClawTheme.colors.text) {
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
Text(text = "Already have a setup? ", style = ClawTheme.type.body, color = ClawTheme.colors.textMuted)
Text(text = "Sign in", style = ClawTheme.type.body.copy(fontWeight = FontWeight.SemiBold), color = ClawTheme.colors.text)
}
}
}
Spacer(modifier = Modifier.height(104.dp))
}
}
}
@Composable
private fun WelcomeLogo() {
Surface(
modifier = Modifier.size(82.dp),
shape = CircleShape,
color = Color.White.copy(alpha = 0.92f),
contentColor = Color.Unspecified,
) {
Box(modifier = Modifier.fillMaxSize().padding(12.dp), contentAlignment = Alignment.Center) {
Image(painter = painterResource(id = R.drawable.openclaw_logo), contentDescription = "OpenClaw logo", modifier = Modifier.fillMaxSize())
}
}
}
@Composable
private fun WelcomeHorizon() {
Canvas(modifier = Modifier.fillMaxWidth().height(120.dp)) {
@@ -427,6 +475,8 @@ private fun GatewaySetupScreen(
token: String,
password: String,
nearbyGatewayName: String?,
discoveryStatusText: String,
discoveryStarted: Boolean,
error: String?,
onBack: () -> Unit,
onScan: () -> Unit,
@@ -441,6 +491,29 @@ private fun GatewaySetupScreen(
modifier: Modifier = Modifier,
) {
var advancedOpen by rememberSaveable { mutableStateOf(false) }
var nearbySearchTimedOut by remember { mutableStateOf(false) }
LaunchedEffect(nearbyGatewayName, discoveryStatusText, discoveryStarted) {
if (!nearbyGatewayName.isNullOrBlank()) {
nearbySearchTimedOut = false
return@LaunchedEffect
}
if (!discoveryStarted) {
nearbySearchTimedOut = false
return@LaunchedEffect
}
nearbySearchTimedOut = false
delay(5_000)
nearbySearchTimedOut = true
}
val nearbyGateway =
nearbyGatewayUiState(
nearbyGatewayName = nearbyGatewayName,
discoveryStatusText = discoveryStatusText,
discoveryStarted = discoveryStarted,
searchTimedOut = nearbySearchTimedOut,
)
ClawScaffold(modifier = modifier, contentPadding = PaddingValues(horizontal = 18.dp, vertical = 16.dp)) {
Column(modifier = Modifier.fillMaxSize().imePadding(), verticalArrangement = Arrangement.SpaceBetween) {
@@ -460,9 +533,9 @@ private fun GatewaySetupScreen(
GatewayOption(
icon = Icons.Default.WifiTethering,
title = "Nearby gateway",
subtitle = nearbyGatewayName ?: "Discovery ready",
status = nearbyGatewayName?.let { "Found" },
onClick = onUseNearby,
subtitle = nearbyGateway.subtitle,
status = nearbyGateway.status,
onClick = onUseNearby.takeIf { nearbyGateway.canConnect },
)
}
item {
@@ -473,6 +546,14 @@ private fun GatewaySetupScreen(
onClick = { advancedOpen = true },
)
}
error?.let { message ->
item {
ClawErrorState(
title = "Setup code issue",
body = message,
)
}
}
item {
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
Surface(
@@ -505,9 +586,6 @@ private fun GatewaySetupScreen(
}
ClawTextField(value = token, onValueChange = onTokenChange, placeholder = "Token optional")
ClawTextField(value = password, onValueChange = onPasswordChange, placeholder = "Password optional")
error?.let {
Text(text = it, style = ClawTheme.type.caption, color = ClawTheme.colors.warning)
}
}
}
}
@@ -521,20 +599,19 @@ private fun GatewaySetupScreen(
private fun GatewayRecoveryScreen(
statusText: String,
serverName: String?,
attemptedGatewayName: String?,
remoteAddress: String?,
ready: Boolean,
attemptedConnect: Boolean,
gatewayConnectionProblem: GatewayConnectionProblem?,
connectSettling: Boolean,
onAutoRetry: () -> Unit,
onBack: () -> Unit,
onRetry: () -> Unit,
onEdit: () -> Unit,
onContinue: () -> Unit,
modifier: Modifier = Modifier,
) {
val recoveryState = gatewayRecoveryUiState(ready = ready, statusText = statusText, connectSettling = connectSettling)
val recoveryState = gatewayRecoveryUiState(ready = ready, statusText = statusText, connectSettling = connectSettling, gatewayConnectionProblem = gatewayConnectionProblem)
val context = LocalContext.current
PairingAutoRetryEffect(enabled = recoveryState.canAutoRetry && attemptedConnect, onRetry = onAutoRetry)
ClawScaffold(modifier = modifier, contentPadding = PaddingValues(horizontal = 18.dp, vertical = 16.dp)) {
Column(modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.spacedBy(18.dp)) {
@@ -545,6 +622,7 @@ private fun GatewayRecoveryScreen(
imageVector =
when (recoveryState) {
GatewayRecoveryUiState.Connected -> Icons.Default.CheckCircle
GatewayRecoveryUiState.ApprovalRequired -> Icons.Default.WifiTethering
GatewayRecoveryUiState.Pairing -> Icons.Default.WifiTethering
GatewayRecoveryUiState.Finishing -> Icons.Default.WifiTethering
GatewayRecoveryUiState.Failed -> Icons.Default.ErrorOutline
@@ -554,6 +632,7 @@ private fun GatewayRecoveryScreen(
tint =
when (recoveryState) {
GatewayRecoveryUiState.Connected -> ClawTheme.colors.success
GatewayRecoveryUiState.ApprovalRequired -> ClawTheme.colors.warning
GatewayRecoveryUiState.Pairing -> ClawTheme.colors.text
GatewayRecoveryUiState.Finishing -> ClawTheme.colors.text
GatewayRecoveryUiState.Failed -> ClawTheme.colors.warning
@@ -571,12 +650,16 @@ private fun GatewayRecoveryScreen(
ClawPanel {
Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
Text(text = "Last gateway", style = ClawTheme.type.caption, color = ClawTheme.colors.textMuted)
Text(text = serverName?.takeIf { it.isNotBlank() } ?: "Home Gateway", style = ClawTheme.type.section, color = ClawTheme.colors.text)
Text(text = recoveryGatewayDetail(ready = ready, remoteAddress = remoteAddress, statusText = statusText), style = ClawTheme.type.body, color = ClawTheme.colors.textMuted)
Text(text = recoveryGatewayName(serverName = serverName, attemptedGatewayName = attemptedGatewayName), style = ClawTheme.type.section, color = ClawTheme.colors.text)
Text(text = recoveryGatewayDetail(ready = ready, remoteAddress = remoteAddress, statusText = statusText, gatewayConnectionProblem = gatewayConnectionProblem), style = ClawTheme.type.body, color = ClawTheme.colors.textMuted)
recoveryGatewayApprovalCommand(gatewayConnectionProblem)?.let { command ->
ApprovalCommandBlock(command = command, onCopy = { copyApprovalCommand(context, command) })
}
ClawStatusPill(
text =
when (recoveryState) {
GatewayRecoveryUiState.Connected -> "Healthy"
GatewayRecoveryUiState.ApprovalRequired -> "Needs approval"
GatewayRecoveryUiState.Pairing -> "Pairing"
GatewayRecoveryUiState.Finishing -> "Connecting"
GatewayRecoveryUiState.Failed -> "Needs attention"
@@ -584,6 +667,7 @@ private fun GatewayRecoveryScreen(
status =
when (recoveryState) {
GatewayRecoveryUiState.Connected -> ClawStatus.Success
GatewayRecoveryUiState.ApprovalRequired -> ClawStatus.Warning
GatewayRecoveryUiState.Pairing -> ClawStatus.Neutral
GatewayRecoveryUiState.Finishing -> ClawStatus.Neutral
GatewayRecoveryUiState.Failed -> ClawStatus.Warning
@@ -600,7 +684,42 @@ private fun GatewayRecoveryScreen(
modifier = Modifier.fillMaxWidth(),
)
OutlinedAction(title = "Edit connection", icon = Icons.Default.Edit, onClick = onEdit)
OutlinedAction(title = "Copy diagnostic", icon = Icons.Default.ContentCopy, onClick = { copyGatewayDiagnostic(context, statusText, serverName, remoteAddress, ready) })
OutlinedAction(title = "Copy diagnostic", icon = Icons.Default.ContentCopy, onClick = { copyGatewayDiagnostic(context, statusText, serverName, remoteAddress, ready, gatewayConnectionProblem) })
}
}
}
}
@Composable
private fun ApprovalCommandBlock(
command: String,
onCopy: () -> Unit,
) {
Surface(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(8.dp),
color = ClawTheme.colors.surfacePressed,
border = BorderStroke(1.dp, ClawTheme.colors.border),
) {
Row(
modifier = Modifier.fillMaxWidth().padding(start = 12.dp, end = 6.dp, top = 8.dp, bottom = 8.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
SelectionContainer(modifier = Modifier.weight(1f)) {
Text(text = command, style = ClawTheme.type.body.copy(fontFamily = FontFamily.Monospace), color = ClawTheme.colors.text)
}
Surface(
onClick = onCopy,
modifier = Modifier.size(36.dp),
shape = RoundedCornerShape(8.dp),
color = ClawTheme.colors.surfaceRaised,
contentColor = ClawTheme.colors.text,
border = BorderStroke(1.dp, ClawTheme.colors.border),
) {
Box(contentAlignment = Alignment.Center) {
Icon(imageVector = Icons.Default.ContentCopy, contentDescription = "Copy approval command", modifier = Modifier.size(18.dp))
}
}
}
}
@@ -697,7 +816,7 @@ private fun GatewayOption(
icon: ImageVector,
title: String,
subtitle: String,
onClick: () -> Unit,
onClick: (() -> Unit)?,
status: String? = null,
) {
ClawPanel(contentPadding = PaddingValues(horizontal = 12.dp, vertical = 8.dp)) {
@@ -706,9 +825,12 @@ private fun GatewayOption(
subtitle = subtitle,
metadata = status,
leading = { Icon(imageVector = icon, contentDescription = null, modifier = Modifier.size(22.dp), tint = ClawTheme.colors.text) },
trailing = {
Icon(imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight, contentDescription = "Open $title", modifier = Modifier.size(20.dp), tint = ClawTheme.colors.text)
},
trailing =
onClick?.let {
{
Icon(imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight, contentDescription = "Open $title", modifier = Modifier.size(20.dp), tint = ClawTheme.colors.text)
}
},
onClick = onClick,
)
}
@@ -884,38 +1006,80 @@ private fun PermissionContinueButton(onClick: () -> Unit) {
internal enum class GatewayRecoveryUiState(
val title: String,
val message: String,
val canAutoRetry: Boolean,
) {
Connected(
title = "Connected",
message = "Your Gateway is ready.",
canAutoRetry = false,
),
ApprovalRequired(
title = "Pairing Gateway",
message = "Approve this phone on the gateway.\nThen retry the connection.",
),
Pairing(
title = "Pairing Gateway",
message = "Approval is in progress.\nOpenClaw will reconnect automatically.",
canAutoRetry = true,
),
Finishing(
title = "Finishing Setup",
message = "Gateway approved this phone.\nOpenClaw is bringing the node online.",
canAutoRetry = true,
title = "Connecting Gateway",
message = "OpenClaw is checking gateway and node access.",
),
Failed(
title = "Connection issue",
message = "We could not reach your Gateway.\nLet's fix this.",
canAutoRetry = false,
),
}
internal data class NearbyGatewayUiState(
val subtitle: String,
val status: String?,
val canConnect: Boolean,
)
/** Maps best-effort discovery into row copy and clickability for onboarding. */
internal fun nearbyGatewayUiState(
nearbyGatewayName: String?,
discoveryStatusText: String,
discoveryStarted: Boolean = true,
searchTimedOut: Boolean = false,
): NearbyGatewayUiState {
val name = nearbyGatewayName?.trim().takeUnless { it.isNullOrEmpty() }
if (name != null) {
return NearbyGatewayUiState(subtitle = name, status = "Found", canConnect = true)
}
if (!discoveryStarted) {
return NearbyGatewayUiState(subtitle = "Starting discovery...", status = "Starting", canConnect = false)
}
val status = discoveryStatusText.trim()
val searching =
status.isEmpty() ||
status.equals("Searching…", ignoreCase = true) ||
status.contains("Searching", ignoreCase = true) ||
status.endsWith("?", ignoreCase = true)
return if (searching) {
if (searchTimedOut) {
NearbyGatewayUiState(subtitle = "No gateway found", status = "Not found", canConnect = false)
} else {
NearbyGatewayUiState(subtitle = "Searching for gateways...", status = "Searching", canConnect = false)
}
} else {
NearbyGatewayUiState(subtitle = "No gateway found", status = "Not found", canConnect = false)
}
}
/** Derives recovery screen state from gateway/node readiness and transient status text. */
internal fun gatewayRecoveryUiState(
ready: Boolean,
statusText: String,
connectSettling: Boolean,
gatewayConnectionProblem: GatewayConnectionProblem? = null,
): GatewayRecoveryUiState =
when {
ready -> GatewayRecoveryUiState.Connected
gatewayConnectionProblem?.isPairingRequired == true &&
!gatewayConnectionProblem.canAutoRetry -> GatewayRecoveryUiState.ApprovalRequired
gatewayConnectionProblem?.isPairingRequired == true -> GatewayRecoveryUiState.Pairing
gatewayConnectionProblem?.pauseReconnect == true -> GatewayRecoveryUiState.Failed
connectSettling -> GatewayRecoveryUiState.Finishing
gatewayStatusLooksLikePairing(statusText) -> GatewayRecoveryUiState.Pairing
gatewayStatusLooksLikePartialConnect(statusText) -> GatewayRecoveryUiState.Finishing
@@ -928,6 +1092,18 @@ internal fun gatewayStatusLooksLikePartialConnect(statusText: String): Boolean {
return lower.contains("operator offline") || lower.contains("node offline")
}
internal fun recoveryGatewayName(
serverName: String?,
attemptedGatewayName: String?,
): String =
serverName
?.trim()
?.takeIf { it.isNotEmpty() }
?: attemptedGatewayName
?.trim()
?.takeIf { it.isNotEmpty() }
?: "Home Gateway"
private data class GatewayConfig(
val host: String,
val port: Int,
@@ -987,11 +1163,16 @@ private fun recoveryGatewayDetail(
ready: Boolean,
remoteAddress: String?,
statusText: String,
gatewayConnectionProblem: GatewayConnectionProblem?,
): String =
remoteAddress
?.takeIf { it.isNotBlank() }
?: if (ready) {
"Ready for chat and voice"
} else if (gatewayConnectionProblem?.isPairingRequired == true && !gatewayConnectionProblem.canAutoRetry) {
recoveryGatewayApprovalCommand(gatewayConnectionProblem)
?.let { "Gateway approval is pending. Run this on the gateway host:" }
?: "Gateway approval is pending. Run openclaw devices list on the gateway host, approve this phone, then retry."
} else if (statusText.contains("operator offline", ignoreCase = true)) {
"Gateway paired. Waiting for operator access."
} else if (gatewayStatusLooksLikePairing(statusText)) {
@@ -1000,6 +1181,25 @@ private fun recoveryGatewayDetail(
"Gateway unreachable"
}
private fun recoveryGatewayApprovalCommand(gatewayConnectionProblem: GatewayConnectionProblem?): String? {
if (gatewayConnectionProblem?.isPairingRequired != true || gatewayConnectionProblem.canAutoRetry) return null
val requestId = gatewayConnectionProblem.requestId?.trim()?.takeIf { it.isNotEmpty() }
return if (requestId != null) {
"openclaw devices approve $requestId"
} else {
"openclaw devices list"
}
}
private fun copyApprovalCommand(
context: Context,
command: String,
) {
val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
clipboard.setPrimaryClip(ClipData.newPlainText("OpenClaw pairing approval command", command))
Toast.makeText(context, "Approval command copied", Toast.LENGTH_SHORT).show()
}
/** Copies the onboarding recovery snapshot for support without including credentials. */
private fun copyGatewayDiagnostic(
context: Context,
@@ -1007,11 +1207,16 @@ private fun copyGatewayDiagnostic(
serverName: String?,
remoteAddress: String?,
ready: Boolean,
gatewayConnectionProblem: GatewayConnectionProblem?,
) {
val approvalCommand = recoveryGatewayApprovalCommand(gatewayConnectionProblem)
val diagnostic =
listOf(
listOfNotNull(
"OpenClaw Android gateway diagnostic",
"Status: $statusText",
gatewayConnectionProblem?.message?.let { "Gateway problem: $it" },
gatewayConnectionProblem?.requestId?.let { "Pairing request: $it" },
approvalCommand?.let { "Approval command: $it" },
"Gateway: ${serverName?.takeIf { it.isNotBlank() } ?: "Home Gateway"}",
"Address: ${remoteAddress?.takeIf { it.isNotBlank() } ?: "Not available"}",
"Ready: ${if (ready) "yes" else "no"}",

View File

@@ -6,7 +6,6 @@ import ai.openclaw.app.MainViewModel
import ai.openclaw.app.providerDisplayName
import ai.openclaw.app.ui.design.ClawEmptyState
import ai.openclaw.app.ui.design.ClawPanel
import ai.openclaw.app.ui.design.ClawPrimaryButton
import ai.openclaw.app.ui.design.ClawScaffold
import ai.openclaw.app.ui.design.ClawSecondaryButton
import ai.openclaw.app.ui.design.ClawTheme
@@ -17,23 +16,20 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.KeyboardArrowDown
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.Surface
@@ -42,25 +38,20 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
/** Android providers/models browser backed by the gateway catalog. */
/** Android provider readiness screen backed by the configured gateway model view. */
@Composable
internal fun ProvidersModelsScreen(
viewModel: MainViewModel,
onBack: () -> Unit,
onAddProvider: () -> Unit,
) {
val isConnected by viewModel.isConnected.collectAsState()
val models by viewModel.modelCatalog.collectAsState()
@@ -68,9 +59,6 @@ internal fun ProvidersModelsScreen(
val refreshing by viewModel.modelCatalogRefreshing.collectAsState()
val errorText by viewModel.modelCatalogErrorText.collectAsState()
val providerRows = providerRows(providers = providers, models = models)
val modelGroups = sortedModelGroups(models)
val setupRows = providerSetupRows(providerRows)
var expandedModelProviders by rememberSaveable { mutableStateOf(emptyList<String>()) }
LaunchedEffect(isConnected) {
if (isConnected) {
@@ -78,9 +66,16 @@ internal fun ProvidersModelsScreen(
}
}
ClawScaffold(contentPadding = PaddingValues(start = 20.dp, top = 13.dp, end = 20.dp, bottom = 13.dp)) {
ClawScaffold(
contentPadding = PaddingValues(start = 20.dp, top = 13.dp, end = 20.dp, bottom = 6.dp),
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
) {
Box(modifier = Modifier.fillMaxSize()) {
LazyColumn(verticalArrangement = Arrangement.spacedBy(7.dp), contentPadding = PaddingValues(bottom = 112.dp)) {
LazyColumn(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.spacedBy(7.dp),
contentPadding = PaddingValues(bottom = 4.dp),
) {
item {
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
Row(
@@ -89,12 +84,11 @@ internal fun ProvidersModelsScreen(
horizontalArrangement = Arrangement.SpaceBetween,
) {
ProviderHeaderIconButton(icon = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back", onClick = onBack)
ProviderHeaderIconButton(icon = Icons.Default.Add, contentDescription = "Add provider", outlined = true, onClick = onAddProvider)
}
Column(verticalArrangement = Arrangement.spacedBy(3.dp)) {
Text(text = "Providers & Models", style = ClawTheme.type.display.copy(fontSize = 14.8.sp, lineHeight = 18.sp), color = ClawTheme.colors.text, maxLines = 1)
Text(
text = "Connect and manage AI providers\nBrowse models and their capabilities.",
text = "Review provider readiness\nand configured models.",
style = ClawTheme.type.caption.copy(fontSize = 12.5.sp, lineHeight = 16.sp),
color = ClawTheme.colors.textMuted,
)
@@ -108,26 +102,17 @@ internal fun ProvidersModelsScreen(
providerRows = providerRows,
modelCount = models.size,
onRefresh = viewModel::refreshModelCatalog,
onSetup = onAddProvider,
refreshing = refreshing,
)
}
item {
ProviderSectionLabel(title = "Provider setup")
}
item {
ProviderSetupList(rows = setupRows, onSetup = onAddProvider)
}
item {
ProviderSectionLabel(title = "Connected providers")
}
item {
if (!isConnected && providerRows.isEmpty()) {
ClawEmptyState(title = "Gateway offline", body = "Connect your Gateway to load provider readiness and model catalog.")
ClawEmptyState(title = "Gateway offline", body = "Connect your Gateway to load provider readiness.")
} else {
ProviderList(rows = providerRows, refreshing = refreshing)
}
@@ -140,50 +125,12 @@ internal fun ProvidersModelsScreen(
}
}
}
item {
ProviderSectionLabel(title = "Model catalog")
}
if (modelGroups.isEmpty()) {
item {
ModelCatalogEmpty(
title = if (refreshing) "Loading models" else "No models loaded",
body = if (isConnected) "Refresh after configuring a provider on the Gateway." else "Connect the Gateway to browse models.",
)
}
} else {
items(modelGroups, key = { it.first }) { entry ->
val expanded = expandedModelProviders.contains(entry.first)
ModelGroup(
provider = entry.first,
models = entry.second,
expanded = expanded,
onToggle = {
expandedModelProviders =
if (expanded) {
expandedModelProviders - entry.first
} else {
expandedModelProviders + entry.first
}
},
)
}
}
}
ProviderAddButton(onClick = onAddProvider, modifier = Modifier.align(Alignment.BottomCenter))
}
}
}
private data class ProviderSetupRow(
val id: String,
val name: String,
val subtitle: String,
val ready: Boolean,
)
private data class ProviderRow(
internal data class ProviderRow(
val id: String,
val name: String,
val status: String,
@@ -191,28 +138,28 @@ private data class ProviderRow(
val modelCount: Int,
)
/** Combines auth-provider readiness rows with catalog-only providers. */
private fun providerRows(
/** Combines gateway auth-provider readiness with configured model providers. */
internal fun providerRows(
providers: List<GatewayModelProviderSummary>,
models: List<GatewayModelSummary>,
): List<ProviderRow> {
val modelCounts = models.groupingBy { it.provider }.eachCount()
val authRows =
providers.map { provider ->
val ready = modelProviderReady(provider.status)
ProviderRow(
id = provider.id,
name = provider.displayName,
status = if (ready) "Ready" else "Needs setup",
ready = ready,
modelCount = modelCounts[provider.id] ?: 0,
)
}
// Static/catalog-only providers may expose models without a matching auth
// provider row; keep them visible as ready providers.
val missingAuthRows =
providers
.map { provider ->
val ready = modelProviderReady(provider.status)
ProviderRow(
id = provider.id,
name = provider.displayName,
status = if (ready) "Ready" else "Needs attention",
ready = ready,
modelCount = modelCounts[provider.id] ?: 0,
)
}
val authProviderIds = authRows.mapTo(mutableSetOf()) { it.id.trim().lowercase() }
val configuredModelRows =
modelCounts.keys
.filter { provider -> authRows.none { it.id == provider } }
.filter { provider -> provider.trim().lowercase() !in authProviderIds }
.map { provider ->
ProviderRow(
id = provider,
@@ -222,33 +169,9 @@ private fun providerRows(
modelCount = modelCounts[provider] ?: 0,
)
}
return (authRows + missingAuthRows).sortedWith(compareBy(::providerPriority, { it.name.lowercase() }))
return (authRows + configuredModelRows).sortedWith(compareBy(::providerPriority, { it.name.lowercase() }))
}
private fun providerSetupRows(providerRows: List<ProviderRow>): List<ProviderSetupRow> {
val byId = providerRows.associateBy { it.id.trim().lowercase() }
return listOf("openai", "anthropic", "google", "openrouter", "ollama").map { id ->
val row = byId[id] ?: byId["ollama-local"].takeIf { id == "ollama" }
ProviderSetupRow(
id = id,
name = providerDisplayName(id),
subtitle = providerSetupSubtitle(id, row),
ready = row?.ready == true,
)
}
}
private fun providerSetupSubtitle(
id: String,
row: ProviderRow?,
): String =
when {
row?.ready == true -> if (row.modelCount > 0) "${row.modelCount} models available" else "Ready"
row != null -> "Finish setup to use ${row.name}"
id == "ollama" -> "Use models running on your network"
else -> "Add provider credentials on your Gateway"
}
/** Normalizes gateway provider status strings into a ready/not-ready boolean. */
internal fun modelProviderReady(status: String): Boolean {
val normalized = status.trim().lowercase()
@@ -259,14 +182,6 @@ internal fun modelProviderReady(status: String): Boolean {
normalized == "static"
}
/** Groups models by provider using the same display priority as provider rows. */
private fun sortedModelGroups(models: List<GatewayModelSummary>): List<Pair<String, List<GatewayModelSummary>>> =
models
.groupBy { it.provider }
.entries
.sortedWith(compareBy({ providerPriority(it.key) }, { providerDisplayName(it.key).lowercase() }))
.map { it.key to it.value }
private fun providerPriority(row: ProviderRow): Int = providerPriority(row.id)
private fun providerPriority(provider: String): Int =
@@ -288,7 +203,15 @@ private fun ProviderList(
ClawPanel(contentPadding = PaddingValues(horizontal = 0.dp, vertical = 0.dp)) {
Column {
if (rows.isEmpty()) {
ProviderListRow(ProviderRow(id = "loading", name = "Provider catalog", status = if (refreshing) "Loading" else "No providers", ready = false, modelCount = 0))
ProviderListRow(
ProviderRow(
id = "loading",
name = "Provider catalog",
status = if (refreshing) "Loading" else "No providers",
ready = false,
modelCount = 0,
),
)
} else {
val visibleRows = rows.take(5)
visibleRows.forEachIndexed { index, row ->
@@ -309,7 +232,6 @@ private fun ProviderOverviewPanel(
modelCount: Int,
refreshing: Boolean,
onRefresh: () -> Unit,
onSetup: () -> Unit,
) {
val readyCount = providerRows.count { it.ready }
val needsSetupCount = providerRows.count { !it.ready }
@@ -318,17 +240,14 @@ private fun ProviderOverviewPanel(
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) {
ProviderMetricTile(label = "Ready", value = readyCount.toString(), modifier = Modifier.weight(1f))
ProviderMetricTile(label = "Models", value = modelCount.toString(), modifier = Modifier.weight(1f))
ProviderMetricTile(label = "Setup", value = needsSetupCount.toString(), modifier = Modifier.weight(1f))
ProviderMetricTile(label = "Needs", value = needsSetupCount.toString(), modifier = Modifier.weight(1f))
}
Text(
text = if (isConnected) "Choose a provider below, then finish credentials on your Gateway." else "Connect your Gateway before adding model providers.",
text = if (isConnected) "Refresh to recheck provider readiness from your Gateway." else "Connect your Gateway to view provider readiness.",
style = ClawTheme.type.body,
color = ClawTheme.colors.textMuted,
)
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(8.dp)) {
ClawSecondaryButton(text = if (refreshing) "Refreshing" else "Refresh", onClick = onRefresh, enabled = isConnected && !refreshing, modifier = Modifier.weight(1f))
ClawPrimaryButton(text = "Setup Provider", onClick = onSetup, enabled = isConnected, modifier = Modifier.weight(1f))
}
ClawSecondaryButton(text = if (refreshing) "Refreshing" else "Refresh", onClick = onRefresh, enabled = isConnected && !refreshing, modifier = Modifier.fillMaxWidth())
}
}
}
@@ -353,55 +272,13 @@ private fun ProviderMetricTile(
}
}
@Composable
private fun ProviderSetupList(
rows: List<ProviderSetupRow>,
onSetup: () -> Unit,
) {
ClawPanel(contentPadding = PaddingValues(horizontal = 0.dp, vertical = 0.dp)) {
Column {
rows.forEachIndexed { index, row ->
ProviderSetupListRow(row = row, onClick = onSetup)
if (index != rows.lastIndex) {
HorizontalDivider(color = ClawTheme.colors.border, thickness = 1.dp)
}
}
}
}
}
@Composable
private fun ProviderSetupListRow(
row: ProviderSetupRow,
onClick: () -> Unit,
) {
Surface(onClick = onClick, color = Color.Transparent, contentColor = ClawTheme.colors.text) {
Row(
modifier = Modifier.fillMaxWidth().heightIn(min = 58.dp).padding(horizontal = 10.dp, vertical = 6.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(10.dp),
) {
ProviderBadge(text = row.name)
Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(1.dp)) {
Text(text = row.name, style = ClawTheme.type.body, color = ClawTheme.colors.text, maxLines = 1)
Text(text = row.subtitle, style = ClawTheme.type.caption.copy(fontSize = 12.5.sp, lineHeight = 16.sp), color = ClawTheme.colors.textMuted, maxLines = 1, overflow = TextOverflow.Ellipsis)
}
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(6.dp)) {
Box(modifier = Modifier.size(5.dp).clip(CircleShape).background(if (row.ready) ClawTheme.colors.success else ClawTheme.colors.warning))
Text(text = if (row.ready) "Ready" else "Setup", style = ClawTheme.type.caption.copy(fontSize = 12.5.sp, lineHeight = 16.sp), color = ClawTheme.colors.textMuted, maxLines = 1)
Icon(imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight, contentDescription = "Open ${row.name}", modifier = Modifier.size(17.dp), tint = ClawTheme.colors.text)
}
}
}
}
@Composable
private fun ProviderListRow(row: ProviderRow) {
Row(modifier = Modifier.fillMaxWidth().heightIn(min = 58.dp).padding(horizontal = 10.dp, vertical = 6.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(10.dp)) {
ProviderBadge(text = row.name)
Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(1.dp)) {
Text(text = row.name, style = ClawTheme.type.body, color = ClawTheme.colors.text, maxLines = 1)
Text(text = if (row.modelCount > 0) "${row.modelCount} models" else "Provider setup", style = ClawTheme.type.caption.copy(fontSize = 12.5.sp, lineHeight = 16.sp), color = ClawTheme.colors.textMuted, maxLines = 1)
Text(text = if (row.modelCount > 0) "${row.modelCount} models" else "No configured models", style = ClawTheme.type.caption.copy(fontSize = 12.5.sp, lineHeight = 16.sp), color = ClawTheme.colors.textMuted, maxLines = 1)
}
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(5.dp)) {
Box(modifier = Modifier.size(4.5.dp).clip(CircleShape).background(if (row.ready) ClawTheme.colors.success else ClawTheme.colors.warning))
@@ -428,78 +305,6 @@ private fun providerInitials(value: String): String =
.joinToString("")
.ifBlank { "AI" }
@Composable
private fun ModelCatalogEmpty(
title: String,
body: String,
) {
ClawPanel(contentPadding = PaddingValues(horizontal = 11.dp, vertical = 10.dp)) {
Column(modifier = Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(4.dp)) {
Text(text = title, style = ClawTheme.type.section, color = ClawTheme.colors.text)
Text(text = body, style = ClawTheme.type.caption.copy(fontSize = 12.5.sp, lineHeight = 16.sp), color = ClawTheme.colors.textMuted)
}
}
}
@Composable
private fun ModelGroup(
provider: String,
models: List<GatewayModelSummary>,
expanded: Boolean,
onToggle: () -> Unit,
) {
ClawPanel(contentPadding = PaddingValues(horizontal = 0.dp, vertical = 0.dp)) {
Column {
Surface(onClick = onToggle, color = Color.Transparent, contentColor = ClawTheme.colors.text) {
Row(modifier = Modifier.fillMaxWidth().heightIn(min = 52.dp).padding(horizontal = 10.dp, vertical = 6.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(10.dp)) {
ProviderBadge(text = providerDisplayName(provider))
Text(text = providerDisplayName(provider), style = ClawTheme.type.body, color = ClawTheme.colors.text, modifier = Modifier.weight(1f), maxLines = 1)
ProviderMiniTag(text = "${models.size} models")
Icon(imageVector = if (expanded) Icons.Default.KeyboardArrowDown else Icons.AutoMirrored.Filled.KeyboardArrowRight, contentDescription = if (expanded) "Collapse ${providerDisplayName(provider)} models" else "Expand ${providerDisplayName(provider)} models", modifier = Modifier.size(14.dp), tint = ClawTheme.colors.textMuted)
}
}
HorizontalDivider(color = ClawTheme.colors.border, thickness = 1.dp)
val visibleModels = if (expanded) models else models.take(3)
visibleModels.forEachIndexed { index, model ->
ModelRow(model)
if (index != visibleModels.lastIndex || models.size > visibleModels.size) {
HorizontalDivider(color = ClawTheme.colors.border, thickness = 1.dp)
}
}
if (models.size > visibleModels.size) {
Surface(onClick = onToggle, color = Color.Transparent, contentColor = ClawTheme.colors.text) {
Row(modifier = Modifier.fillMaxWidth().padding(horizontal = 10.dp, vertical = 8.dp), verticalAlignment = Alignment.CenterVertically) {
Text(text = "View all models", style = ClawTheme.type.caption.copy(fontSize = 12.5.sp, lineHeight = 16.sp), color = ClawTheme.colors.textMuted, modifier = Modifier.weight(1f))
Icon(imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight, contentDescription = "View all models", modifier = Modifier.size(14.dp), tint = ClawTheme.colors.text)
}
}
}
}
}
}
@Composable
private fun ModelRow(model: GatewayModelSummary) {
Row(modifier = Modifier.fillMaxWidth().heightIn(min = 48.dp).padding(horizontal = 10.dp, vertical = 5.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(6.dp)) {
Text(text = model.name, style = ClawTheme.type.mono, color = ClawTheme.colors.text, modifier = Modifier.weight(1f), maxLines = 1, overflow = TextOverflow.Ellipsis)
modelCapabilityLabels(model).take(3).forEach { label ->
ProviderMiniTag(text = label)
}
Box(modifier = Modifier.size(4.5.dp).clip(CircleShape).background(ClawTheme.colors.success))
}
}
/** Derives compact capability chips for model catalog rows. */
private fun modelCapabilityLabels(model: GatewayModelSummary): List<String> =
buildList {
if (model.supportsReasoning) add("Reasoning")
if (model.supportsVision) add("Vision")
if (model.supportsAudio) add("Voice")
if (model.supportsDocuments) add("Docs")
if ((model.contextTokens ?: 0L) >= 100_000L) add("Long context")
if (isEmpty()) add("Fast")
}
@Composable
private fun ProviderSectionLabel(title: String) {
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween) {
@@ -527,39 +332,3 @@ private fun ProviderHeaderIconButton(
}
}
}
@Composable
private fun ProviderAddButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
) {
Surface(
onClick = onClick,
modifier = modifier.fillMaxWidth().height(ClawTheme.spacing.touchTarget),
shape = RoundedCornerShape(ClawTheme.radii.pill),
color = ClawTheme.colors.primary,
contentColor = ClawTheme.colors.primaryText,
) {
Row(
modifier = Modifier.fillMaxSize(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
) {
Icon(imageVector = Icons.Default.Add, contentDescription = null, modifier = Modifier.size(17.dp))
Spacer(modifier = Modifier.width(7.dp))
Text(text = "Open Gateway Setup", style = ClawTheme.type.label, maxLines = 1)
}
}
}
@Composable
private fun ProviderMiniTag(text: String) {
Surface(
shape = RoundedCornerShape(5.dp),
color = Color.Transparent,
border = BorderStroke(1.dp, ClawTheme.colors.border),
contentColor = ClawTheme.colors.textMuted,
) {
Text(text = text, modifier = Modifier.padding(horizontal = 4.dp, vertical = 0.5.dp), style = ClawTheme.type.caption.copy(fontSize = 12.5.sp, lineHeight = 16.sp), maxLines = 1)
}
}

View File

@@ -13,11 +13,14 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
@@ -88,8 +91,15 @@ internal fun SessionsScreen(
}
}
ClawScaffold(contentPadding = PaddingValues(start = 20.dp, top = 14.dp, end = 20.dp, bottom = 20.dp)) {
LazyColumn(verticalArrangement = Arrangement.spacedBy(7.dp)) {
ClawScaffold(
contentPadding = PaddingValues(start = 20.dp, top = 14.dp, end = 20.dp, bottom = 6.dp),
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
) {
LazyColumn(
modifier = Modifier.fillMaxSize(),
verticalArrangement = Arrangement.spacedBy(7.dp),
contentPadding = PaddingValues(bottom = 4.dp),
) {
item {
Row(
modifier = Modifier.fillMaxWidth(),
@@ -133,11 +143,16 @@ internal fun SessionsScreen(
if (visibleSessions.isEmpty()) {
item {
ClawEmptyState(
title = emptySessionTitle(filter),
body = emptySessionBody(filter),
action = { ClawPrimaryButton(text = "Start Chat", onClick = onOpenChat) },
)
Box(
modifier = Modifier.fillParentMaxHeight(0.56f).fillMaxWidth(),
contentAlignment = Alignment.Center,
) {
ClawEmptyState(
title = emptySessionTitle(filter),
body = emptySessionBody(filter),
action = { ClawPrimaryButton(text = "Start Chat", onClick = onOpenChat) },
)
}
}
} else {
items(visibleSessions, key = { it.key }) { session ->
@@ -155,10 +170,6 @@ internal fun SessionsScreen(
)
}
}
item {
Spacer(modifier = Modifier.height(16.dp))
}
}
}
}

View File

@@ -8,7 +8,6 @@ import ai.openclaw.app.LocationMode
import ai.openclaw.app.MainViewModel
import ai.openclaw.app.NotificationPackageFilterMode
import ai.openclaw.app.chat.ChatPendingToolCall
import ai.openclaw.app.gateway.GatewayEndpoint
import ai.openclaw.app.node.DeviceNotificationListenerService
import ai.openclaw.app.ui.design.ClawDetailRow
import ai.openclaw.app.ui.design.ClawIconBadge
@@ -44,11 +43,15 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.CircleShape
@@ -493,6 +496,8 @@ private fun playVoiceSetupTone() {
Handler(Looper.getMainLooper()).postDelayed({ tone.release() }, 300L)
}
private const val NOTIFICATION_PICKER_RESULT_LIMIT = 40
@Composable
private fun NotificationSettingsScreen(
viewModel: MainViewModel,
@@ -507,6 +512,19 @@ private fun NotificationSettingsScreen(
val quietEnd by viewModel.notificationForwardingQuietEnd.collectAsState()
val maxEventsPerMinute by viewModel.notificationForwardingMaxEventsPerMinute.collectAsState()
val modeLabel = if (mode == NotificationPackageFilterMode.Blocklist) "Blocklist" else "Allowlist"
val installedApps = remember(context, packages) { queryInstalledApps(context, packages) }
var notificationPickerExpanded by remember { mutableStateOf(false) }
var notificationAppSearch by remember { mutableStateOf("") }
var notificationShowSystemApps by remember { mutableStateOf(false) }
val filteredApps =
remember(installedApps, packages, notificationAppSearch, notificationShowSystemApps) {
filterNotificationAppsForPicker(
apps = installedApps,
selectedPackages = packages,
query = notificationAppSearch,
showSystemApps = notificationShowSystemApps,
)
}
var listenerEnabled by remember { mutableStateOf(DeviceNotificationListenerService.isAccessEnabled(context)) }
val notificationPermissionLauncher =
rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
@@ -567,6 +585,124 @@ private fun NotificationSettingsScreen(
)
}
}
NotificationPackagePickerPanel(
mode = mode,
selectedPackages = packages,
apps = filteredApps,
search = notificationAppSearch,
showSystemApps = notificationShowSystemApps,
expanded = notificationPickerExpanded,
onSearchChange = { notificationAppSearch = it },
onShowSystemAppsChange = { notificationShowSystemApps = it },
onExpandedChange = { notificationPickerExpanded = it },
onPackageSelectionChange = { packageName, selected ->
val next = packages.toMutableSet()
if (selected) {
next.add(packageName)
} else {
next.remove(packageName)
}
viewModel.setNotificationForwardingPackagesCsv(next.sorted().joinToString(","))
},
)
}
}
@Composable
private fun NotificationPackagePickerPanel(
mode: NotificationPackageFilterMode,
selectedPackages: Set<String>,
apps: List<InstalledApp>,
search: String,
showSystemApps: Boolean,
expanded: Boolean,
onSearchChange: (String) -> Unit,
onShowSystemAppsChange: (Boolean) -> Unit,
onExpandedChange: (Boolean) -> Unit,
onPackageSelectionChange: (String, Boolean) -> Unit,
) {
val visibleApps = apps.take(NOTIFICATION_PICKER_RESULT_LIMIT)
ClawPanel {
Column(verticalArrangement = Arrangement.spacedBy(10.dp)) {
Text(text = "App Filter", style = ClawTheme.type.section, color = ClawTheme.colors.text)
Text(
text = notificationPackageSelectionSummary(mode = mode, selectedCount = selectedPackages.size),
style = ClawTheme.type.body,
color = ClawTheme.colors.textMuted,
)
ClawSecondaryButton(
text = if (expanded) "Close App Picker" else "Open App Picker",
onClick = { onExpandedChange(!expanded) },
modifier = Modifier.fillMaxWidth(),
)
if (expanded) {
ClawTextField(value = search, onValueChange = onSearchChange, placeholder = "Search apps")
SettingsToggleListRow(
SettingsToggleRow(
title = "Show System Apps",
subtitle = "Include Android and background packages.",
icon = Icons.Default.Storage,
checked = showSystemApps,
onCheckedChange = onShowSystemAppsChange,
),
)
if (visibleApps.isEmpty()) {
Text(text = "No matching apps.", style = ClawTheme.type.body, color = ClawTheme.colors.textMuted)
} else {
ClawSeparatedColumn(items = visibleApps) { app ->
NotificationPackageAppRow(
app = app,
selected = selectedPackages.contains(app.packageName),
onSelectedChange = { selected -> onPackageSelectionChange(app.packageName, selected) },
)
}
if (apps.size > visibleApps.size) {
Text(
text = "Showing ${visibleApps.size} of ${apps.size}. Refine search for more.",
style = ClawTheme.type.caption,
color = ClawTheme.colors.textMuted,
)
}
}
}
}
}
}
@Composable
private fun NotificationPackageAppRow(
app: InstalledApp,
selected: Boolean,
onSelectedChange: (Boolean) -> Unit,
) {
Row(
modifier =
Modifier
.fillMaxWidth()
.heightIn(min = 58.dp)
.clickable { onSelectedChange(!selected) }
.padding(vertical = 7.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(9.dp),
) {
ClawTextBadge(text = notificationAppBadge(app.label))
Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(1.dp)) {
Text(
text = app.label,
style = ClawTheme.type.body,
color = ClawTheme.colors.text,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
Text(
text = app.packageName,
style = ClawTheme.type.caption,
color = ClawTheme.colors.textMuted,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
Switch(checked = selected, onCheckedChange = onSelectedChange)
}
}
@@ -581,6 +717,7 @@ private fun PhoneCapabilitiesScreen(
val locationPreciseEnabled by viewModel.locationPreciseEnabled.collectAsState()
val preventSleep by viewModel.preventSleep.collectAsState()
val canvasDebugStatusEnabled by viewModel.canvasDebugStatusEnabled.collectAsState()
val installedAppsSharingEnabled by viewModel.installedAppsSharingEnabled.collectAsState()
val cameraPermissionLauncher =
rememberLauncherForActivityResult(ActivityResultContracts.RequestPermission()) { granted ->
viewModel.setCameraEnabled(granted)
@@ -635,6 +772,13 @@ private fun PhoneCapabilitiesScreen(
listOf(
SettingsToggleRow("Camera", "Allow camera tools when requested.", Icons.Default.CameraAlt, cameraEnabled, ::setCameraAccess),
SettingsToggleRow("Precise Location", "Share precise location while location is enabled.", Icons.Default.LocationOn, locationPreciseEnabled, ::setPreciseLocation),
SettingsToggleRow(
"Installed Apps",
if (installedAppsSharingEnabled) "OpenClaw can list launcher-visible apps." else "App list stays on this phone.",
Icons.Default.Storage,
installedAppsSharingEnabled,
viewModel::setInstalledAppsSharingEnabled,
),
SettingsToggleRow("Keep Awake", "Keep the node available during active work.", Icons.Default.Bolt, preventSleep, viewModel::setPreventSleep),
SettingsToggleRow("Canvas Status", "Show screen-sharing debug state.", Icons.AutoMirrored.Filled.ScreenShare, canvasDebugStatusEnabled, viewModel::setCanvasDebugStatusEnabled),
),
@@ -752,18 +896,14 @@ private fun GatewaySettingsScreen(
.orEmpty()
.ifEmpty { passwordInput.trim() }
validationText = null
viewModel.setManualEnabled(true)
viewModel.setManualHost(endpointConfig.host)
viewModel.setManualPort(endpointConfig.port)
viewModel.setManualTls(endpointConfig.tls)
viewModel.setGatewayBootstrapToken(bootstrapToken)
viewModel.setGatewayToken(token)
viewModel.setGatewayPassword(password)
viewModel.connect(
GatewayEndpoint.manual(host = endpointConfig.host, port = endpointConfig.port),
token = token.ifEmpty { null },
bootstrapToken = bootstrapToken.ifEmpty { null },
password = password.ifEmpty { null },
viewModel.saveGatewayConfigAndConnect(
host = endpointConfig.host,
port = endpointConfig.port,
tls = endpointConfig.tls,
token = token,
bootstrapToken = bootstrapToken,
password = password,
resetSetupAuth = setup != null,
)
},
modifier = Modifier.fillMaxWidth(),
@@ -826,7 +966,7 @@ private fun AboutSettingsScreen(
listOf(
SettingsMetric("Android App", BuildConfig.VERSION_NAME),
SettingsMetric("Build", BuildConfig.VERSION_CODE.toString()),
SettingsMetric("Channel", "Play"),
SettingsMetric("Channel", androidDistributionChannel()),
SettingsMetric("Gateway", currentGatewayVersion ?: "Not connected"),
),
)
@@ -849,6 +989,14 @@ private fun AboutSettingsScreen(
}
}
internal fun androidDistributionChannel(flavor: String = BuildConfig.FLAVOR): String =
when (flavor.trim()) {
"play" -> "Play"
"thirdParty" -> "Third-party"
"" -> "Unknown"
else -> flavor.trim()
}
@Composable
private fun AboutStatusRow(
title: String,
@@ -887,8 +1035,11 @@ internal fun SettingsDetailFrame(
onBack: () -> Unit,
content: @Composable () -> Unit,
) {
ClawScaffold(contentPadding = PaddingValues(start = ClawTheme.spacing.lg, top = 14.dp, end = ClawTheme.spacing.lg, bottom = 20.dp)) {
LazyColumn(verticalArrangement = Arrangement.spacedBy(10.dp)) {
ClawScaffold(
contentPadding = PaddingValues(start = ClawTheme.spacing.lg, top = 14.dp, end = ClawTheme.spacing.lg, bottom = 6.dp),
contentWindowInsets = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal),
) {
LazyColumn(modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.spacedBy(10.dp), contentPadding = PaddingValues(bottom = 4.dp)) {
item {
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(9.dp)) {
SettingsBackButton(onClick = onBack)
@@ -904,9 +1055,6 @@ internal fun SettingsDetailFrame(
content()
}
}
item {
Spacer(modifier = Modifier.height(12.dp))
}
}
}
}
@@ -1112,6 +1260,58 @@ private fun cronJobStatus(job: GatewayCronJobSummary): ClawStatus {
}
}
/** Applies query/system visibility rules while always preserving selected packages. */
internal fun filterNotificationAppsForPicker(
apps: List<InstalledApp>,
selectedPackages: Set<String>,
query: String,
showSystemApps: Boolean,
): List<InstalledApp> {
val normalizedQuery = query.trim().lowercase()
return apps.filter { app ->
val selected = app.packageName in selectedPackages
val visibleByType = showSystemApps || !app.isSystemApp || selected
val visibleBySearch =
normalizedQuery.isEmpty() ||
app.label.lowercase().contains(normalizedQuery) ||
app.packageName.lowercase().contains(normalizedQuery)
visibleByType && visibleBySearch
}
}
/** Summarizes allowlist/blocklist mode with an empty-state warning when needed. */
private fun notificationPackageSelectionSummary(
mode: NotificationPackageFilterMode,
selectedCount: Int,
): String =
when (mode) {
NotificationPackageFilterMode.Allowlist ->
if (selectedCount == 0) {
"No apps selected. Nothing forwards until you add apps."
} else {
"$selectedCount ${if (selectedCount == 1) "app" else "apps"} allowed to forward."
}
NotificationPackageFilterMode.Blocklist ->
if (selectedCount == 0) {
"No apps blocked. Apps can forward unless you add blocks."
} else {
"$selectedCount ${if (selectedCount == 1) "app" else "apps"} blocked from forwarding."
}
}
/** Builds compact two-letter app badges from package-picker labels. */
private fun notificationAppBadge(label: String): String {
val initials =
label
.split(' ', '-', '_', '.')
.asSequence()
.filter { it.isNotBlank() }
.take(2)
.mapNotNull { it.firstOrNull()?.uppercaseChar()?.toString() }
.joinToString("")
return initials.ifBlank { "A" }
}
/**
* Converts cron wake times into short relative labels for scheduled-work rows.
*/

View File

@@ -1222,82 +1222,6 @@ fun SettingsSheet(viewModel: MainViewModel) {
}
}
/** App entry shown in the notification-forwarding package picker. */
data class InstalledApp(
val label: String,
val packageName: String,
val isSystemApp: Boolean,
)
/** Reads launcher, recent-notification, and configured packages for the picker. */
private fun queryInstalledApps(
context: Context,
configuredPackages: Set<String>,
): List<InstalledApp> {
val packageManager = context.packageManager
val launcherIntent = Intent(Intent.ACTION_MAIN).apply { addCategory(Intent.CATEGORY_LAUNCHER) }
val launcherPackages =
packageManager
.queryIntentActivities(launcherIntent, PackageManager.MATCH_ALL)
.asSequence()
.mapNotNull {
it.activityInfo
?.packageName
?.trim()
?.takeIf(String::isNotEmpty)
}.toMutableSet()
val recentNotificationPackages =
DeviceNotificationListenerService
.recentPackages(context)
.asSequence()
.map { it.trim() }
.filter { it.isNotEmpty() }
.toList()
val candidatePackages =
resolveNotificationCandidatePackages(
launcherPackages = launcherPackages,
recentPackages = recentNotificationPackages,
configuredPackages = configuredPackages,
appPackageName = context.packageName,
)
return candidatePackages
.asSequence()
.mapNotNull { packageName ->
runCatching {
val appInfo = packageManager.getApplicationInfo(packageName, 0)
val label = packageManager.getApplicationLabel(appInfo).toString().trim()
InstalledApp(
label = if (label.isEmpty()) packageName else label,
packageName = packageName,
isSystemApp = (appInfo.flags and android.content.pm.ApplicationInfo.FLAG_SYSTEM) != 0,
)
}.getOrNull()
}.sortedWith(compareBy<InstalledApp> { it.label.lowercase() }.thenBy { it.packageName })
.toList()
}
/** Merges package sources while excluding OpenClaw from its own forwarding filter. */
internal fun resolveNotificationCandidatePackages(
launcherPackages: Set<String>,
recentPackages: List<String>,
configuredPackages: Set<String>,
appPackageName: String,
): Set<String> {
val blockedPackage = appPackageName.trim()
return sequenceOf(
configuredPackages.asSequence(),
launcherPackages.asSequence(),
recentPackages.asSequence(),
).flatten()
.map { it.trim() }
.filter { it.isNotEmpty() && it != blockedPackage }
.toSet()
}
/** Shared Material text-field colors for the legacy mobile settings sheet. */
@Composable
private fun settingsTextFieldColors() =

View File

@@ -9,11 +9,14 @@ import ai.openclaw.app.HomeDestination
import ai.openclaw.app.MainViewModel
import ai.openclaw.app.NodeRuntime
import ai.openclaw.app.ui.chat.ChatScreen
import ai.openclaw.app.ui.design.ClawBottomNav
import ai.openclaw.app.ui.design.ClawDesignTheme
import ai.openclaw.app.ui.design.ClawEmptyState
import ai.openclaw.app.ui.design.ClawNavItem
import ai.openclaw.app.ui.design.ClawPanel
import ai.openclaw.app.ui.design.ClawPrimaryButton
import ai.openclaw.app.ui.design.ClawScaffold
import ai.openclaw.app.ui.design.ClawSecondaryButton
import ai.openclaw.app.ui.design.ClawTheme
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.BorderStroke
@@ -24,20 +27,26 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.ime
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.safeDrawing
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.automirrored.filled.ExitToApp
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
import androidx.compose.material.icons.automirrored.filled.ScreenShare
import androidx.compose.material.icons.filled.Cloud
import androidx.compose.material.icons.filled.Home
import androidx.compose.material.icons.filled.Lock
import androidx.compose.material.icons.filled.Mic
import androidx.compose.material.icons.filled.Notifications
@@ -54,6 +63,7 @@ import androidx.compose.material.icons.outlined.Settings
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
@@ -69,23 +79,35 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
private enum class Tab(
internal enum class Tab(
val key: String,
val label: String,
val icon: ImageVector,
) {
Overview(key = "overview", label = "Home"),
Chat(key = "chat", label = "Chat"),
Voice(key = "voice", label = "Voice"),
Sessions(key = "sessions", label = "Sessions"),
Settings(key = "settings", label = "Settings"),
ProvidersModels(key = "providers-models", label = "Providers"),
Overview(key = "overview", label = "Home", icon = Icons.Default.Home),
Chat(key = "chat", label = "Chat", icon = Icons.Outlined.ChatBubbleOutline),
Voice(key = "voice", label = "Voice", icon = Icons.Outlined.MicNone),
Sessions(key = "sessions", label = "Sessions", icon = Icons.Outlined.AccessTime),
Settings(key = "settings", label = "Settings", icon = Icons.Outlined.Settings),
ProvidersModels(key = "providers-models", label = "Providers", icon = Icons.Outlined.Inventory2),
}
private val shellNavTabs = listOf(Tab.Overview, Tab.Chat, Tab.Voice, Tab.Settings)
private val shellContentInsets: WindowInsets
@Composable get() = WindowInsets.safeDrawing.only(WindowInsetsSides.Top + WindowInsetsSides.Horizontal)
internal fun shellBottomNavVisible(
keyboardVisible: Boolean,
commandOpen: Boolean,
): Boolean = !keyboardVisible && !commandOpen
/** Main post-onboarding shell that owns top-level Android navigation state. */
@Composable
fun ShellScreen(
@@ -97,8 +119,10 @@ fun ShellScreen(
var settingsRoute by rememberSaveable { mutableStateOf(SettingsRoute.Home) }
var returnToOverviewFromSettings by rememberSaveable { mutableStateOf(false) }
var commandOpen by rememberSaveable { mutableStateOf(false) }
var voiceScreenWasActive by rememberSaveable { mutableStateOf(false) }
val requestedHomeDestination by viewModel.requestedHomeDestination.collectAsState()
val pendingTrust by viewModel.pendingGatewayTrust.collectAsState()
val runtimeInitialized by viewModel.runtimeInitialized.collectAsState()
LaunchedEffect(requestedHomeDestination) {
val destination = requestedHomeDestination ?: return@LaunchedEffect
@@ -119,8 +143,12 @@ fun ShellScreen(
viewModel.clearRequestedHomeDestination()
}
LaunchedEffect(activeTab) {
viewModel.setVoiceScreenActive(activeTab == Tab.Voice)
LaunchedEffect(activeTab, runtimeInitialized) {
val voiceScreenActive = activeTab == Tab.Voice
if (voiceScreenActive || voiceScreenWasActive || runtimeInitialized) {
viewModel.setVoiceScreenActive(voiceScreenActive)
}
voiceScreenWasActive = voiceScreenActive
}
BackHandler(enabled = activeTab != Tab.Overview) {
@@ -131,117 +159,139 @@ fun ShellScreen(
commandOpen = false
}
Box(modifier = modifier.fillMaxSize()) {
when (activeTab) {
Tab.Overview ->
OverviewScreen(
viewModel = viewModel,
onSelectTab = { activeTab = it },
onOpenSettingsRoute = {
settingsRoute = it
returnToOverviewFromSettings = true
activeTab = Tab.Settings
},
onOpenCommand = { commandOpen = true },
)
Tab.Chat ->
ChatShellScreen(
viewModel = viewModel,
onBack = { activeTab = Tab.Overview },
onVoice = { activeTab = Tab.Voice },
)
Tab.Voice ->
VoiceShellScreen(
viewModel = viewModel,
onOpenCommand = { commandOpen = true },
onOpenGatewaySettings = {
settingsRoute = SettingsRoute.Gateway
returnToOverviewFromSettings = false
activeTab = Tab.Settings
},
onOpenVoiceSettings = {
settingsRoute = SettingsRoute.Voice
returnToOverviewFromSettings = false
activeTab = Tab.Settings
},
)
Tab.ProvidersModels ->
ProvidersModelsScreen(
viewModel = viewModel,
onBack = { activeTab = Tab.Overview },
onAddProvider = {
settingsRoute = SettingsRoute.Gateway
returnToOverviewFromSettings = false
activeTab = Tab.Settings
},
)
Tab.Sessions ->
SessionsScreen(
viewModel = viewModel,
onOpenCommand = { commandOpen = true },
onOpenChat = { activeTab = Tab.Chat },
)
Tab.Settings ->
SettingsShellScreen(
viewModel = viewModel,
route = settingsRoute,
onRouteChange = {
settingsRoute = it
returnToOverviewFromSettings = false
},
onRouteBack = {
settingsRoute = SettingsRoute.Home
if (returnToOverviewFromSettings) {
val density = LocalDensity.current
val keyboardVisible = WindowInsets.ime.getBottom(density) > 0
val showBottomNav = shellBottomNavVisible(keyboardVisible = keyboardVisible, commandOpen = commandOpen)
Scaffold(
modifier = modifier.fillMaxSize(),
containerColor = ClawTheme.colors.canvas,
contentWindowInsets = WindowInsets(0, 0, 0, 0),
bottomBar = {
if (showBottomNav) {
ClawBottomNav(
items = shellNavTabs.map { ClawNavItem(key = it.key, label = it.label, icon = it.icon) },
selectedKey = if (activeTab in shellNavTabs) activeTab.key else Tab.Overview.key,
onSelect = { key ->
val next = shellNavTabs.firstOrNull { it.key == key } ?: Tab.Overview
if (next == Tab.Settings) {
settingsRoute = SettingsRoute.Home
returnToOverviewFromSettings = false
activeTab = Tab.Overview
}
activeTab = next
},
onOpenCommand = { commandOpen = true },
)
}
}
},
) { shellPadding ->
Box(modifier = Modifier.fillMaxSize().padding(shellPadding)) {
when (activeTab) {
Tab.Overview ->
OverviewScreen(
viewModel = viewModel,
onSelectTab = { activeTab = it },
onOpenSettingsRoute = {
settingsRoute = it
returnToOverviewFromSettings = true
activeTab = Tab.Settings
},
onOpenCommand = { commandOpen = true },
)
Tab.Chat ->
ChatShellScreen(
viewModel = viewModel,
onVoice = { activeTab = Tab.Voice },
onOpenSessions = { activeTab = Tab.Sessions },
)
Tab.Voice ->
VoiceShellScreen(
viewModel = viewModel,
onOpenCommand = { commandOpen = true },
onOpenGatewaySettings = {
settingsRoute = SettingsRoute.Gateway
returnToOverviewFromSettings = false
activeTab = Tab.Settings
},
onOpenVoiceSettings = {
settingsRoute = SettingsRoute.Voice
returnToOverviewFromSettings = false
activeTab = Tab.Settings
},
)
Tab.ProvidersModels ->
ProvidersModelsScreen(
viewModel = viewModel,
onBack = { activeTab = Tab.Overview },
)
Tab.Sessions ->
SessionsScreen(
viewModel = viewModel,
onOpenCommand = { commandOpen = true },
onOpenChat = { activeTab = Tab.Chat },
)
Tab.Settings ->
SettingsShellScreen(
viewModel = viewModel,
route = settingsRoute,
onRouteChange = {
settingsRoute = it
returnToOverviewFromSettings = false
},
onRouteBack = {
settingsRoute = SettingsRoute.Home
if (returnToOverviewFromSettings) {
returnToOverviewFromSettings = false
activeTab = Tab.Overview
}
},
onBackHome = { activeTab = Tab.Overview },
onOpenCommand = { commandOpen = true },
)
}
if (commandOpen) {
CommandPalette(
viewModel = viewModel,
onDismiss = { commandOpen = false },
onOpenChat = {
activeTab = Tab.Chat
commandOpen = false
},
onOpenVoice = {
activeTab = Tab.Voice
commandOpen = false
},
onOpenSessions = {
activeTab = Tab.Sessions
commandOpen = false
},
onOpenProviders = {
activeTab = Tab.ProvidersModels
commandOpen = false
},
onOpenSettings = {
settingsRoute = SettingsRoute.Home
returnToOverviewFromSettings = false
activeTab = Tab.Settings
commandOpen = false
},
onOpenSession = { sessionKey ->
viewModel.switchChatSession(sessionKey)
activeTab = Tab.Chat
commandOpen = false
},
)
}
if (commandOpen) {
CommandPalette(
viewModel = viewModel,
onDismiss = { commandOpen = false },
onOpenChat = {
activeTab = Tab.Chat
commandOpen = false
},
onOpenVoice = {
activeTab = Tab.Voice
commandOpen = false
},
onOpenSessions = {
activeTab = Tab.Sessions
commandOpen = false
},
onOpenProviders = {
activeTab = Tab.ProvidersModels
commandOpen = false
},
onOpenSettings = {
settingsRoute = SettingsRoute.Home
returnToOverviewFromSettings = false
activeTab = Tab.Settings
commandOpen = false
},
onOpenSession = { sessionKey ->
viewModel.switchChatSession(sessionKey)
activeTab = Tab.Chat
commandOpen = false
},
)
}
pendingTrust?.let { prompt ->
// Gateway certificate trust is modal across the shell so navigation
// cannot hide a changed TLS identity prompt.
GatewayTrustDialog(
prompt = prompt,
onAccept = viewModel::acceptGatewayTrustPrompt,
onDecline = viewModel::declineGatewayTrustPrompt,
)
pendingTrust?.let { prompt ->
// Gateway certificate trust is modal across the shell so navigation
// cannot hide a changed TLS identity prompt.
GatewayTrustDialog(
prompt = prompt,
onAccept = viewModel::acceptGatewayTrustPrompt,
onDecline = viewModel::declineGatewayTrustPrompt,
)
}
}
}
}
@@ -289,33 +339,39 @@ private fun OverviewScreen(
val isConnected by viewModel.isConnected.collectAsState()
val sessions by viewModel.chatSessions.collectAsState()
val pendingRunCount by viewModel.pendingRunCount.collectAsState()
val statusText by viewModel.statusText.collectAsState()
val models by viewModel.modelCatalog.collectAsState()
val providers by viewModel.modelAuthProviders.collectAsState()
val agents by viewModel.gatewayAgents.collectAsState()
val pendingToolCalls by viewModel.chatPendingToolCalls.collectAsState()
val cronStatus by viewModel.cronStatus.collectAsState()
val usageSummary by viewModel.usageSummary.collectAsState()
val skillsSummary by viewModel.skillsSummary.collectAsState()
val nodesDevicesSummary by viewModel.nodesDevicesSummary.collectAsState()
val channelsSummary by viewModel.channelsSummary.collectAsState()
val readyProviderCount = providers.count { modelProviderReady(it.status) }
val readyProviderCount = providerRows(providers = providers, models = models).count { it.ready }
val attentionRows =
homeAttentionRows(
isConnected = isConnected,
pendingApprovals = pendingToolCalls.size,
channelsSummary = channelsSummary,
nodesDevicesSummary = nodesDevicesSummary,
readyProviderCount = readyProviderCount,
)
LaunchedEffect(isConnected) {
if (isConnected) {
viewModel.refreshChatSessions(limit = 20)
viewModel.refreshModelCatalog()
viewModel.refreshAgents()
viewModel.refreshCronJobs()
viewModel.refreshUsage()
viewModel.refreshSkills()
viewModel.refreshNodesDevices()
viewModel.refreshChannels()
}
}
ClawScaffold(contentPadding = PaddingValues(start = 20.dp, top = 14.dp, end = 20.dp, bottom = 20.dp)) {
ClawScaffold(
contentPadding = PaddingValues(start = 20.dp, top = 14.dp, end = 20.dp, bottom = 6.dp),
contentWindowInsets = shellContentInsets,
) {
Box(modifier = Modifier.fillMaxSize()) {
LazyColumn(verticalArrangement = Arrangement.spacedBy(10.dp), contentPadding = PaddingValues(bottom = 104.dp)) {
LazyColumn(modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.spacedBy(12.dp), contentPadding = PaddingValues(bottom = 4.dp)) {
item {
Row(
modifier = Modifier.fillMaxWidth(),
@@ -334,41 +390,20 @@ private fun OverviewScreen(
}
item {
SectionLabel(title = "MODULES")
CompanionHeroPanel(
statusText = gatewaySummary(statusText, isConnected),
isConnected = isConnected,
pendingRunCount = pendingRunCount,
onOpenChat = { onSelectTab(Tab.Chat) },
onOpenVoice = { onSelectTab(Tab.Voice) },
onOpenGateway = { onOpenSettingsRoute(SettingsRoute.Gateway) },
)
}
item {
ModuleList(
rows =
listOf(
ModuleRow("Chat", null, null, Icons.Outlined.ChatBubbleOutline, Tab.Chat),
ModuleRow("Sessions", null, if (sessions.isEmpty()) "Empty" else "${sessions.size} recent", Icons.Outlined.AccessTime, Tab.Sessions),
ModuleRow("Voice", null, if (isConnected) "Ready" else "Offline", Icons.Outlined.MicNone, Tab.Voice),
ModuleRow(
title = "Providers & Models",
subtitle = null,
metadata =
when {
!isConnected -> "Offline"
readyProviderCount > 0 -> "$readyProviderCount ready"
models.isNotEmpty() -> "${models.size} models"
else -> "Setup"
},
icon = Icons.Outlined.Inventory2,
tab = Tab.ProvidersModels,
),
ModuleRow("Channels", null, channelsSummaryText(channelsSummary), Icons.Default.Notifications, Tab.Settings, SettingsRoute.Channels),
ModuleRow("Agents", null, if (agents.isEmpty()) "Load" else "${agents.size} ready", Icons.Default.Person, Tab.Settings, SettingsRoute.Agents),
ModuleRow("Approvals", null, approvalsSummary(pendingToolCalls.size), Icons.Default.Lock, Tab.Settings, SettingsRoute.Approvals),
ModuleRow("Cron Jobs", null, cronJobsSummary(cronStatus.jobs), Icons.Outlined.AccessTime, Tab.Settings, SettingsRoute.CronJobs),
ModuleRow("Skills", null, skillsSummaryText(skillsSummary.skills), Icons.Default.Settings, Tab.Settings, SettingsRoute.Skills),
ModuleRow("Nodes & Devices", null, nodesDevicesSummaryText(nodesDevicesSummary), Icons.Default.Cloud, Tab.Settings, SettingsRoute.NodesDevices),
ModuleRow("Usage", null, usageSummaryText(usageSummary.providers.size), Icons.Default.Storage, Tab.Settings, SettingsRoute.Usage),
ModuleRow("Settings", null, null, Icons.Outlined.Settings, Tab.Settings, SettingsRoute.Home),
),
onSelectTab = onSelectTab,
onOpenSettingsRoute = onOpenSettingsRoute,
)
if (attentionRows.isNotEmpty()) {
item {
HomeAttentionPanel(rows = attentionRows, onSelectTab = onSelectTab, onOpenSettingsRoute = onOpenSettingsRoute)
}
}
item {
@@ -397,7 +432,7 @@ private fun OverviewScreen(
item {
RecentSessionList(
rows =
sessions.take(7).map { session ->
sessions.take(5).map { session ->
RecentSessionListItem(
key = session.key,
title = displaySessionTitle(session.displayName),
@@ -412,8 +447,38 @@ private fun OverviewScreen(
)
}
}
item {
SectionLabel(title = "Control center")
}
item {
ModuleList(
rows =
listOf(
ModuleRow("Sessions", "Conversation history", if (sessions.isEmpty()) "Empty" else "${sessions.size} recent", Icons.Outlined.AccessTime, Tab.Sessions),
ModuleRow(
title = "Providers & Models",
subtitle = "Provider readiness",
metadata =
when {
!isConnected -> "Offline"
readyProviderCount > 0 -> "$readyProviderCount ready"
else -> "No ready"
},
icon = Icons.Outlined.Inventory2,
tab = Tab.ProvidersModels,
),
ModuleRow("Channels", "Connected messengers", channelsSummaryText(channelsSummary), Icons.Default.Notifications, Tab.Settings, SettingsRoute.Channels),
ModuleRow("Nodes & Devices", "Phone and node health", nodesDevicesSummaryText(nodesDevicesSummary), Icons.Default.Cloud, Tab.Settings, SettingsRoute.NodesDevices),
ModuleRow("Approvals", "Tool decisions", approvalsSummary(pendingToolCalls.size), Icons.Default.Lock, Tab.Settings, SettingsRoute.Approvals),
ModuleRow("Settings", "More runtime controls", null, Icons.Outlined.Settings, Tab.Settings, SettingsRoute.Home),
),
onSelectTab = onSelectTab,
onOpenSettingsRoute = onOpenSettingsRoute,
)
}
}
OverviewChatButton(onClick = { onSelectTab(Tab.Chat) }, modifier = Modifier.align(Alignment.BottomEnd).padding(bottom = 20.dp))
}
}
}
@@ -427,26 +492,110 @@ private data class ModuleRow(
val settingsRoute: SettingsRoute? = null,
)
/** Floating overview shortcut that keeps chat one tap away from module lists. */
@Composable
private fun OverviewChatButton(
onClick: () -> Unit,
modifier: Modifier = Modifier,
private fun CompanionHeroPanel(
statusText: String,
isConnected: Boolean,
pendingRunCount: Int,
onOpenChat: () -> Unit,
onOpenVoice: () -> Unit,
onOpenGateway: () -> Unit,
) {
Surface(
onClick = onClick,
modifier = modifier.height(ClawTheme.spacing.touchTarget),
shape = RoundedCornerShape(ClawTheme.radii.button),
color = ClawTheme.colors.primary,
contentColor = ClawTheme.colors.primaryText,
) {
Row(
modifier = Modifier.padding(horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
Icon(imageVector = Icons.Outlined.ChatBubbleOutline, contentDescription = null, modifier = Modifier.size(18.dp))
Text(text = "Chat", style = ClawTheme.type.label.copy(fontSize = 16.sp, lineHeight = 20.sp))
ClawPanel(contentPadding = PaddingValues(16.dp)) {
Column(verticalArrangement = Arrangement.spacedBy(14.dp)) {
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(10.dp)) {
Surface(
modifier = Modifier.size(38.dp),
shape = CircleShape,
color = if (isConnected) ClawTheme.colors.successSoft else ClawTheme.colors.surfacePressed,
border = BorderStroke(1.dp, if (isConnected) ClawTheme.colors.success else ClawTheme.colors.border),
) {
Box(contentAlignment = Alignment.Center) {
Icon(imageVector = Icons.Outlined.ChatBubbleOutline, contentDescription = null, modifier = Modifier.size(19.dp), tint = if (isConnected) ClawTheme.colors.success else ClawTheme.colors.text)
}
}
Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(3.dp)) {
Text(text = if (pendingRunCount > 0) "OpenClaw is working" else "Ready when you are", style = ClawTheme.type.title.copy(fontSize = 20.sp, lineHeight = 24.sp), color = ClawTheme.colors.text)
Text(text = statusText, style = ClawTheme.type.body, color = ClawTheme.colors.textMuted, maxLines = 1, overflow = TextOverflow.Ellipsis)
}
}
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.spacedBy(9.dp)) {
ClawPrimaryButton(text = "Start chat", icon = Icons.Outlined.ChatBubbleOutline, onClick = onOpenChat, modifier = Modifier.weight(1f))
ClawSecondaryButton(text = "Voice", icon = Icons.Outlined.MicNone, onClick = onOpenVoice, modifier = Modifier.weight(1f))
}
if (!isConnected) {
ClawSecondaryButton(text = "Reconnect gateway", icon = Icons.Default.Cloud, onClick = onOpenGateway, modifier = Modifier.fillMaxWidth())
}
}
}
}
internal data class HomeAttentionRow(
val title: String,
val subtitle: String,
val icon: ImageVector,
val tab: Tab,
val settingsRoute: SettingsRoute? = null,
)
internal fun homeAttentionRows(
isConnected: Boolean,
pendingApprovals: Int,
channelsSummary: GatewayChannelsSummary,
nodesDevicesSummary: GatewayNodesDevicesSummary,
readyProviderCount: Int,
expiringProviderCount: Int = 0,
): List<HomeAttentionRow> =
listOfNotNull(
if (!isConnected) {
HomeAttentionRow("Gateway", "Connect before chat, voice, and live status.", Icons.Default.Cloud, Tab.Settings, SettingsRoute.Gateway)
} else {
null
},
if (pendingApprovals > 0) {
HomeAttentionRow("Approvals", approvalsSummary(pendingApprovals), Icons.Default.Lock, Tab.Settings, SettingsRoute.Approvals)
} else {
null
},
if (channelsSummary.channels.any { it.error != null }) {
HomeAttentionRow("Channels", channelsSummaryText(channelsSummary), Icons.Default.Notifications, Tab.Settings, SettingsRoute.Channels)
} else {
null
},
if (nodesDevicesSummary.pendingDevices.isNotEmpty()) {
HomeAttentionRow("Nodes & Devices", nodesDevicesSummaryText(nodesDevicesSummary), Icons.Default.Cloud, Tab.Settings, SettingsRoute.NodesDevices)
} else {
null
},
if (isConnected && readyProviderCount == 0) {
HomeAttentionRow("Providers", "No ready providers", Icons.Outlined.Inventory2, Tab.Settings, SettingsRoute.Gateway)
} else {
null
},
)
@Composable
private fun HomeAttentionPanel(
rows: List<HomeAttentionRow>,
onSelectTab: (Tab) -> Unit,
onOpenSettingsRoute: (SettingsRoute) -> Unit,
) {
ClawPanel(contentPadding = PaddingValues(horizontal = 14.dp, vertical = 8.dp)) {
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
Text(text = "Needs attention", style = ClawTheme.type.caption.copy(fontSize = 12.5.sp, lineHeight = 16.sp), color = ClawTheme.colors.warning)
rows.forEach { row ->
ModuleListRow(
row = ModuleRow(row.title, row.subtitle, null, row.icon, row.tab, row.settingsRoute),
onClick = {
val route = row.settingsRoute
if (route == null) {
onSelectTab(row.tab)
} else {
onOpenSettingsRoute(route)
}
},
)
}
}
}
}
@@ -527,14 +676,18 @@ private fun ModuleListRow(
horizontalArrangement = Arrangement.spacedBy(9.dp),
) {
Icon(imageVector = row.icon, contentDescription = null, modifier = Modifier.size(20.dp), tint = ClawTheme.colors.text)
Text(
text = row.title,
style = ClawTheme.type.body,
color = ClawTheme.colors.text,
modifier = Modifier.weight(1f),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(1.dp)) {
Text(
text = row.title,
style = ClawTheme.type.body,
color = ClawTheme.colors.text,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
row.subtitle?.let {
Text(text = it, style = ClawTheme.type.caption.copy(fontSize = 12.5.sp, lineHeight = 16.sp), color = ClawTheme.colors.textSubtle, maxLines = 1, overflow = TextOverflow.Ellipsis)
}
}
row.metadata?.let {
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(6.dp)) {
Box(modifier = Modifier.size(4.5.dp).clip(CircleShape).background(statusDotColor(it)))
@@ -638,11 +791,18 @@ private fun RecentSessionRowContent(
@Composable
private fun ChatShellScreen(
viewModel: MainViewModel,
onBack: () -> Unit,
onVoice: () -> Unit,
onOpenSessions: () -> Unit,
) {
ClawScaffold(contentPadding = PaddingValues(start = 0.dp, top = 8.dp, end = 0.dp, bottom = 8.dp)) {
ChatScreen(viewModel = viewModel, onBack = onBack, onVoice = onVoice)
ClawScaffold(
contentPadding = PaddingValues(start = 0.dp, top = 8.dp, end = 0.dp, bottom = 0.dp),
contentWindowInsets = shellContentInsets,
) {
ChatScreen(
viewModel = viewModel,
onVoice = onVoice,
onOpenSessions = onOpenSessions,
)
}
}
@@ -653,7 +813,10 @@ private fun VoiceShellScreen(
onOpenGatewaySettings: () -> Unit,
onOpenVoiceSettings: () -> Unit,
) {
ClawScaffold(contentPadding = PaddingValues(start = 0.dp, top = 8.dp, end = 0.dp, bottom = 8.dp)) {
ClawScaffold(
contentPadding = PaddingValues(start = 0.dp, top = 8.dp, end = 0.dp, bottom = 0.dp),
contentWindowInsets = shellContentInsets,
) {
VoiceScreen(
viewModel = viewModel,
onOpenCommand = onOpenCommand,
@@ -669,6 +832,7 @@ private fun SettingsShellScreen(
route: SettingsRoute,
onRouteChange: (SettingsRoute) -> Unit,
onRouteBack: () -> Unit,
onBackHome: () -> Unit,
onOpenCommand: () -> Unit,
) {
val displayName by viewModel.displayName.collectAsState()
@@ -707,14 +871,18 @@ private fun SettingsShellScreen(
return
}
ClawScaffold(contentPadding = PaddingValues(start = 20.dp, top = 14.dp, end = 20.dp, bottom = 20.dp)) {
LazyColumn(verticalArrangement = Arrangement.spacedBy(13.dp)) {
ClawScaffold(
contentPadding = PaddingValues(start = 20.dp, top = 14.dp, end = 20.dp, bottom = 6.dp),
contentWindowInsets = shellContentInsets,
) {
LazyColumn(modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.spacedBy(13.dp), contentPadding = PaddingValues(bottom = 4.dp)) {
item {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(9.dp),
) {
PlainIconButton(icon = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back to home", onClick = onBackHome)
Text(text = "Settings", style = ClawTheme.type.title.copy(fontSize = 16.sp, lineHeight = 20.sp), color = ClawTheme.colors.text, modifier = Modifier.weight(1f))
SettingsSearchButton(onClick = onOpenCommand)
}

View File

@@ -97,6 +97,7 @@ fun VoiceScreen(
val talkModeEnabled by viewModel.talkModeEnabled.collectAsState()
val talkModeListening by viewModel.talkModeListening.collectAsState()
val talkModeSpeaking by viewModel.talkModeSpeaking.collectAsState()
val talkModeStatusText by viewModel.talkModeStatusText.collectAsState()
val talkModeConversation by viewModel.talkModeConversation.collectAsState()
var pendingAction by remember { mutableStateOf<VoiceAction?>(null) }
@@ -119,6 +120,16 @@ fun VoiceScreen(
val activeConversation = if (voiceCaptureMode == VoiceCaptureMode.TalkMode) talkModeConversation else micConversation
val voiceActive = micEnabled || micIsSending || talkModeEnabled
val gatewayReady = gatewayStatus.isVoiceGatewayReady()
val voiceAttentionStatus =
voiceAttentionStatus(
talkModeStatusText = talkModeStatusText,
voiceCaptureMode = voiceCaptureMode,
micEnabled = micEnabled,
micIsSending = micIsSending,
talkModeEnabled = talkModeEnabled,
talkModeListening = talkModeListening,
talkModeSpeaking = talkModeSpeaking,
)
val activeStatus =
voiceStatusLabel(
gatewayStatus = gatewayStatus,
@@ -128,6 +139,7 @@ fun VoiceScreen(
micIsSending = micIsSending,
talkModeListening = talkModeListening,
talkModeSpeaking = talkModeSpeaking,
voiceAttentionStatus = voiceAttentionStatus,
)
if (talkModeEnabled) {
@@ -169,7 +181,7 @@ fun VoiceScreen(
verticalArrangement = Arrangement.spacedBy(10.dp),
) {
VoiceHeader(
statusText = if (voiceActive || !gatewayReady) activeStatus else "Your voice command center.",
statusText = voiceAttentionStatus ?: if (voiceActive || !gatewayReady) activeStatus else "Your voice command center.",
speakerEnabled = speakerEnabled,
onToggleSpeaker = { viewModel.setSpeakerEnabled(!speakerEnabled) },
onOpenCommand = onOpenCommand,
@@ -184,6 +196,7 @@ fun VoiceScreen(
talkModeSpeaking = talkModeSpeaking,
micLiveTranscript = micLiveTranscript,
gatewayReady = gatewayReady,
voiceAttentionStatus = voiceAttentionStatus,
onStartTalk = {
runVoiceAction(
action = VoiceAction.Talk,
@@ -242,7 +255,9 @@ private fun DictationScreen(
) {
val lastUserText = conversation.lastOrNull { it.role == VoiceConversationRole.User }?.text
val draftText = liveTranscript?.takeIf { it.isNotBlank() } ?: lastUserText.orEmpty()
val speechProviderReady = gatewayStatus.isVoiceGatewayReady()
val providerAttentionStatus = voiceRuntimeAttentionStatus(statusText)
val displayStatusText = providerAttentionStatus ?: statusText
val speechProviderReady = providerAttentionStatus == null && gatewayStatus.isVoiceGatewayReady()
Column(
modifier =
Modifier
@@ -278,7 +293,7 @@ private fun DictationScreen(
DictationWaveform(active = listening || sending)
Row(horizontalArrangement = Arrangement.spacedBy(7.dp), verticalAlignment = Alignment.CenterVertically) {
Icon(imageVector = Icons.Default.Mic, contentDescription = null, modifier = Modifier.size(15.dp), tint = if (listening) ClawTheme.colors.success else ClawTheme.colors.textMuted)
Text(text = statusText, style = ClawTheme.type.body, color = ClawTheme.colors.textMuted)
Text(text = displayStatusText, style = ClawTheme.type.body, color = ClawTheme.colors.textMuted)
}
}
}
@@ -298,13 +313,20 @@ private fun DictationScreen(
}
Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(2.dp)) {
Text(text = "Speech provider", style = ClawTheme.type.section, color = ClawTheme.colors.text)
Text(text = gatewayStatus.voiceGatewayLabel(), style = ClawTheme.type.body, color = ClawTheme.colors.textMuted, maxLines = 1, overflow = TextOverflow.Ellipsis)
Text(
text = providerAttentionStatus ?: gatewayStatus.voiceGatewayLabel(),
style = ClawTheme.type.body,
color = ClawTheme.colors.textMuted,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(6.dp)) {
Text(
text =
when {
sending -> "Sending"
providerAttentionStatus != null -> "Attention"
speechProviderReady -> "Ready"
else -> "Offline"
},
@@ -312,6 +334,7 @@ private fun DictationScreen(
color =
when {
sending -> ClawTheme.colors.warning
providerAttentionStatus != null -> ClawTheme.colors.warning
speechProviderReady -> ClawTheme.colors.success
else -> ClawTheme.colors.textMuted
},
@@ -324,6 +347,7 @@ private fun DictationScreen(
.background(
when {
sending -> ClawTheme.colors.warning
providerAttentionStatus != null -> ClawTheme.colors.warning
speechProviderReady -> ClawTheme.colors.success
else -> ClawTheme.colors.textSubtle
},
@@ -594,6 +618,7 @@ private fun VoiceHero(
talkModeSpeaking: Boolean,
micLiveTranscript: String?,
gatewayReady: Boolean,
voiceAttentionStatus: String?,
onStartTalk: () -> Unit,
onStartDictation: () -> Unit,
onConnectGateway: () -> Unit,
@@ -616,6 +641,7 @@ private fun VoiceHero(
Text(
text =
when {
voiceAttentionStatus != null -> voiceAttentionStatus
talkModeSpeaking -> "OpenClaw is replying"
talkModeListening -> "Listening"
talkModeEnabled -> "Talk is live"
@@ -672,7 +698,7 @@ private fun VoiceHero(
)
}
VoiceProviderCard(gatewayStatus = gatewayStatus)
VoiceProviderCard(gatewayStatus = gatewayStatus, voiceAttentionStatus = voiceAttentionStatus)
VoicePrimaryAction(
text =
@@ -734,8 +760,11 @@ private fun VoiceModeRow(
}
@Composable
private fun VoiceProviderCard(gatewayStatus: String) {
val ready = gatewayStatus.isVoiceGatewayReady()
private fun VoiceProviderCard(
gatewayStatus: String,
voiceAttentionStatus: String?,
) {
val ready = voiceAttentionStatus == null && gatewayStatus.isVoiceGatewayReady()
Surface(
modifier = Modifier.fillMaxWidth().heightIn(min = 58.dp),
shape = RoundedCornerShape(ClawTheme.radii.panel),
@@ -761,7 +790,13 @@ private fun VoiceProviderCard(gatewayStatus: String) {
}
Column(modifier = Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(2.dp)) {
Text(text = "Provider", style = ClawTheme.type.body, color = ClawTheme.colors.text, maxLines = 1)
Text(text = gatewayStatus.voiceGatewayLabel(), style = ClawTheme.type.caption, color = ClawTheme.colors.textMuted, maxLines = 1, overflow = TextOverflow.Ellipsis)
Text(
text = voiceAttentionStatus ?: gatewayStatus.voiceGatewayLabel(),
style = ClawTheme.type.caption,
color = ClawTheme.colors.textMuted,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(7.dp)) {
Box(
@@ -769,9 +804,25 @@ private fun VoiceProviderCard(gatewayStatus: String) {
Modifier
.size(7.dp)
.clip(CircleShape)
.background(if (ready) ClawTheme.colors.success else ClawTheme.colors.textSubtle),
.background(
when {
ready -> ClawTheme.colors.success
voiceAttentionStatus != null -> ClawTheme.colors.warning
else -> ClawTheme.colors.textSubtle
},
),
)
Text(
text =
when {
ready -> "Ready"
voiceAttentionStatus != null -> "Attention"
else -> "Offline"
},
style = ClawTheme.type.caption,
color = ClawTheme.colors.textMuted,
maxLines = 1,
)
Text(text = if (ready) "Ready" else "Offline", style = ClawTheme.type.caption, color = ClawTheme.colors.textMuted, maxLines = 1)
}
}
}
@@ -968,7 +1019,7 @@ private fun runVoiceAction(
}
}
private fun voiceStatusLabel(
internal fun voiceStatusLabel(
gatewayStatus: String,
voiceCaptureMode: VoiceCaptureMode,
micStatusText: String,
@@ -976,8 +1027,10 @@ private fun voiceStatusLabel(
micIsSending: Boolean,
talkModeListening: Boolean,
talkModeSpeaking: Boolean,
voiceAttentionStatus: String?,
): String =
when {
voiceAttentionStatus != null -> voiceAttentionStatus
voiceCaptureMode == VoiceCaptureMode.TalkMode && talkModeSpeaking -> "OpenClaw is speaking"
voiceCaptureMode == VoiceCaptureMode.TalkMode && talkModeListening -> "Listening"
voiceCaptureMode == VoiceCaptureMode.TalkMode -> "Talk is live"
@@ -988,6 +1041,69 @@ private fun voiceStatusLabel(
else -> "Ready to talk"
}
internal fun voiceAttentionStatus(
talkModeStatusText: String,
voiceCaptureMode: VoiceCaptureMode,
micEnabled: Boolean,
micIsSending: Boolean,
talkModeEnabled: Boolean,
talkModeListening: Boolean,
talkModeSpeaking: Boolean,
): String? {
if (voiceCaptureMode != VoiceCaptureMode.Off || micEnabled || micIsSending) return null
if (talkModeEnabled || talkModeListening || talkModeSpeaking) return null
val status = talkModeStatusText.trim()
if (status.isBlank()) return null
val lower = status.lowercase()
if (lower == "off" || lower == "ready" || lower == "listening" || lower == "connecting…") return null
return status
.takeIf {
lower.contains("failed") ||
lower.contains("unavailable") ||
lower.contains("permission required") ||
lower.contains("not connected") ||
lower.contains("error")
}?.let(::userFacingVoiceAttentionStatus)
}
internal fun voiceRuntimeAttentionStatus(statusText: String): String? {
val status = statusText.trim()
if (status.isBlank()) return null
val lower = status.lowercase()
return status
.takeIf {
lower.contains("transcription unavailable") ||
lower.contains("provider unavailable") ||
(lower.contains("provider") && lower.contains("not configured")) ||
lower.contains("no realtime transcription provider") ||
lower.contains("failed")
}?.let(::userFacingVoiceAttentionStatus)
}
private fun userFacingVoiceAttentionStatus(status: String): String {
val normalized =
status
.removePrefix("Start failed:")
.trim()
.removePrefix("Transcription unavailable:")
.trim()
.removePrefix("UNAVAILABLE:")
.trim()
.removePrefix("Error:")
.trim()
val lower = normalized.lowercase()
if (lower.contains("realtime voice provider") && lower.contains("not configured")) {
return "Realtime voice provider is not configured."
}
if (lower.contains("no realtime transcription provider")) {
return "Realtime transcription provider is not configured."
}
if (lower.contains("microphone permission required")) {
return "Microphone permission is required."
}
return if (normalized.length <= 90) normalized else "${normalized.take(87)}..."
}
private fun String.isVoiceGatewayReady(): Boolean {
val status = lowercase()
return !status.contains("offline") && !status.contains("not connected") && !status.contains("failed") && !status.contains("error")

View File

@@ -15,7 +15,7 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.CircularProgressIndicator
@@ -40,17 +40,19 @@ fun ChatMessageListCard(
modifier: Modifier = Modifier,
) {
val listState = rememberLazyListState()
val displayMessages = remember(messages) { messages.asReversed() }
val stream = streamingAssistantText?.trim()
val timeline =
remember(messages, pendingRunCount, pendingToolCalls, streamingAssistantText) {
buildChatTimeline(
messages = messages,
pendingRunCount = pendingRunCount,
pendingToolCalls = pendingToolCalls,
streamingAssistantText = streamingAssistantText,
)
}
// New list items/tool rows should animate into view, but token streaming should not restart
// that animation on every delta.
LaunchedEffect(messages.size, pendingRunCount, pendingToolCalls.size) {
listState.animateScrollToItem(index = 0)
}
LaunchedEffect(stream) {
if (!stream.isNullOrEmpty()) {
listState.scrollToItem(index = 0)
LaunchedEffect(timeline.scrollTargetIndex, timeline.items.size, pendingRunCount, pendingToolCalls.size) {
timeline.scrollTargetIndex?.let { index ->
listState.animateScrollToItem(index = index)
}
}
@@ -64,32 +66,17 @@ fun ChatMessageListCard(
androidx.compose.foundation.layout
.PaddingValues(bottom = 8.dp),
) {
// With reverseLayout = true, index 0 renders at the BOTTOM.
// So we emit newest items first: streaming → tools → typing → messages (newest→oldest).
if (!stream.isNullOrEmpty()) {
item(key = "stream") {
ChatStreamingAssistantBubble(text = stream)
itemsIndexed(items = timeline.items, key = { _, item -> chatTimelineItemKey(item) }) { _, item ->
when (item) {
is ChatTimelineItem.Message -> ChatMessageBubble(message = item.message)
is ChatTimelineItem.PendingTools -> ChatPendingToolsBubble(toolCalls = item.toolCalls)
is ChatTimelineItem.StreamingAssistant -> ChatStreamingAssistantBubble(text = item.text)
ChatTimelineItem.Thinking -> ChatTypingIndicatorBubble()
}
}
if (pendingToolCalls.isNotEmpty()) {
item(key = "tools") {
ChatPendingToolsBubble(toolCalls = pendingToolCalls)
}
}
if (pendingRunCount > 0) {
item(key = "typing") {
ChatTypingIndicatorBubble()
}
}
items(items = displayMessages, key = { it.id }) { message ->
ChatMessageBubble(message = message)
}
}
if (messages.isEmpty() && pendingRunCount == 0 && pendingToolCalls.isEmpty() && streamingAssistantText.isNullOrBlank()) {
if (timeline.items.isEmpty()) {
if (historyLoading) {
LoadingChatHint(modifier = Modifier.align(Alignment.Center))
} else {

View File

@@ -4,6 +4,7 @@ import ai.openclaw.app.MainViewModel
import ai.openclaw.app.chat.ChatMessage
import ai.openclaw.app.chat.ChatMessageContent
import ai.openclaw.app.chat.ChatPendingToolCall
import ai.openclaw.app.chat.ChatSessionEntry
import ai.openclaw.app.chat.OutgoingAttachment
import ai.openclaw.app.ui.design.ClawListItem
import ai.openclaw.app.ui.design.ClawLoadingState
@@ -30,18 +31,18 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.automirrored.filled.Send
import androidx.compose.material.icons.filled.AttachFile
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Mic
import androidx.compose.material.icons.filled.MoreHoriz
import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
@@ -78,8 +79,8 @@ import java.util.Locale
@Composable
fun ChatScreen(
viewModel: MainViewModel,
onBack: () -> Unit,
onVoice: () -> Unit,
onOpenSessions: () -> Unit,
) {
val messages by viewModel.chatMessages.collectAsState()
val historyLoading by viewModel.chatHistoryLoading.collectAsState()
@@ -158,13 +159,23 @@ fun ChatScreen(
thinkingLevel = thinkingLevel,
healthOk = healthOk,
pendingRunCount = pendingRunCount,
onBack = onBack,
onMore = {
viewModel.refreshChat()
viewModel.refreshChatSessions(limit = 100)
},
)
ChatSessionSwitcher(
sessionKey = sessionKey,
sessions = sessions,
mainSessionKey = mainSessionKey,
onSelectSession = { key ->
viewModel.switchChatSession(key)
viewModel.refreshChatSessions(limit = 100)
},
onOpenSessions = onOpenSessions,
)
errorText?.takeIf { it.isNotBlank() }?.let { error ->
ChatNotice(title = "Chat needs attention", body = userFacingChatError(error))
}
@@ -214,13 +225,88 @@ fun ChatScreen(
}
}
@Composable
private fun ChatSessionSwitcher(
sessionKey: String,
sessions: List<ChatSessionEntry>,
mainSessionKey: String,
onSelectSession: (String) -> Unit,
onOpenSessions: () -> Unit,
) {
val choices =
remember(sessionKey, sessions, mainSessionKey) {
resolveCompactSessionChoices(
currentSessionKey = sessionKey,
sessions = sessions,
mainSessionKey = mainSessionKey,
)
}
if (choices.size <= 1 && sessions.size <= 1) return
Row(
modifier = Modifier.fillMaxWidth().horizontalScroll(rememberScrollState()),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(6.dp),
) {
choices.forEach { entry ->
ChatSessionChip(
text = chatSessionChipText(entry = entry, mainSessionKey = mainSessionKey),
active = isActiveSessionChoice(entry.key, sessionKey, mainSessionKey),
onClick = { onSelectSession(entry.key) },
)
}
if (sessions.size > choices.size) {
Surface(
onClick = onOpenSessions,
modifier = Modifier.heightIn(min = 36.dp),
shape = RoundedCornerShape(ClawTheme.radii.pill),
color = ClawTheme.colors.canvas,
contentColor = ClawTheme.colors.textMuted,
border = BorderStroke(1.dp, ClawTheme.colors.border),
) {
Row(
modifier = Modifier.padding(horizontal = 10.dp, vertical = 7.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(5.dp),
) {
Icon(imageVector = Icons.Default.MoreHoriz, contentDescription = null, modifier = Modifier.size(16.dp))
Text(text = "All", style = ClawTheme.type.caption, maxLines = 1)
}
}
}
}
}
@Composable
private fun ChatSessionChip(
text: String,
active: Boolean,
onClick: () -> Unit,
) {
Surface(
onClick = onClick,
modifier = Modifier.heightIn(min = 36.dp),
shape = RoundedCornerShape(ClawTheme.radii.pill),
color = if (active) ClawTheme.colors.primary else ClawTheme.colors.surfaceRaised,
contentColor = if (active) ClawTheme.colors.primaryText else ClawTheme.colors.text,
border = BorderStroke(1.dp, if (active) ClawTheme.colors.primary else ClawTheme.colors.border),
) {
Text(
text = text,
modifier = Modifier.padding(horizontal = 11.dp, vertical = 7.dp),
style = ClawTheme.type.caption,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
}
@Composable
private fun ChatHeader(
sessionTitle: String,
thinkingLevel: String,
healthOk: Boolean,
pendingRunCount: Int,
onBack: () -> Unit,
onMore: () -> Unit,
) {
Row(
@@ -228,7 +314,7 @@ private fun ChatHeader(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(6.dp),
) {
HeaderIcon(icon = Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back", onClick = onBack)
Box(modifier = Modifier.size(ClawTheme.spacing.touchTarget))
Column(
modifier = Modifier.weight(1f),
@@ -320,15 +406,19 @@ private fun ChatMessageList(
modifier: Modifier = Modifier,
) {
val listState = rememberLazyListState()
val displayMessages = remember(messages) { messages.asReversed() }
val stream = streamingAssistantText?.trim()
val timeline =
remember(messages, pendingRunCount, pendingToolCalls, streamingAssistantText) {
buildChatTimeline(
messages = messages,
pendingRunCount = pendingRunCount,
pendingToolCalls = pendingToolCalls,
streamingAssistantText = streamingAssistantText,
)
}
LaunchedEffect(messages.size, pendingRunCount, pendingToolCalls.size) {
listState.animateScrollToItem(index = 0)
}
LaunchedEffect(stream) {
if (!stream.isNullOrEmpty()) {
listState.scrollToItem(index = 0)
LaunchedEffect(timeline.scrollTargetIndex, timeline.items.size, pendingRunCount, pendingToolCalls.size) {
timeline.scrollTargetIndex?.let { index ->
listState.animateScrollToItem(index = index)
}
}
@@ -340,30 +430,29 @@ private fun ChatMessageList(
verticalArrangement = Arrangement.spacedBy(5.dp),
contentPadding = PaddingValues(top = 6.dp, bottom = 3.dp),
) {
if (!stream.isNullOrEmpty()) {
item(key = "stream") {
ChatBubble(role = "assistant", live = true, content = listOf(ChatMessageContent(text = stream)), timestampMs = null)
itemsIndexed(items = timeline.items, key = { _, item -> chatTimelineItemKey(item) }) { _, item ->
when (item) {
is ChatTimelineItem.Message ->
ChatBubble(
role = item.message.role,
live = false,
content = item.message.content,
timestampMs = item.message.timestampMs,
)
is ChatTimelineItem.PendingTools -> ToolBubble(toolCalls = item.toolCalls)
is ChatTimelineItem.StreamingAssistant ->
ChatBubble(
role = "assistant",
live = true,
content = listOf(ChatMessageContent(text = item.text)),
timestampMs = null,
)
ChatTimelineItem.Thinking -> ChatThinkingBubble()
}
}
if (pendingToolCalls.isNotEmpty()) {
item(key = "tools") {
ToolBubble(toolCalls = pendingToolCalls)
}
}
if (pendingRunCount > 0) {
item(key = "thinking") {
ChatThinkingBubble()
}
}
items(items = displayMessages, key = { it.id }) { message ->
ChatBubble(role = message.role, live = false, content = message.content, timestampMs = message.timestampMs)
}
}
if (messages.isEmpty() && pendingRunCount == 0 && pendingToolCalls.isEmpty() && stream.isNullOrBlank()) {
if (timeline.items.isEmpty()) {
if (historyLoading) {
ClawLoadingState(title = "Loading session", modifier = Modifier.align(Alignment.Center))
} else {
@@ -786,13 +875,33 @@ private fun AttachmentChip(
private fun currentSessionTitle(
sessionKey: String,
sessions: List<ai.openclaw.app.chat.ChatSessionEntry>,
sessions: List<ChatSessionEntry>,
): String {
val entry = sessions.firstOrNull { it.key == sessionKey }
val name = entry?.displayName?.takeIf { it.isNotBlank() } ?: return "New chat"
return friendlySessionName(name)
}
private fun chatSessionChipText(
entry: ChatSessionEntry,
mainSessionKey: String,
): String {
val mainKey = mainSessionKey.trim().ifEmpty { "main" }
if (entry.key == mainKey || (entry.key == "main" && mainKey == "main")) return "Main"
val name = entry.displayName?.takeIf { it.isNotBlank() } ?: entry.key.takeIf { entry.updatedAtMs != null } ?: "Current"
return friendlySessionName(name)
}
private fun isActiveSessionChoice(
choiceKey: String,
sessionKey: String,
mainSessionKey: String,
): Boolean {
val mainKey = mainSessionKey.trim().ifEmpty { "main" }
val current = sessionKey.trim().let { if (it == "main" && mainKey != "main") mainKey else it }
return choiceKey == current
}
@Composable
private fun SendButton(
enabled: Boolean,

View File

@@ -0,0 +1,69 @@
package ai.openclaw.app.ui.chat
import ai.openclaw.app.chat.ChatMessage
import ai.openclaw.app.chat.ChatPendingToolCall
internal sealed class ChatTimelineItem {
data class Message(
val message: ChatMessage,
) : ChatTimelineItem()
data class StreamingAssistant(
val text: String,
) : ChatTimelineItem()
data class PendingTools(
val toolCalls: List<ChatPendingToolCall>,
) : ChatTimelineItem()
object Thinking : ChatTimelineItem()
}
internal data class ChatTimeline(
val items: List<ChatTimelineItem>,
val scrollTargetIndex: Int?,
)
internal fun buildChatTimeline(
messages: List<ChatMessage>,
pendingRunCount: Int,
pendingToolCalls: List<ChatPendingToolCall>,
streamingAssistantText: String?,
): ChatTimeline {
val stream = streamingAssistantText?.trim()?.takeIf { it.isNotEmpty() }
val hasActiveRun = pendingRunCount > 0 || pendingToolCalls.isNotEmpty() || stream != null
val items =
buildList {
if (stream != null) add(ChatTimelineItem.StreamingAssistant(stream))
if (pendingToolCalls.isNotEmpty()) add(ChatTimelineItem.PendingTools(pendingToolCalls))
if (pendingRunCount > 0) add(ChatTimelineItem.Thinking)
messages.asReversed().forEach { message -> add(ChatTimelineItem.Message(message)) }
}
if (items.isEmpty()) return ChatTimeline(items = items, scrollTargetIndex = null)
// In reverseLayout, index 0 is bottom-most. During an active run, keep the prompt
// anchored so streaming/tool rows do not immediately push the just-sent message away.
val activePromptIndex =
if (hasActiveRun) {
items.indexOfFirst { item ->
item is ChatTimelineItem.Message &&
item.message.role
.trim()
.equals("user", ignoreCase = true)
}
} else {
-1
}
return ChatTimeline(
items = items,
scrollTargetIndex = activePromptIndex.takeIf { it >= 0 } ?: 0,
)
}
internal fun chatTimelineItemKey(item: ChatTimelineItem): String =
when (item) {
is ChatTimelineItem.Message -> "message:${item.message.id}"
is ChatTimelineItem.PendingTools -> "tools"
is ChatTimelineItem.StreamingAssistant -> "stream"
ChatTimelineItem.Thinking -> "thinking"
}

View File

@@ -4,22 +4,9 @@ import ai.openclaw.app.chat.ChatSessionEntry
private const val RECENT_WINDOW_MS = 24 * 60 * 60 * 1000L
/**
* Derive a human-friendly label from a raw session key.
* Examples:
* "telegram:g-agent-main-main" -> "Main"
* "agent:main:main" -> "Main"
* "discord:g-server-channel" -> "Server Channel"
* "my-custom-session" -> "My Custom Session"
*/
fun friendlySessionName(key: String): String {
// Strip common prefixes like "telegram:", "agent:", "discord:" etc.
val stripped = key.substringAfterLast(":")
// Remove leading "g-" prefix (gateway artifact)
val cleaned = if (stripped.startsWith("g-")) stripped.removePrefix("g-") else stripped
// Split on hyphens/underscores, title-case each word, collapse "main main" -> "Main"
val words =
cleaned
.split('-', '_')
@@ -78,3 +65,34 @@ fun resolveSessionChoices(
return result
}
fun resolveCompactSessionChoices(
currentSessionKey: String,
sessions: List<ChatSessionEntry>,
mainSessionKey: String,
nowMs: Long = System.currentTimeMillis(),
maxOptions: Int = 5,
): List<ChatSessionEntry> {
val allChoices =
resolveSessionChoices(
currentSessionKey = currentSessionKey,
sessions = sessions,
mainSessionKey = mainSessionKey,
nowMs = nowMs,
)
val mainKey = mainSessionKey.trim().ifEmpty { "main" }
val current = currentSessionKey.trim().let { if (it == "main" && mainKey != "main") mainKey else it }
val pinnedRank =
listOf(mainKey, current)
.filter { it.isNotBlank() }
.distinct()
.withIndex()
.associate { it.value to it.index }
val unpinnedRank = pinnedRank.size
return allChoices
.withIndex()
.sortedWith(compareBy({ pinnedRank[it.value.key] ?: unpinnedRank }, { it.index }))
.take(maxOptions)
.map { it.value }
}

View File

@@ -61,6 +61,7 @@ internal enum class ClawStatus {
internal fun ClawScaffold(
modifier: Modifier = Modifier,
contentPadding: PaddingValues = PaddingValues(horizontal = ClawTheme.spacing.lg, vertical = ClawTheme.spacing.lg),
contentWindowInsets: WindowInsets = WindowInsets.safeDrawing,
content: @Composable () -> Unit,
) {
Box(
@@ -68,7 +69,7 @@ internal fun ClawScaffold(
modifier
.fillMaxSize()
.background(ClawTheme.colors.canvas)
.windowInsetsPadding(WindowInsets.safeDrawing)
.windowInsetsPadding(contentWindowInsets)
.padding(contentPadding),
) {
content()

View File

@@ -1,6 +1,7 @@
package ai.openclaw.app.ui.design
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -91,27 +92,29 @@ internal fun ClawBottomNav(
) {
val safeInsets = WindowInsets.navigationBars.only(androidx.compose.foundation.layout.WindowInsetsSides.Bottom)
Surface(
modifier = modifier.fillMaxWidth(),
color = ClawTheme.colors.surface.copy(alpha = 0.96f),
border = BorderStroke(1.dp, ClawTheme.colors.border),
shape = RoundedCornerShape(topStart = ClawTheme.radii.sheet, topEnd = ClawTheme.radii.sheet),
) {
Row(
modifier =
Modifier
.windowInsetsPadding(safeInsets)
.padding(horizontal = 8.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp),
Box(modifier = modifier.fillMaxWidth().background(ClawTheme.colors.canvas)) {
Surface(
modifier = Modifier.fillMaxWidth(),
color = ClawTheme.colors.surface.copy(alpha = 0.96f),
border = BorderStroke(1.dp, ClawTheme.colors.border),
shape = RoundedCornerShape(topStart = ClawTheme.radii.sheet, topEnd = ClawTheme.radii.sheet),
) {
items.forEach { item ->
ClawBottomNavItem(
item = item,
selected = item.key == selectedKey,
onClick = { onSelect(item.key) },
modifier = Modifier.weight(1f),
)
Row(
modifier =
Modifier
.windowInsetsPadding(safeInsets)
.padding(horizontal = 8.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
items.forEach { item ->
ClawBottomNavItem(
item = item,
selected = item.key == selectedKey,
onClick = { onSelect(item.key) },
modifier = Modifier.weight(1f),
)
}
}
}
}
@@ -129,7 +132,7 @@ private fun ClawBottomNavItem(
modifier = modifier.heightIn(min = 48.dp),
shape = RoundedCornerShape(ClawTheme.radii.control),
color = if (selected) ClawTheme.colors.primary else Color.Transparent,
contentColor = if (selected) ClawTheme.colors.primaryText else ClawTheme.colors.textSubtle,
contentColor = if (selected) ClawTheme.colors.primaryText else ClawTheme.colors.textMuted,
) {
Column(
modifier = Modifier.padding(horizontal = 5.dp, vertical = 6.dp),

View File

@@ -104,6 +104,7 @@ class MicCaptureManager(
private val messageQueue = ArrayDeque<String>()
private val messageQueueLock = Any()
private var flushedPartialTranscript: String? = null
// Correlates chat events with the idempotency key generated before sendChat returns.
private var pendingRunId: String? = null
private var pendingAssistantEntryId: String? = null

View File

@@ -168,6 +168,7 @@ class TalkModeManager internal constructor(
@Volatile private var realtimeSessionId: String? = null
private var realtimeCaptureJob: Job? = null
private var realtimeAppendJob: Job? = null
// Realtime tool calls can complete before their chat final arrives; cache by call/run id until both sides meet.
private val realtimeToolRuns = LinkedHashMap<String, RealtimeToolRun>()
private val pendingRealtimeToolCalls = LinkedHashSet<String>()

View File

@@ -0,0 +1,67 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="120dp"
android:height="120dp"
android:viewportWidth="120"
android:viewportHeight="120">
<path android:pathData="M60,10 C30,10 15,35 15,55 C15,75 30,95 45,100 L45,110 L55,110 L55,100 C55,100 60,102 65,100 L65,110 L75,110 L75,100 C90,95 105,75 105,55 C105,35 90,10 60,10Z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="120"
android:endY="120"
android:startColor="#ff4d4d"
android:startX="0"
android:startY="0"
android:type="linear"
android:endColor="#991b1b" />
</aapt:attr>
</path>
<path android:pathData="M20,45 C5,40 0,50 5,60 C10,70 20,65 25,55 C28,48 25,45 20,45Z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="120"
android:endY="120"
android:startColor="#ff4d4d"
android:startX="0"
android:startY="0"
android:type="linear"
android:endColor="#991b1b" />
</aapt:attr>
</path>
<path android:pathData="M100,45 C115,40 120,50 115,60 C110,70 100,65 95,55 C92,48 95,45 100,45Z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="120"
android:endY="120"
android:startColor="#ff4d4d"
android:startX="0"
android:startY="0"
android:type="linear"
android:endColor="#991b1b" />
</aapt:attr>
</path>
<path
android:fillColor="@android:color/transparent"
android:pathData="M45,15 Q35,5 30,8"
android:strokeColor="#ff4d4d"
android:strokeLineCap="round"
android:strokeWidth="3" />
<path
android:fillColor="@android:color/transparent"
android:pathData="M75,15 Q85,5 90,8"
android:strokeColor="#ff4d4d"
android:strokeLineCap="round"
android:strokeWidth="3" />
<path
android:fillColor="#050810"
android:pathData="M45,35 m-6,0 a6,6 0,1 0,12 0 a6,6 0,1 0,-12 0" />
<path
android:fillColor="#050810"
android:pathData="M75,35 m-6,0 a6,6 0,1 0,12 0 a6,6 0,1 0,-12 0" />
<path
android:fillColor="#00e5cc"
android:pathData="M46,34 m-2.5,0 a2.5,2.5 0,1 0,5 0 a2.5,2.5 0,1 0,-5 0" />
<path
android:fillColor="#00e5cc"
android:pathData="M76,34 m-2.5,0 a2.5,2.5 0,1 0,5 0 a2.5,2.5 0,1 0,-5 0" />
</vector>

View File

@@ -294,6 +294,38 @@ class GatewayBootstrapAuthTest {
assertEquals("aaaaaaaa", prefs.loadGatewayTlsFingerprint(endpoint.stableId))
}
@Test
fun refreshGatewayConnection_reconnectsSavedManualEndpointAfterDisconnect() {
val app = RuntimeEnvironment.getApplication()
val securePrefs =
app.getSharedPreferences(
"openclaw.node.secure.test.${UUID.randomUUID()}",
android.content.Context.MODE_PRIVATE,
)
val prefs = SecurePrefs(app, securePrefsOverride = securePrefs)
prefs.setManualEnabled(true)
prefs.setManualHost("127.0.0.1")
prefs.setManualPort(18789)
prefs.setManualTls(false)
prefs.setGatewayToken("shared-token")
val runtime = NodeRuntime(app, prefs)
runtime.connect(
GatewayEndpoint.manual(host = "127.0.0.1", port = 18789),
NodeRuntime.GatewayConnectAuth(token = "initial-token", bootstrapToken = null, password = null),
)
runtime.disconnect()
assertNull(desiredConnection(runtime, "nodeSession"))
runtime.refreshGatewayConnection()
val desired = desiredConnection(runtime, "nodeSession") ?: error("Expected desired node connection")
val endpoint = readField<GatewayEndpoint>(desired, "endpoint")
assertEquals("127.0.0.1", endpoint.host)
assertEquals(18789, endpoint.port)
assertEquals("shared-token", readField<String?>(desired, "token"))
}
@Test
fun connect_showsSecureEndpointGuidanceWhenTlsProbeFails() {
val app = RuntimeEnvironment.getApplication()

View File

@@ -62,6 +62,21 @@ class SecurePrefsTest {
assertFalse(plainPrefs.getBoolean("talk.enabled", false))
}
@Test
fun installedAppsSharing_defaultsOffAndPersistsOptIn() {
val context = RuntimeEnvironment.getApplication()
val plainPrefs = context.getSharedPreferences("openclaw.node", Context.MODE_PRIVATE)
plainPrefs.edit().clear().commit()
val prefs = SecurePrefs(context)
assertFalse(prefs.installedAppsSharingEnabled.value)
prefs.setInstalledAppsSharingEnabled(true)
assertTrue(prefs.installedAppsSharingEnabled.value)
assertTrue(plainPrefs.getBoolean("device.apps.sharing.enabled", false))
}
@Test
fun saveGatewayBootstrapToken_persistsSeparatelyFromSharedToken() {
val context = RuntimeEnvironment.getApplication()

View File

@@ -1,10 +1,44 @@
package ai.openclaw.app.chat
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonObject
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
import org.junit.Test
class ChatControllerMessageIdentityTest {
private val json = Json { ignoreUnknownKeys = true }
@Test
fun parseChatMessageContentsReadsGatewayStringContent() {
val obj =
json
.parseToJsonElement(
"""
{"role":"user","content":"Hello","idempotencyKey":"run-1:user"}
""".trimIndent(),
).jsonObject
val content = parseChatMessageContents(obj)
assertEquals(listOf(ChatMessageContent(type = "text", text = "Hello")), content)
}
@Test
fun parseChatMessageContentsFallsBackToTopLevelText() {
val obj =
json
.parseToJsonElement(
"""
{"role":"assistant","text":"Hi there"}
""".trimIndent(),
).jsonObject
val content = parseChatMessageContents(obj)
assertEquals(listOf(ChatMessageContent(type = "text", text = "Hi there")), content)
}
@Test
fun reconcileMessageIdsReusesMatchingIdsAcrossHistoryReload() {
val previous =
@@ -101,6 +135,62 @@ class ChatControllerMessageIdentityTest {
assertEquals(listOf("local-user", "remote-assistant"), merged.map { it.id })
}
@Test
fun retainUnmatchedOptimisticMessagesKeepsOutgoingUserTurnWhenHistoryOmitsIt() {
val optimistic =
ChatMessage(
id = "local-user",
role = "user",
content = listOf(ChatMessageContent(type = "text", text = "Testing testing 1 2 3")),
timestampMs = 1000L,
)
val assistant =
ChatMessage(
id = "remote-assistant",
role = "assistant",
content = listOf(ChatMessageContent(type = "text", text = "Received.")),
timestampMs = 2000L,
)
val retained = retainUnmatchedOptimisticMessages(incoming = listOf(assistant), optimistic = listOf(optimistic))
assertEquals(listOf("local-user"), retained.map { it.id })
}
@Test
fun retainUnmatchedOptimisticMessagesDropsGatewayPersistedUserTurn() {
val optimistic =
ChatMessage(
id = "local-user",
role = "user",
content = listOf(ChatMessageContent(type = "text", text = "hello")),
timestampMs = 1000L,
idempotencyKey = "run-1:user",
)
val remoteUser = optimistic.copy(id = "remote-user", timestampMs = 500L)
val retained = retainUnmatchedOptimisticMessages(incoming = listOf(remoteUser), optimistic = listOf(optimistic))
assertEquals(emptyList<String>(), retained.map { it.id })
}
@Test
fun retainUnmatchedOptimisticMessagesKeepsDistinctIdempotencyKey() {
val optimistic =
ChatMessage(
id = "local-user",
role = "user",
content = listOf(ChatMessageContent(type = "text", text = "hello")),
timestampMs = 1000L,
idempotencyKey = "run-2:user",
)
val remoteUser = optimistic.copy(id = "remote-user", timestampMs = 2000L, idempotencyKey = "run-1:user")
val retained = retainUnmatchedOptimisticMessages(incoming = listOf(remoteUser), optimistic = listOf(optimistic))
assertEquals(listOf("local-user"), retained.map { it.id })
}
@Test
fun mergeOptimisticMessagesDoesNotDuplicateHistoryTurns() {
val user =

View File

@@ -20,6 +20,7 @@ import okhttp3.mockwebserver.MockWebServer
import okhttp3.mockwebserver.RecordedRequest
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Test
import org.junit.runner.RunWith
@@ -233,7 +234,75 @@ class GatewaySessionReconnectTest {
)
}
private fun createReconnectHarness(): ReconnectHarness {
@Test
fun pairingRequiredFailureNotifiesPauseReconnectProblem() =
runBlocking {
val json = Json { ignoreUnknownKeys = true }
val connectFailure = CompletableDeferred<Pair<GatewaySession.ErrorShape, Boolean>>()
val server =
startGatewayServer(json = json) { webSocket, id, method ->
if (method == "connect") {
webSocket.send(
"""
{"type":"res","id":"$id","ok":false,"error":{"code":"NOT_PAIRED","message":"pairing required: device approval is required","details":{"code":"PAIRING_REQUIRED","reason":"not-paired","requestId":"request-1"}}}
""".trimIndent(),
)
}
}
val harness =
createReconnectHarness { error, pauseReconnect ->
connectFailure.complete(error to pauseReconnect)
}
try {
connectNodeSession(harness.session, server.port)
val (error, pauseReconnect) = withTimeout(LIFECYCLE_TEST_TIMEOUT_MS) { connectFailure.await() }
assertEquals("PAIRING_REQUIRED", error.details?.code)
assertEquals("not-paired", error.details?.reason)
assertEquals("request-1", error.details?.requestId)
assertTrue(pauseReconnect)
} finally {
shutdownReconnectHarness(harness, server)
}
}
@Test
fun pairingRequiredFailureDropsUnsafeRequestId() =
runBlocking {
val json = Json { ignoreUnknownKeys = true }
val connectFailure = CompletableDeferred<Pair<GatewaySession.ErrorShape, Boolean>>()
val server =
startGatewayServer(json = json) { webSocket, id, method ->
if (method == "connect") {
webSocket.send(
"""
{"type":"res","id":"$id","ok":false,"error":{"code":"NOT_PAIRED","message":"pairing required: device approval is required","details":{"code":"PAIRING_REQUIRED","reason":"not-paired","requestId":"request-1;echo unsafe"}}}
""".trimIndent(),
)
}
}
val harness =
createReconnectHarness { error, pauseReconnect ->
connectFailure.complete(error to pauseReconnect)
}
try {
connectNodeSession(harness.session, server.port)
val (error, pauseReconnect) = withTimeout(LIFECYCLE_TEST_TIMEOUT_MS) { connectFailure.await() }
assertEquals("PAIRING_REQUIRED", error.details?.code)
assertEquals("not-paired", error.details?.reason)
assertNull(error.details?.requestId)
assertTrue(pauseReconnect)
} finally {
shutdownReconnectHarness(harness, server)
}
}
private fun createReconnectHarness(
onConnectFailure: (GatewaySession.ErrorShape, Boolean) -> Unit = { _, _ -> },
): ReconnectHarness {
val app = RuntimeEnvironment.getApplication()
val sessionJob = SupervisorJob()
val session =
@@ -243,6 +312,7 @@ class GatewaySessionReconnectTest {
deviceAuthStore = ReconnectDeviceAuthStore(),
onConnected = {},
onDisconnected = { _ -> },
onConnectFailure = onConnectFailure,
onEvent = { _, _ -> },
onInvoke = { GatewaySession.InvokeResult.ok("""{"handled":true}""") },
)

View File

@@ -7,66 +7,57 @@ import org.junit.Test
class CanvasActionTrustTest {
@Test
fun acceptsBundledScaffoldAsset() {
assertTrue(CanvasActionTrust.isTrustedCanvasActionUrl(CanvasActionTrust.scaffoldAssetUrl, emptyList()))
assertTrue(CanvasActionTrust.isTrustedCanvasActionUrl(CanvasActionTrust.scaffoldAssetUrl))
}
@Test
fun acceptsTrustedA2uiPageOnAdvertisedCanvasHost() {
assertTrue(
fun acceptsBundledA2uiAsset() {
assertTrue(CanvasActionTrust.isTrustedCanvasActionUrl(CanvasActionTrust.localA2uiAssetUrl))
}
@Test
fun rejectsRemoteHttpA2uiPageEvenWhenGatewayAdvertised() {
assertFalse(
CanvasActionTrust.isTrustedCanvasActionUrl(
rawUrl = "http://canvas.example.com:9443/__openclaw__/cap/token/__openclaw__/a2ui/?platform=android",
),
)
}
@Test
fun rejectsRemoteHttpsA2uiPageEvenWhenGatewayAdvertised() {
assertFalse(
CanvasActionTrust.isTrustedCanvasActionUrl(
rawUrl = "https://canvas.example.com:9443/__openclaw__/cap/token/__openclaw__/a2ui/?platform=android",
trustedA2uiUrls = listOf("https://canvas.example.com:9443/__openclaw__/cap/token/__openclaw__/a2ui/?platform=android"),
),
)
}
@Test
fun rejectsDifferentOriginEvenIfPathMatches() {
fun rejectsRemoteCanvasPage() {
assertFalse(
CanvasActionTrust.isTrustedCanvasActionUrl(
rawUrl = "https://evil.example.com:9443/__openclaw__/cap/token/__openclaw__/a2ui/?platform=android",
trustedA2uiUrls = listOf("https://canvas.example.com:9443/__openclaw__/cap/token/__openclaw__/a2ui/?platform=android"),
rawUrl = "https://canvas.example.com:9443/__openclaw__/canvas/",
),
)
}
@Test
fun rejectsUntrustedCanvasPagePathOnTrustedOrigin() {
fun rejectsDescendantPathUnderBundledA2uiRoot() {
assertFalse(
CanvasActionTrust.isTrustedCanvasActionUrl(
rawUrl = "https://canvas.example.com:9443/untrusted/index.html",
trustedA2uiUrls = listOf("https://canvas.example.com:9443/__openclaw__/cap/token/__openclaw__/a2ui/?platform=android"),
rawUrl = "file:///android_asset/CanvasA2UI/child/index.html",
),
)
}
@Test
fun acceptsFragmentOnlyDifferenceForTrustedA2uiPage() {
assertTrue(
CanvasActionTrust.isTrustedCanvasActionUrl(
rawUrl = "https://canvas.example.com:9443/__openclaw__/cap/token/__openclaw__/a2ui/?platform=android#step2",
trustedA2uiUrls = listOf("https://canvas.example.com:9443/__openclaw__/cap/token/__openclaw__/a2ui/?platform=android"),
),
)
}
@Test
fun rejectsQueryMismatchOnTrustedOriginAndPath() {
fun rejectsQueryOrFragmentChangesToBundledA2uiAsset() {
assertFalse(
CanvasActionTrust.isTrustedCanvasActionUrl(
rawUrl = "https://canvas.example.com:9443/__openclaw__/cap/token/__openclaw__/a2ui/?platform=ios",
trustedA2uiUrls = listOf("https://canvas.example.com:9443/__openclaw__/cap/token/__openclaw__/a2ui/?platform=android"),
),
)
}
@Test
fun rejectsDescendantPathUnderTrustedA2uiRoot() {
assertFalse(
CanvasActionTrust.isTrustedCanvasActionUrl(
rawUrl = "https://canvas.example.com:9443/__openclaw__/cap/token/__openclaw__/a2ui/child/index.html?platform=android",
trustedA2uiUrls = listOf("https://canvas.example.com:9443/__openclaw__/cap/token/__openclaw__/a2ui/?platform=android"),
rawUrl = "${CanvasActionTrust.localA2uiAssetUrl}?platform=android",
),
)
assertFalse(CanvasActionTrust.isTrustedCanvasActionUrl("${CanvasActionTrust.localA2uiAssetUrl}#step2"))
}
}

View File

@@ -9,6 +9,7 @@ import ai.openclaw.app.gateway.isLoopbackGatewayHost
import ai.openclaw.app.protocol.OpenClawCallLogCommand
import ai.openclaw.app.protocol.OpenClawCameraCommand
import ai.openclaw.app.protocol.OpenClawCapability
import ai.openclaw.app.protocol.OpenClawDeviceCommand
import ai.openclaw.app.protocol.OpenClawLocationCommand
import ai.openclaw.app.protocol.OpenClawMotionCommand
import ai.openclaw.app.protocol.OpenClawPhotosCommand
@@ -475,6 +476,15 @@ class ConnectionManagerTest {
assertTrue(options.caps.contains(OpenClawCapability.VoiceWake.rawValue))
}
@Test
fun buildNodeConnectOptions_advertisesDeviceAppsOnlyWhenUserOptedIn() {
val disabled = newManager(installedAppsSharingEnabled = false).buildNodeConnectOptions()
val enabled = newManager(installedAppsSharingEnabled = true).buildNodeConnectOptions()
assertFalse(disabled.commands.contains(OpenClawDeviceCommand.Apps.rawValue))
assertTrue(enabled.commands.contains(OpenClawDeviceCommand.Apps.rawValue))
}
@Test
fun buildNodeConnectOptions_omitsVoiceWakeWithoutMicrophonePermission() {
val options =
@@ -546,6 +556,7 @@ class ConnectionManagerTest {
callLogAvailable: Boolean = false,
photosAvailable: Boolean = false,
hasRecordAudioPermission: Boolean = false,
installedAppsSharingEnabled: Boolean = false,
): ConnectionManager {
val context = RuntimeEnvironment.getApplication()
val prefs =
@@ -567,6 +578,7 @@ class ConnectionManagerTest {
callLogAvailable = { callLogAvailable },
photosAvailable = { photosAvailable },
hasRecordAudioPermission = { hasRecordAudioPermission },
installedAppsSharingEnabled = { installedAppsSharingEnabled },
manualTls = { false },
)
}

View File

@@ -1,6 +1,7 @@
package ai.openclaw.app.node
import android.content.Context
import android.content.pm.ApplicationInfo
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.boolean
@@ -320,6 +321,108 @@ class DeviceHandlerTest {
system["securityPatchLevel"]?.jsonPrimitive?.content
}
@Test
fun handleDeviceApps_filtersAndLimitsVisibleApps() {
val handler =
DeviceHandler.forTesting(
appContext = appContext(),
appSource =
FakeDeviceAppSource(
listOf(
DeviceAppEntry(
label = "Calendar",
packageName = "com.google.android.calendar",
system = false,
enabled = true,
launchable = true,
),
DeviceAppEntry(
label = "Android System",
packageName = "android",
system = true,
enabled = true,
launchable = false,
),
DeviceAppEntry(
label = "Disabled App",
packageName = "com.example.disabled",
system = false,
enabled = false,
launchable = true,
),
DeviceAppEntry(
label = "Gmail",
packageName = "com.google.android.gm",
system = false,
enabled = true,
launchable = true,
),
),
),
)
val result = handler.handleDeviceApps("""{"query":"google","limit":1}""")
assertTrue(result.ok)
val payload = parsePayload(result.payloadJson)
assertEquals("1", payload.getValue("count").jsonPrimitive.content)
assertEquals("2", payload.getValue("totalMatched").jsonPrimitive.content)
assertTrue(payload.getValue("truncated").jsonPrimitive.boolean)
assertEquals("launcher", payload.getValue("visibility").jsonPrimitive.content)
val apps = payload.getValue("apps").jsonArray
assertEquals(1, apps.size)
val app = apps.first().jsonObject
assertEquals("Calendar", app.getValue("label").jsonPrimitive.content)
assertEquals("com.google.android.calendar", app.getValue("packageName").jsonPrimitive.content)
assertTrue(!app.getValue("system").jsonPrimitive.boolean)
assertTrue(app.getValue("enabled").jsonPrimitive.boolean)
assertTrue(app.getValue("launchable").jsonPrimitive.boolean)
}
@Test
fun handleDeviceApps_canIncludeSystemAndNonLaunchableApps() {
val source =
FakeDeviceAppSource(
listOf(
DeviceAppEntry(
label = "Android System",
packageName = "android",
system = true,
enabled = true,
launchable = false,
),
),
)
val handler = DeviceHandler.forTesting(appContext = appContext(), appSource = source)
val result = handler.handleDeviceApps("""{"includeSystem":true,"includeNonLaunchable":true}""")
assertTrue(result.ok)
val payload = parsePayload(result.payloadJson)
assertEquals("android-visible", payload.getValue("visibility").jsonPrimitive.content)
assertTrue(payload.getValue("includeSystem").jsonPrimitive.boolean)
val app =
payload
.getValue("apps")
.jsonArray
.first()
.jsonObject
assertEquals("android", app.getValue("packageName").jsonPrimitive.content)
assertTrue(app.getValue("system").jsonPrimitive.boolean)
assertTrue(!app.getValue("launchable").jsonPrimitive.boolean)
assertTrue(source.includeNonLaunchableRequests.single())
}
@Test
fun isSystemDeviceApp_treatsUpdatedBuiltInsAsSystemApps() {
val appInfo =
ApplicationInfo().apply {
flags = ApplicationInfo.FLAG_UPDATED_SYSTEM_APP
}
assertTrue(isSystemDeviceApp(appInfo))
}
private fun appContext(): Context = RuntimeEnvironment.getApplication()
private fun parsePayload(payloadJson: String?): JsonObject {
@@ -327,3 +430,14 @@ class DeviceHandlerTest {
return Json.parseToJsonElement(jsonString).jsonObject
}
}
private class FakeDeviceAppSource(
private val apps: List<DeviceAppEntry>,
) : DeviceAppSource {
val includeNonLaunchableRequests = mutableListOf<Boolean>()
override fun listApps(includeNonLaunchable: Boolean): List<DeviceAppEntry> {
includeNonLaunchableRequests += includeNonLaunchable
return apps
}
}

View File

@@ -115,6 +115,15 @@ class InvokeCommandRegistryTest {
assertMissingAll(commands, optionalCommands + debugCommands)
}
@Test
fun advertisedCommands_includesDeviceAppsOnlyWhenUserOptedIn() {
val disabled = InvokeCommandRegistry.advertisedCommands(defaultFlags(installedAppsSharingEnabled = false))
val enabled = InvokeCommandRegistry.advertisedCommands(defaultFlags(installedAppsSharingEnabled = true))
assertFalse(disabled.contains(OpenClawDeviceCommand.Apps.rawValue))
assertTrue(enabled.contains(OpenClawDeviceCommand.Apps.rawValue))
}
@Test
fun advertisedCommands_includesFeatureCommandsWhenEnabled() {
val commands =
@@ -151,6 +160,7 @@ class InvokeCommandRegistryTest {
voiceWakeEnabled = false,
motionActivityAvailable = true,
motionPedometerAvailable = false,
installedAppsSharingEnabled = false,
debugBuild = false,
),
)
@@ -262,6 +272,7 @@ class InvokeCommandRegistryTest {
voiceWakeEnabled: Boolean = false,
motionActivityAvailable: Boolean = false,
motionPedometerAvailable: Boolean = false,
installedAppsSharingEnabled: Boolean = false,
debugBuild: Boolean = false,
): NodeRuntimeFlags =
NodeRuntimeFlags(
@@ -275,6 +286,7 @@ class InvokeCommandRegistryTest {
voiceWakeEnabled = voiceWakeEnabled,
motionActivityAvailable = motionActivityAvailable,
motionPedometerAvailable = motionPedometerAvailable,
installedAppsSharingEnabled = installedAppsSharingEnabled,
debugBuild = debugBuild,
)

View File

@@ -4,6 +4,7 @@ import ai.openclaw.app.gateway.DeviceIdentityStore
import ai.openclaw.app.gateway.GatewaySession
import ai.openclaw.app.protocol.OpenClawCallLogCommand
import ai.openclaw.app.protocol.OpenClawCameraCommand
import ai.openclaw.app.protocol.OpenClawDeviceCommand
import ai.openclaw.app.protocol.OpenClawLocationCommand
import ai.openclaw.app.protocol.OpenClawMotionCommand
import ai.openclaw.app.protocol.OpenClawPhotosCommand
@@ -170,6 +171,20 @@ class InvokeDispatcherTest {
assertEquals("LOCATION_DISABLED: enable Location in Settings", result.error?.message)
}
@Test
fun handleInvoke_blocksDeviceAppsWhenSharingDisabled() =
runTest {
val result =
newDispatcher(installedAppsSharingEnabled = false)
.handleInvoke(OpenClawDeviceCommand.Apps.rawValue, """{"limit":1}""")
assertEquals("INSTALLED_APPS_SHARING_DISABLED", result.error?.code)
assertEquals(
"INSTALLED_APPS_SHARING_DISABLED: enable Installed Apps in Settings",
result.error?.message,
)
}
@Test
fun handleInvoke_blocksMotionActivityWhenUnavailable() =
runTest {
@@ -250,6 +265,7 @@ class InvokeDispatcherTest {
smsTelephonyAvailable: Boolean = true,
callLogAvailable: Boolean = false,
photosAvailable: Boolean = true,
installedAppsSharingEnabled: Boolean = true,
debugBuild: Boolean = false,
motionActivityAvailable: Boolean = false,
motionPedometerAvailable: Boolean = false,
@@ -283,8 +299,6 @@ class InvokeDispatcherTest {
A2UIHandler(
canvas = canvas,
json = Json { ignoreUnknownKeys = true },
getNodeCanvasHostUrl = { null },
getOperatorCanvasHostUrl = { null },
),
debugHandler = DebugHandler(appContext, DeviceIdentityStore(appContext)),
callLogHandler = CallLogHandler.forTesting(appContext, InvokeDispatcherFakeCallLogDataSource()),
@@ -297,10 +311,10 @@ class InvokeDispatcherTest {
smsTelephonyAvailable = { smsTelephonyAvailable },
callLogAvailable = { callLogAvailable },
photosAvailable = { photosAvailable },
installedAppsSharingEnabled = { installedAppsSharingEnabled },
debugBuild = { debugBuild },
onCanvasA2uiPush = {},
onCanvasA2uiReset = {},
refreshCanvasHostUrl = { null },
motionActivityAvailable = { motionActivityAvailable },
motionPedometerAvailable = { motionPedometerAvailable },
)

View File

@@ -57,6 +57,7 @@ class OpenClawProtocolConstantsTest {
assertEquals("device.info", OpenClawDeviceCommand.Info.rawValue)
assertEquals("device.permissions", OpenClawDeviceCommand.Permissions.rawValue)
assertEquals("device.health", OpenClawDeviceCommand.Health.rawValue)
assertEquals("device.apps", OpenClawDeviceCommand.Apps.rawValue)
}
@Test

View File

@@ -1,5 +1,6 @@
package ai.openclaw.app.ui
import ai.openclaw.app.GatewayConnectionProblem
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
@@ -26,6 +27,53 @@ class OnboardingFlowLogicTest {
assertTrue(canFinishOnboarding(isConnected = true, isNodeConnected = true))
}
@Test
fun nearbyGatewayFoundStateIsConnectable() {
assertEquals(
NearbyGatewayUiState(subtitle = "Studio Gateway", status = "Found", canConnect = true),
nearbyGatewayUiState(nearbyGatewayName = "Studio Gateway", discoveryStatusText = "Searching…", discoveryStarted = false),
)
}
@Test
fun nearbyGatewayBeforeDiscoveryStartsIsNotConnectable() {
assertEquals(
NearbyGatewayUiState(subtitle = "Starting discovery...", status = "Starting", canConnect = false),
nearbyGatewayUiState(nearbyGatewayName = null, discoveryStatusText = "Searching…", discoveryStarted = false, searchTimedOut = true),
)
}
@Test
fun nearbyGatewaySearchingStateIsNotConnectable() {
assertEquals(
NearbyGatewayUiState(subtitle = "Searching for gateways...", status = "Searching", canConnect = false),
nearbyGatewayUiState(nearbyGatewayName = null, discoveryStatusText = "Searching for gateways…"),
)
}
@Test
fun nearbyGatewayTimedOutSearchShowsEmptyState() {
assertEquals(
NearbyGatewayUiState(subtitle = "No gateway found", status = "Not found", canConnect = false),
nearbyGatewayUiState(nearbyGatewayName = null, discoveryStatusText = "Searching for gateways…", searchTimedOut = true),
)
}
@Test
fun nearbyGatewayEmptyResultStateIsNotConnectable() {
assertEquals(
NearbyGatewayUiState(subtitle = "No gateway found", status = "Not found", canConnect = false),
nearbyGatewayUiState(nearbyGatewayName = null, discoveryStatusText = "Local: 0 • Wide: 0"),
)
}
@Test
fun recoveryGatewayNamePrefersServerThenAttemptedGateway() {
assertEquals("Server Gateway", recoveryGatewayName(serverName = "Server Gateway", attemptedGatewayName = "Discovered Gateway"))
assertEquals("Discovered Gateway", recoveryGatewayName(serverName = null, attemptedGatewayName = "Discovered Gateway"))
assertEquals("Home Gateway", recoveryGatewayName(serverName = " ", attemptedGatewayName = " "))
}
@Test
fun showsPairingStateForPairingRequiredGatewayStatus() {
assertEquals(
@@ -50,6 +98,50 @@ class OnboardingFlowLogicTest {
)
}
@Test
fun showsApprovalRequiredForPausedPairingProblem() {
assertEquals(
GatewayRecoveryUiState.ApprovalRequired,
gatewayRecoveryUiState(
ready = false,
statusText = "Connecting…",
connectSettling = false,
gatewayConnectionProblem =
GatewayConnectionProblem(
code = "PAIRING_REQUIRED",
message = "pairing required: device approval is required",
reason = "not-paired",
requestId = "request-1",
recommendedNextStep = null,
pauseReconnect = true,
retryable = false,
),
),
)
}
@Test
fun showsPairingForRetryablePairingProblem() {
assertEquals(
GatewayRecoveryUiState.Pairing,
gatewayRecoveryUiState(
ready = false,
statusText = "Connecting…",
connectSettling = false,
gatewayConnectionProblem =
GatewayConnectionProblem(
code = "PAIRING_REQUIRED",
message = "pairing required: device approval is required",
reason = "not-paired",
requestId = "request-1",
recommendedNextStep = "wait_then_retry",
pauseReconnect = false,
retryable = true,
),
),
)
}
@Test
fun showsFinishingStateWhileGatewayConnectionSettles() {
assertEquals(

View File

@@ -1,5 +1,8 @@
package ai.openclaw.app.ui
import ai.openclaw.app.GatewayModelProviderSummary
import ai.openclaw.app.GatewayModelSummary
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
@@ -10,8 +13,55 @@ class ProviderModelStatusTest {
assertTrue(modelProviderReady("static"))
}
@Test
fun expiringProviderStatusIsNotFullyReady() {
assertFalse(modelProviderReady("expiring"))
}
@Test
fun missingProviderStatusIsNotReady() {
assertFalse(modelProviderReady("missing"))
}
@Test
fun providerRowsIncludeConfiguredModelProvidersWithoutAuthRows() {
val rows =
providerRows(
providers =
listOf(
GatewayModelProviderSummary(
id = "openai",
displayName = "OpenAI",
status = "ok",
profileCount = 1,
),
),
models =
listOf(
model(provider = "openai", id = "gpt-5.5"),
model(provider = "byteplus", id = "seed-1-8-251228"),
),
)
assertEquals(listOf("openai", "byteplus"), rows.map { it.id })
assertEquals(1, rows.first { it.id == "openai" }.modelCount)
assertEquals(1, rows.first { it.id == "byteplus" }.modelCount)
assertTrue(rows.first { it.id == "byteplus" }.ready)
}
private fun model(
provider: String,
id: String,
): GatewayModelSummary =
GatewayModelSummary(
id = id,
name = id,
provider = provider,
supportsVision = false,
supportsAudio = false,
supportsDocuments = false,
supportsReasoning = false,
contextTokens = null,
available = null,
)
}

View File

@@ -0,0 +1,13 @@
package ai.openclaw.app.ui
import org.junit.Assert.assertEquals
import org.junit.Test
class SettingsScreensTest {
@Test
fun androidDistributionChannelUsesBuildFlavorLabels() {
assertEquals("Play", androidDistributionChannel("play"))
assertEquals("Third-party", androidDistributionChannel("thirdParty"))
assertEquals("Unknown", androidDistributionChannel(""))
}
}

View File

@@ -32,4 +32,46 @@ class SettingsSheetNotificationAppsTest {
assertEquals(setOf("com.example.recent", "com.example.configured"), packages)
}
@Test
fun filterNotificationAppsForPicker_keepsSelectedSystemPackagesVisible() {
val apps =
listOf(
InstalledApp(label = "Android System", packageName = "android", isSystemApp = true),
InstalledApp(label = "Phone Services", packageName = "com.android.phone", isSystemApp = true),
InstalledApp(label = "Gmail", packageName = "com.google.android.gm", isSystemApp = false),
)
val filtered =
filterNotificationAppsForPicker(
apps = apps,
selectedPackages = setOf("com.android.phone"),
query = "",
showSystemApps = false,
)
assertEquals(
listOf("com.android.phone", "com.google.android.gm"),
filtered.map { it.packageName },
)
}
@Test
fun filterNotificationAppsForPicker_matchesLabelsAndPackageNames() {
val apps =
listOf(
InstalledApp(label = "Gmail", packageName = "com.google.android.gm", isSystemApp = false),
InstalledApp(label = "Calendar", packageName = "com.google.android.calendar", isSystemApp = false),
)
val filtered =
filterNotificationAppsForPicker(
apps = apps,
selectedPackages = emptySet(),
query = "gm",
showSystemApps = false,
)
assertEquals(listOf("com.google.android.gm"), filtered.map { it.packageName })
}
}

View File

@@ -0,0 +1,101 @@
package ai.openclaw.app.ui
import ai.openclaw.app.GatewayChannelSummary
import ai.openclaw.app.GatewayChannelsSummary
import ai.openclaw.app.GatewayNodesDevicesSummary
import ai.openclaw.app.GatewayPendingDeviceSummary
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
class ShellScreenLogicTest {
@Test
fun bottomNavHidesForKeyboardAndCommandPalette() {
assertTrue(shellBottomNavVisible(keyboardVisible = false, commandOpen = false))
assertFalse(shellBottomNavVisible(keyboardVisible = true, commandOpen = false))
assertFalse(shellBottomNavVisible(keyboardVisible = false, commandOpen = true))
}
@Test
fun homeAttentionRowsSurfaceGatewayWhenDisconnected() {
val rows =
homeAttentionRows(
isConnected = false,
pendingApprovals = 0,
channelsSummary = emptyChannels(),
nodesDevicesSummary = emptyNodesDevices(),
readyProviderCount = 0,
)
assertEquals(listOf("Gateway"), rows.map { it.title })
}
@Test
fun homeAttentionRowsSurfaceOnlyActionableConnectedIssues() {
val rows =
homeAttentionRows(
isConnected = true,
pendingApprovals = 2,
channelsSummary =
GatewayChannelsSummary(
channels =
listOf(
GatewayChannelSummary(
id = "telegram",
label = "Telegram",
accountCount = 1,
enabled = true,
configured = true,
linked = true,
running = false,
connected = false,
error = "offline",
),
),
),
nodesDevicesSummary =
GatewayNodesDevicesSummary(
nodes = emptyList(),
pendingDevices =
listOf(
GatewayPendingDeviceSummary(
requestId = "request-1",
deviceId = "device-1",
displayName = "Phone",
remoteIp = null,
roles = emptyList(),
scopes = emptyList(),
requestedAtMs = null,
repair = false,
),
),
pairedDevices = emptyList(),
),
readyProviderCount = 0,
)
assertEquals(listOf("Approvals", "Channels", "Nodes & Devices", "Providers"), rows.map { it.title })
val providersRow = rows.single { it.title == "Providers" }
assertEquals(Tab.Settings, providersRow.tab)
assertEquals(SettingsRoute.Gateway, providersRow.settingsRoute)
}
@Test
fun homeAttentionRowsStayQuietWhenConnectedAndHealthy() {
val rows =
homeAttentionRows(
isConnected = true,
pendingApprovals = 0,
channelsSummary = emptyChannels(),
nodesDevicesSummary = emptyNodesDevices(),
readyProviderCount = 1,
)
assertEquals(emptyList<String>(), rows.map { it.title })
}
private fun emptyChannels(): GatewayChannelsSummary = GatewayChannelsSummary(channels = emptyList())
private fun emptyNodesDevices(): GatewayNodesDevicesSummary = GatewayNodesDevicesSummary(nodes = emptyList(), pendingDevices = emptyList(), pairedDevices = emptyList())
}

View File

@@ -0,0 +1,75 @@
package ai.openclaw.app.ui
import ai.openclaw.app.VoiceCaptureMode
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNull
import org.junit.Test
class VoiceScreenLogicTest {
@Test
fun voiceAttentionStatusKeepsFailedTalkStartVisibleAfterModeStops() {
val attention =
voiceAttentionStatus(
talkModeStatusText = "Start failed: Error: Realtime voice provider \"openai\" is not configured",
voiceCaptureMode = VoiceCaptureMode.Off,
micEnabled = false,
micIsSending = false,
talkModeEnabled = false,
talkModeListening = false,
talkModeSpeaking = false,
)
assertEquals("Realtime voice provider is not configured.", attention)
assertEquals(
attention,
voiceStatusLabel(
gatewayStatus = "Online",
voiceCaptureMode = VoiceCaptureMode.Off,
micStatusText = "Mic off",
micQueuedMessages = 0,
micIsSending = false,
talkModeListening = false,
talkModeSpeaking = false,
voiceAttentionStatus = attention,
),
)
}
@Test
fun voiceAttentionStatusDoesNotOverrideActiveTalkState() {
assertNull(
voiceAttentionStatus(
talkModeStatusText = "Start failed: provider unavailable",
voiceCaptureMode = VoiceCaptureMode.TalkMode,
micEnabled = false,
micIsSending = false,
talkModeEnabled = true,
talkModeListening = false,
talkModeSpeaking = false,
),
)
}
@Test
fun voiceAttentionStatusDoesNotOverrideDictationState() {
assertNull(
voiceAttentionStatus(
talkModeStatusText = "Start failed: provider unavailable",
voiceCaptureMode = VoiceCaptureMode.ManualMic,
micEnabled = true,
micIsSending = false,
talkModeEnabled = false,
talkModeListening = false,
talkModeSpeaking = false,
),
)
}
@Test
fun voiceRuntimeAttentionStatusSanitizesTranscriptionProviderFailures() {
assertEquals(
"Realtime transcription provider is not configured.",
voiceRuntimeAttentionStatus("Transcription unavailable: UNAVAILABLE: Error: No realtime transcription provider registered"),
)
}
}

View File

@@ -0,0 +1,94 @@
package ai.openclaw.app.ui.chat
import ai.openclaw.app.chat.ChatMessage
import ai.openclaw.app.chat.ChatMessageContent
import ai.openclaw.app.chat.ChatPendingToolCall
import org.junit.Assert.assertEquals
import org.junit.Test
class ChatTimelineTest {
@Test
fun activeRunAnchorsNewestUserPromptInsteadOfThinkingRow() {
val user = textMessage(id = "user-1", role = "user", text = "hello")
val timeline =
buildChatTimeline(
messages = listOf(user),
pendingRunCount = 1,
pendingToolCalls = emptyList(),
streamingAssistantText = null,
)
assertEquals(listOf("thinking", "message:user-1"), timeline.items.map(::chatTimelineItemKey))
assertEquals(1, timeline.scrollTargetIndex)
}
@Test
fun activeRunAnchorsNewestUserPromptWhileAssistantStreams() {
val olderAssistant = textMessage(id = "assistant-1", role = "assistant", text = "previous")
val user = textMessage(id = "user-1", role = "user", text = "next")
val tool =
ChatPendingToolCall(
toolCallId = "tool-1",
name = "memory.search",
startedAtMs = 1000L,
)
val timeline =
buildChatTimeline(
messages = listOf(olderAssistant, user),
pendingRunCount = 1,
pendingToolCalls = listOf(tool),
streamingAssistantText = "streaming",
)
assertEquals(
listOf("stream", "tools", "thinking", "message:user-1", "message:assistant-1"),
timeline.items.map(::chatTimelineItemKey),
)
assertEquals(3, timeline.scrollTargetIndex)
}
@Test
fun finishedRunAnchorsNewestPersistedMessage() {
val user = textMessage(id = "user-1", role = "user", text = "hello")
val assistant = textMessage(id = "assistant-1", role = "assistant", text = "done")
val timeline =
buildChatTimeline(
messages = listOf(user, assistant),
pendingRunCount = 0,
pendingToolCalls = emptyList(),
streamingAssistantText = null,
)
assertEquals(listOf("message:assistant-1", "message:user-1"), timeline.items.map(::chatTimelineItemKey))
assertEquals(0, timeline.scrollTargetIndex)
}
@Test
fun emptyTimelineHasNoScrollTarget() {
val timeline =
buildChatTimeline(
messages = emptyList(),
pendingRunCount = 0,
pendingToolCalls = emptyList(),
streamingAssistantText = null,
)
assertEquals(emptyList<String>(), timeline.items.map(::chatTimelineItemKey))
assertEquals(null, timeline.scrollTargetIndex)
}
private fun textMessage(
id: String,
role: String,
text: String,
): ChatMessage =
ChatMessage(
id = id,
role = role,
content = listOf(ChatMessageContent(type = "text", text = text)),
timestampMs = null,
)
}

View File

@@ -32,4 +32,29 @@ class SessionFiltersTest {
val result = resolveSessionChoices("custom", sessions, mainSessionKey = "main", nowMs = now).map { it.key }
assertEquals(listOf("main", "custom"), result)
}
@Test
fun compactChoicesKeepMainAndCurrentWhileCappingRecentSessions() {
val now = 1_700_000_000_000L
val sessions =
listOf(
ChatSessionEntry(key = "recent-1", updatedAtMs = now - 1),
ChatSessionEntry(key = "recent-2", updatedAtMs = now - 2),
ChatSessionEntry(key = "recent-3", updatedAtMs = now - 3),
ChatSessionEntry(key = "recent-4", updatedAtMs = now - 4),
ChatSessionEntry(key = "main", updatedAtMs = now - 5),
ChatSessionEntry(key = "active-old", updatedAtMs = now - 30 * 60 * 60 * 1000L),
)
val result =
resolveCompactSessionChoices(
currentSessionKey = "active-old",
sessions = sessions,
mainSessionKey = "main",
nowMs = now,
maxOptions = 4,
).map { it.key }
assertEquals(listOf("main", "active-old", "recent-1", "recent-2"), result)
}
}

View File

@@ -1,9 +1,9 @@
[versions]
agp = "9.2.0"
agp = "9.2.1"
androidx-activity = "1.13.0"
androidx-benchmark = "1.4.1"
androidx-camera = "1.6.0"
androidx-compose-bom = "2026.04.01"
androidx-compose-bom = "2026.05.01"
androidx-core = "1.18.0"
androidx-exifinterface = "1.4.2"
androidx-lifecycle = "2.10.0"
@@ -13,14 +13,14 @@ androidx-uiautomator = "2.4.0-beta02"
androidx-webkit = "1.15.0"
bcprov = "1.84"
commonmark = "0.28.0"
coroutines = "1.10.2"
dnsjava = "3.6.4"
coroutines = "1.11.0"
dnsjava = "3.6.5"
junit = "4.13.2"
junit-vintage = "6.0.3"
junit-vintage = "6.1.0"
kotest = "6.1.11"
ktlint-gradle = "14.2.0"
kotlin = "2.3.21"
material = "1.13.0"
material = "1.14.0"
okhttp = "5.3.2"
play-services-code-scanner = "16.1.0"
robolectric = "4.16.1"

Binary file not shown.

View File

@@ -1,7 +1,9 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.5.1-bin.zip
networkTimeout=10000
retries=0
retryBackOffMs=500
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -57,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/2d6327017519d23b96af35865dc997fcb544fb40/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/3d91ce3b8caaf77ad09f381f43615b715b53f72c/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.

View File

@@ -23,8 +23,8 @@
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Set local scope for the variables, and ensure extensions are enabled
setlocal EnableExtensions
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@@ -51,7 +51,7 @@ echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
"%COMSPEC%" /c exit 1
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
@@ -65,7 +65,7 @@ echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
"%COMSPEC%" /c exit 1
:execute
@rem Setup the command line
@@ -73,21 +73,10 @@ goto fail
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
@rem endlocal doesn't take effect until after the line is parsed and variables are expanded
@rem which allows us to clear the local environment before executing the java command
endlocal & "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* & call :exitWithErrorLevel
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
:exitWithErrorLevel
@rem Use "%COMSPEC%" /c exit to allow operators to work properly in scripts
"%COMSPEC%" /c exit %ERRORLEVEL%

View File

@@ -1,4 +1,8 @@
#!/usr/bin/env bun
/**
* Android release helper that bumps version fields, builds release AAB variants,
* verifies signatures, and prints SHA-256 checksums.
*/
import { $ } from "bun";
import { dirname, join } from "node:path";

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