mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-07 22:41:16 +08:00
Compare commits
401 Commits
codex/plug
...
feat/befor
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a60ff3137 | ||
|
|
bc50add741 | ||
|
|
a50455452d | ||
|
|
03e7e3cd27 | ||
|
|
f09b449ab1 | ||
|
|
76d3c67a88 | ||
|
|
adb20a9fa9 | ||
|
|
8ae90e16fc | ||
|
|
a0cc684d02 | ||
|
|
992b30604d | ||
|
|
d506eea076 | ||
|
|
7d18799bbe | ||
|
|
9134dbd252 | ||
|
|
c125c33724 | ||
|
|
f41cd12b54 | ||
|
|
85cf23a9d6 | ||
|
|
eacd5ac3ef | ||
|
|
1a9abb13bd | ||
|
|
b50b9b16ab | ||
|
|
c813222671 | ||
|
|
f8edd09a2c | ||
|
|
c73c050276 | ||
|
|
03333100ba | ||
|
|
cb5aefb790 | ||
|
|
2bdbb189bd | ||
|
|
fa56682b3c | ||
|
|
9446ee8ea3 | ||
|
|
8eaa3417c3 | ||
|
|
fc570934de | ||
|
|
7772395618 | ||
|
|
366c1d6b9e | ||
|
|
fa05c351a1 | ||
|
|
20c7cbbf78 | ||
|
|
511093d4b3 | ||
|
|
e403decb6e | ||
|
|
355abe5eba | ||
|
|
be00fcfccb | ||
|
|
aa66ae1fc7 | ||
|
|
e64a881ae0 | ||
|
|
77060aa9f9 | ||
|
|
ae7d93adc4 | ||
|
|
41901c19bf | ||
|
|
79e495a627 | ||
|
|
d61f8e5672 | ||
|
|
85777e726c | ||
|
|
d73dbb6753 | ||
|
|
c28e76c490 | ||
|
|
5d3d54ee36 | ||
|
|
888be707cf | ||
|
|
4430805719 | ||
|
|
8bcab7ec6f | ||
|
|
c3d45fbb19 | ||
|
|
fa89d68e7a | ||
|
|
4505987b9c | ||
|
|
eb6a3fca26 | ||
|
|
953a438420 | ||
|
|
49dbf64ab1 | ||
|
|
cf10183389 | ||
|
|
3e4222e9d4 | ||
|
|
02e3061aa7 | ||
|
|
496a1a35bd | ||
|
|
1dae6cc617 | ||
|
|
16ed9bf147 | ||
|
|
605c9306ab | ||
|
|
febcb01128 | ||
|
|
4d7cc6bb4f | ||
|
|
68ceaf7a5f | ||
|
|
9ec44fad39 | ||
|
|
7ce2670043 | ||
|
|
824e16f9dd | ||
|
|
c603123528 | ||
|
|
55cd272fe1 | ||
|
|
7a801cc451 | ||
|
|
fef1b1918c | ||
|
|
80d1e8a11a | ||
|
|
2f13758f42 | ||
|
|
4ee4960de2 | ||
|
|
2e23d44491 | ||
|
|
6eb82fba3c | ||
|
|
fdbcfced84 | ||
|
|
b7b3c806b4 | ||
|
|
d6affb17d8 | ||
|
|
c9d68fb9c2 | ||
|
|
3cec3bd48b | ||
|
|
78e2f3d66d | ||
|
|
c774db9a1f | ||
|
|
25210317b8 | ||
|
|
694619caaf | ||
|
|
6e107b8857 | ||
|
|
52ef2ef790 | ||
|
|
894f57a4ce | ||
|
|
69e67a764d | ||
|
|
8f44bd6426 | ||
|
|
1086acf3c2 | ||
|
|
47ae562cc9 | ||
|
|
d35f37a58c | ||
|
|
6f7579803b | ||
|
|
2d26f2d876 | ||
|
|
e25f634d50 | ||
|
|
570bfb655f | ||
|
|
910cb9f1af | ||
|
|
67f609ea9a | ||
|
|
3628451aa3 | ||
|
|
94780dde5d | ||
|
|
b568ccee7c | ||
|
|
3702409427 | ||
|
|
e599cb26de | ||
|
|
4693813503 | ||
|
|
c352a018f1 | ||
|
|
ef1784d264 | ||
|
|
ed055f44ae | ||
|
|
e425056aa3 | ||
|
|
425032ed4d | ||
|
|
07df59287a | ||
|
|
f1503bd5c7 | ||
|
|
8d054e7892 | ||
|
|
09f2832670 | ||
|
|
89267f4273 | ||
|
|
ce5b0577d4 | ||
|
|
8ff39007c4 | ||
|
|
cd92549119 | ||
|
|
6ade9c474c | ||
|
|
351a931a62 | ||
|
|
df5b9ef0c6 | ||
|
|
5e8db468ff | ||
|
|
9098e948ac | ||
|
|
833636f0b2 | ||
|
|
6a0f9afc4e | ||
|
|
8ddeada97d | ||
|
|
97297049e7 | ||
|
|
454f094c36 | ||
|
|
be0e994cf0 | ||
|
|
87dddb818d | ||
|
|
f9b8499bf6 | ||
|
|
66a2e72bee | ||
|
|
a6f5e57f46 | ||
|
|
c2ca99aa0b | ||
|
|
bb932beeac | ||
|
|
206c29514c | ||
|
|
b1c982bb2d | ||
|
|
546a1aad98 | ||
|
|
ad89fa669c | ||
|
|
b5a8d5a230 | ||
|
|
9d10a2e242 | ||
|
|
f217a10780 | ||
|
|
9917f3b3a1 | ||
|
|
858b1dffb8 | ||
|
|
b70b99d46d | ||
|
|
e42b4afd39 | ||
|
|
d0c77c9dfd | ||
|
|
169bf6adba | ||
|
|
c7b4c34e89 | ||
|
|
02d9f0d631 | ||
|
|
4019671331 | ||
|
|
c2b28753e7 | ||
|
|
4d630b7e92 | ||
|
|
a79ef1591a | ||
|
|
45849eb757 | ||
|
|
fc3246d8fd | ||
|
|
04bde8c2b1 | ||
|
|
8d39fe5a76 | ||
|
|
051d6f9342 | ||
|
|
7999577ce1 | ||
|
|
a10763e118 | ||
|
|
9a775aa59c | ||
|
|
4c8ed2ce46 | ||
|
|
398af90a22 | ||
|
|
4cf783b7c1 | ||
|
|
45535ff433 | ||
|
|
bcfddcc768 | ||
|
|
324cddee4c | ||
|
|
ac2c2ac954 | ||
|
|
f625a0b106 | ||
|
|
7dd196ed74 | ||
|
|
d746690be5 | ||
|
|
c40884d306 | ||
|
|
9d58f9e24f | ||
|
|
d80b67124b | ||
|
|
c259ff7e01 | ||
|
|
58cdcf74c7 | ||
|
|
14c63ca42a | ||
|
|
32a3733dbe | ||
|
|
8fa62985b9 | ||
|
|
f1de00c107 | ||
|
|
2b96569e2d | ||
|
|
3d609b112e | ||
|
|
8270baa1d0 | ||
|
|
37538072e6 | ||
|
|
e765daaed3 | ||
|
|
4b9542716c | ||
|
|
83da3cfe31 | ||
|
|
cb5f7e201f | ||
|
|
76411b2afc | ||
|
|
4d5f762b7d | ||
|
|
2d80dbfeba | ||
|
|
3a7cf5364f | ||
|
|
d6662e2aa7 | ||
|
|
a30dae3c71 | ||
|
|
1b77e6fd72 | ||
|
|
e1235ca7b4 | ||
|
|
53304ff704 | ||
|
|
9322481075 | ||
|
|
ae72977076 | ||
|
|
23fae00fad | ||
|
|
f5643544c2 | ||
|
|
7c00cc9d0a | ||
|
|
854b71a4b0 | ||
|
|
3cbd4de95c | ||
|
|
6f92148da9 | ||
|
|
cfddce4196 | ||
|
|
a3e73daa6b | ||
|
|
bd2c208689 | ||
|
|
e58170ddc1 | ||
|
|
f2b2b12af4 | ||
|
|
59a25978dd | ||
|
|
6ad50ce474 | ||
|
|
1042710e3b | ||
|
|
b23dc5073f | ||
|
|
9a7c8e186e | ||
|
|
a8066ad96d | ||
|
|
599d880c49 | ||
|
|
a729eab6ee | ||
|
|
f86765cba3 | ||
|
|
e3e9a56b02 | ||
|
|
8923fb8766 | ||
|
|
9db096a98f | ||
|
|
571d4d52e9 | ||
|
|
f248fc8f86 | ||
|
|
9ce2dbe9aa | ||
|
|
f799cd6a14 | ||
|
|
55ab98dc40 | ||
|
|
417024f9ad | ||
|
|
716c93f624 | ||
|
|
2b586b423a | ||
|
|
04d01984ef | ||
|
|
bd4ecbfe49 | ||
|
|
2f979e9be0 | ||
|
|
4c27c90fc2 | ||
|
|
25879be46a | ||
|
|
64bf80d4d5 | ||
|
|
53a3922e1c | ||
|
|
8ab6891d97 | ||
|
|
517ae6ea14 | ||
|
|
5d91b68af3 | ||
|
|
85d5e4360d | ||
|
|
39fae14c72 | ||
|
|
9368f834c0 | ||
|
|
f804da9444 | ||
|
|
75534f7a47 | ||
|
|
ee12f24760 | ||
|
|
c13d6dbf55 | ||
|
|
ed2798417e | ||
|
|
fbd8990e78 | ||
|
|
ffa2a47c58 | ||
|
|
a9e241dacb | ||
|
|
a1f995053e | ||
|
|
137a56194e | ||
|
|
e2d0a808e0 | ||
|
|
38adeb888c | ||
|
|
2942df6b9f | ||
|
|
8cf1e46a94 | ||
|
|
3557bce827 | ||
|
|
a4b77ad33f | ||
|
|
17203d0af9 | ||
|
|
4629ab3d8a | ||
|
|
06820b6daf | ||
|
|
ca9659ffb0 | ||
|
|
51d851e092 | ||
|
|
b1d853d88b | ||
|
|
8b13710c09 | ||
|
|
70184d0a5e | ||
|
|
d8a1808bd6 | ||
|
|
2b55708f40 | ||
|
|
5eee793669 | ||
|
|
4262abe05d | ||
|
|
eebce9e9c7 | ||
|
|
490b2f881c | ||
|
|
b99b16d71e | ||
|
|
513df9fdb0 | ||
|
|
4604d252b2 | ||
|
|
962cc740a0 | ||
|
|
ef56d79a6a | ||
|
|
e25965ed4a | ||
|
|
a2cc0630d2 | ||
|
|
6e5bc5647a | ||
|
|
8e4115947b | ||
|
|
5783c2e070 | ||
|
|
bd5fe92c94 | ||
|
|
e6c5ce136e | ||
|
|
1d1f36adff | ||
|
|
35b132884c | ||
|
|
5b4669632a | ||
|
|
6ef6e80abe | ||
|
|
d81b5fc792 | ||
|
|
7016659dbe | ||
|
|
a9b982c954 | ||
|
|
3e121edf20 | ||
|
|
be6b841334 | ||
|
|
0235cca58a | ||
|
|
b15d9eb565 | ||
|
|
158c4c1f4f | ||
|
|
f6de4cd766 | ||
|
|
c9ab095099 | ||
|
|
a331270f8a | ||
|
|
bd6c7969ea | ||
|
|
dff3ca2018 | ||
|
|
c959ac3a25 | ||
|
|
9df9bd436e | ||
|
|
66e7e29219 | ||
|
|
417b3dd5e0 | ||
|
|
b96f5d94db | ||
|
|
465f830bcd | ||
|
|
0b94382930 | ||
|
|
dd098596cf | ||
|
|
ea60bc01b9 | ||
|
|
10527ff8a3 | ||
|
|
b49accc273 | ||
|
|
86bac4ee2a | ||
|
|
fa2a318f40 | ||
|
|
e9f54ca815 | ||
|
|
1ff1679984 | ||
|
|
3e5d86384e | ||
|
|
77d15841d7 | ||
|
|
5404b0eaa6 | ||
|
|
708b9339a5 | ||
|
|
14b3360c22 | ||
|
|
7a35bca2ec | ||
|
|
42be3fb059 | ||
|
|
b666ce692f | ||
|
|
60a8dd95de | ||
|
|
40bd36e35d | ||
|
|
e1ff753790 | ||
|
|
ca01595699 | ||
|
|
d7b61228e2 | ||
|
|
ad21d84940 | ||
|
|
ab6ddf7245 | ||
|
|
485bfe95ed | ||
|
|
ba7804df50 | ||
|
|
b75be09144 | ||
|
|
320c0d65a1 | ||
|
|
3fdd7c9e00 | ||
|
|
f862685ed8 | ||
|
|
b33ad4d7cb | ||
|
|
cfbef8035d | ||
|
|
18dc98b00e | ||
|
|
f3b152e0d9 | ||
|
|
7d6d642cb8 | ||
|
|
23aded30d8 | ||
|
|
5e35e6a95f | ||
|
|
b9c60fd37a | ||
|
|
c326083ad8 | ||
|
|
48ff617169 | ||
|
|
ba60154826 | ||
|
|
046a950877 | ||
|
|
6fd1725a06 | ||
|
|
cc359d4c9d | ||
|
|
c9556c257e | ||
|
|
dbf78de7c6 | ||
|
|
aed6283faa | ||
|
|
d4da878d64 | ||
|
|
7de494fcec | ||
|
|
89e6b91b89 | ||
|
|
770c462c47 | ||
|
|
fa0835dd32 | ||
|
|
5a98a1dbe2 | ||
|
|
540b98b23f | ||
|
|
0d9e4f20d5 | ||
|
|
48ae976333 | ||
|
|
4329c93f85 | ||
|
|
984f98be95 | ||
|
|
da845ce598 | ||
|
|
2f43c6b334 | ||
|
|
e3cd209889 | ||
|
|
8e285d112d | ||
|
|
afc649255c | ||
|
|
bdeb7d859b | ||
|
|
b8ff152a98 | ||
|
|
85b169c453 | ||
|
|
f0c1057f68 | ||
|
|
d25c4fd6c5 | ||
|
|
4726593d6d | ||
|
|
96a44d979d | ||
|
|
623f4d3056 | ||
|
|
4ad7d51c01 | ||
|
|
e46e655451 | ||
|
|
5b85d0efa4 | ||
|
|
9f8c4efa9b | ||
|
|
99f0ea8d43 | ||
|
|
16565020a1 | ||
|
|
0ef2a9c8b5 | ||
|
|
9a7ceceffa | ||
|
|
22348914cf | ||
|
|
01bcbcf8d5 | ||
|
|
cad83db8b2 | ||
|
|
0e182dd3e1 | ||
|
|
abf95c5f99 | ||
|
|
0106b0488a | ||
|
|
4890656d9d | ||
|
|
bfad32aa16 | ||
|
|
4151b48d6c | ||
|
|
8eeccb116d |
@@ -59,6 +59,7 @@ Use this skill for Parallels guest workflows and smoke interpretation. Do not lo
|
||||
- Multi-word `openclaw agent --message ...` checks should call `& $openclaw ...` inside PowerShell, not `Start-Process ... -ArgumentList` against `openclaw.cmd`, or Commander can see split argv and throw `too many arguments for 'agent'`.
|
||||
- Windows installer/tgz phases now retry once after guest-ready recheck; keep new Windows smoke steps idempotent so a transport-flake retry is safe.
|
||||
- Windows global `npm install -g` phases can stay quiet for a minute or more even when healthy; inspect the phase log before calling it hung, and only treat it as a regression once the retry wrapper or timeout trips.
|
||||
- Fresh Windows ref-mode onboard should use the same background PowerShell runner plus done-file/log-drain pattern as the npm-update helper, including startup materialization checks, host-side timeouts on short poll `prlctl exec` calls, and retry-on-poll-failure behavior for transient transport flakes.
|
||||
- Keep onboarding and status output ASCII-clean in logs; fancy punctuation becomes mojibake in current capture paths.
|
||||
- If you hit an older run with `rc=255` plus an empty `fresh.install-main.log` or `upgrade.install-main.log`, treat it as a likely `prlctl exec` transport drop after guest start-up, not immediate proof of an npm/package failure.
|
||||
|
||||
|
||||
4
.github/workflows/ci-bun.yml
vendored
4
.github/workflows/ci-bun.yml
vendored
@@ -66,10 +66,12 @@ jobs:
|
||||
run: pnpm canvas:a2ui:bundle
|
||||
|
||||
- name: Upload A2UI bundle artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: canvas-a2ui-bundle
|
||||
path: src/canvas-host/a2ui/
|
||||
include-hidden-files: true
|
||||
retention-days: 1
|
||||
|
||||
bun-checks:
|
||||
name: ${{ matrix.check_name }}
|
||||
|
||||
118
.github/workflows/ci.yml
vendored
118
.github/workflows/ci.yml
vendored
@@ -35,7 +35,6 @@ jobs:
|
||||
has_changed_extensions: ${{ steps.manifest.outputs.has_changed_extensions }}
|
||||
changed_extensions_matrix: ${{ steps.manifest.outputs.changed_extensions_matrix }}
|
||||
run_build_artifacts: ${{ steps.manifest.outputs.run_build_artifacts }}
|
||||
run_release_check: ${{ steps.manifest.outputs.run_release_check }}
|
||||
run_checks_fast: ${{ steps.manifest.outputs.run_checks_fast }}
|
||||
checks_fast_matrix: ${{ steps.manifest.outputs.checks_fast_matrix }}
|
||||
run_checks: ${{ steps.manifest.outputs.run_checks }}
|
||||
@@ -278,34 +277,6 @@ jobs:
|
||||
include-hidden-files: true
|
||||
retention-days: 1
|
||||
|
||||
# Validate npm pack contents after build (only on push to main, not PRs).
|
||||
release-check:
|
||||
needs: [preflight, build-artifacts]
|
||||
if: needs.preflight.outputs.run_release_check == 'true'
|
||||
runs-on: blacksmith-16vcpu-ubuntu-2404
|
||||
timeout-minutes: 20
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
persist-credentials: false
|
||||
submodules: false
|
||||
|
||||
- name: Setup Node environment
|
||||
uses: ./.github/actions/setup-node-env
|
||||
with:
|
||||
install-bun: "false"
|
||||
use-sticky-disk: "false"
|
||||
|
||||
- name: Download dist artifact
|
||||
uses: actions/download-artifact@v8
|
||||
with:
|
||||
name: dist-build
|
||||
path: dist/
|
||||
|
||||
- name: Check release contents
|
||||
run: pnpm release:check
|
||||
|
||||
checks-fast:
|
||||
name: ${{ matrix.check_name }}
|
||||
needs: [preflight]
|
||||
@@ -339,6 +310,7 @@ jobs:
|
||||
pnpm test:extensions
|
||||
;;
|
||||
contracts|contracts-protocol)
|
||||
pnpm build
|
||||
pnpm test:contracts
|
||||
pnpm protocol:check
|
||||
;;
|
||||
@@ -432,8 +404,6 @@ jobs:
|
||||
node openclaw.mjs --help
|
||||
node openclaw.mjs status --json --timeout 1
|
||||
pnpm test:build:singleton
|
||||
node scripts/stage-bundled-plugin-runtime-deps.mjs
|
||||
node --import tsx scripts/release-check.ts
|
||||
;;
|
||||
*)
|
||||
echo "Unsupported checks task: $TASK" >&2
|
||||
@@ -518,6 +488,51 @@ jobs:
|
||||
continue-on-error: true
|
||||
run: pnpm run lint:plugins:no-extension-imports
|
||||
|
||||
- name: Run no-random-messaging guard
|
||||
id: no_random_messaging
|
||||
continue-on-error: true
|
||||
run: pnpm run lint:tmp:no-random-messaging
|
||||
|
||||
- name: Run channel-agnostic boundary guard
|
||||
id: channel_agnostic_boundaries
|
||||
continue-on-error: true
|
||||
run: pnpm run lint:tmp:channel-agnostic-boundaries
|
||||
|
||||
- name: Run no-raw-channel-fetch guard
|
||||
id: no_raw_channel_fetch
|
||||
continue-on-error: true
|
||||
run: pnpm run lint:tmp:no-raw-channel-fetch
|
||||
|
||||
- name: Run ingress owner guard
|
||||
id: ingress_owner
|
||||
continue-on-error: true
|
||||
run: pnpm run lint:agent:ingress-owner
|
||||
|
||||
- name: Run no-register-http-handler guard
|
||||
id: no_register_http_handler
|
||||
continue-on-error: true
|
||||
run: pnpm run lint:plugins:no-register-http-handler
|
||||
|
||||
- name: Run no-monolithic plugin-sdk entry import guard
|
||||
id: no_monolithic_plugin_sdk_entry_imports
|
||||
continue-on-error: true
|
||||
run: pnpm run lint:plugins:no-monolithic-plugin-sdk-entry-imports
|
||||
|
||||
- name: Run no-extension-src-imports guard
|
||||
id: no_extension_src_imports
|
||||
continue-on-error: true
|
||||
run: pnpm run lint:plugins:no-extension-src-imports
|
||||
|
||||
- name: Run no-extension-test-core-imports guard
|
||||
id: no_extension_test_core_imports
|
||||
continue-on-error: true
|
||||
run: pnpm run lint:plugins:no-extension-test-core-imports
|
||||
|
||||
- name: Run plugin-sdk subpaths exported guard
|
||||
id: plugin_sdk_subpaths_exported
|
||||
continue-on-error: true
|
||||
run: pnpm run lint:plugins:plugin-sdk-subpaths-exported
|
||||
|
||||
- name: Run web search provider boundary guard
|
||||
id: web_search_provider_boundary
|
||||
continue-on-error: true
|
||||
@@ -533,6 +548,11 @@ jobs:
|
||||
continue-on-error: true
|
||||
run: pnpm run lint:extensions:no-plugin-sdk-internal
|
||||
|
||||
- name: Run extension relative-outside-package guard
|
||||
id: extension_relative_outside_package_boundary
|
||||
continue-on-error: true
|
||||
run: pnpm run lint:extensions:no-relative-outside-package
|
||||
|
||||
- name: Enforce safe external URL opening policy
|
||||
id: no_raw_window_open
|
||||
continue-on-error: true
|
||||
@@ -543,16 +563,6 @@ jobs:
|
||||
continue-on-error: true
|
||||
run: pnpm test:gateway:watch-regression
|
||||
|
||||
- name: Check config docs drift statefile
|
||||
id: config_docs_drift
|
||||
continue-on-error: true
|
||||
run: pnpm config:docs:check
|
||||
|
||||
- name: Check plugin SDK API baseline drift
|
||||
id: plugin_sdk_api_drift
|
||||
continue-on-error: true
|
||||
run: pnpm plugin-sdk:api:check
|
||||
|
||||
- name: Upload gateway watch regression artifacts
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v7
|
||||
@@ -565,24 +575,40 @@ jobs:
|
||||
if: always()
|
||||
env:
|
||||
PLUGIN_EXTENSION_BOUNDARY_OUTCOME: ${{ steps.plugin_extension_boundary.outcome }}
|
||||
NO_RANDOM_MESSAGING_OUTCOME: ${{ steps.no_random_messaging.outcome }}
|
||||
CHANNEL_AGNOSTIC_BOUNDARIES_OUTCOME: ${{ steps.channel_agnostic_boundaries.outcome }}
|
||||
NO_RAW_CHANNEL_FETCH_OUTCOME: ${{ steps.no_raw_channel_fetch.outcome }}
|
||||
INGRESS_OWNER_OUTCOME: ${{ steps.ingress_owner.outcome }}
|
||||
NO_REGISTER_HTTP_HANDLER_OUTCOME: ${{ steps.no_register_http_handler.outcome }}
|
||||
NO_MONOLITHIC_PLUGIN_SDK_ENTRY_IMPORTS_OUTCOME: ${{ steps.no_monolithic_plugin_sdk_entry_imports.outcome }}
|
||||
NO_EXTENSION_SRC_IMPORTS_OUTCOME: ${{ steps.no_extension_src_imports.outcome }}
|
||||
NO_EXTENSION_TEST_CORE_IMPORTS_OUTCOME: ${{ steps.no_extension_test_core_imports.outcome }}
|
||||
PLUGIN_SDK_SUBPATHS_EXPORTED_OUTCOME: ${{ steps.plugin_sdk_subpaths_exported.outcome }}
|
||||
WEB_SEARCH_PROVIDER_BOUNDARY_OUTCOME: ${{ steps.web_search_provider_boundary.outcome }}
|
||||
EXTENSION_SRC_OUTSIDE_PLUGIN_SDK_BOUNDARY_OUTCOME: ${{ steps.extension_src_outside_plugin_sdk_boundary.outcome }}
|
||||
EXTENSION_PLUGIN_SDK_INTERNAL_BOUNDARY_OUTCOME: ${{ steps.extension_plugin_sdk_internal_boundary.outcome }}
|
||||
EXTENSION_RELATIVE_OUTSIDE_PACKAGE_BOUNDARY_OUTCOME: ${{ steps.extension_relative_outside_package_boundary.outcome }}
|
||||
NO_RAW_WINDOW_OPEN_OUTCOME: ${{ steps.no_raw_window_open.outcome }}
|
||||
GATEWAY_WATCH_REGRESSION_OUTCOME: ${{ steps.gateway_watch_regression.outcome }}
|
||||
CONFIG_DOCS_DRIFT_OUTCOME: ${{ steps.config_docs_drift.outcome }}
|
||||
PLUGIN_SDK_API_DRIFT_OUTCOME: ${{ steps.plugin_sdk_api_drift.outcome }}
|
||||
run: |
|
||||
failures=0
|
||||
for result in \
|
||||
"plugin-extension-boundary|$PLUGIN_EXTENSION_BOUNDARY_OUTCOME" \
|
||||
"lint:tmp:no-random-messaging|$NO_RANDOM_MESSAGING_OUTCOME" \
|
||||
"lint:tmp:channel-agnostic-boundaries|$CHANNEL_AGNOSTIC_BOUNDARIES_OUTCOME" \
|
||||
"lint:tmp:no-raw-channel-fetch|$NO_RAW_CHANNEL_FETCH_OUTCOME" \
|
||||
"lint:agent:ingress-owner|$INGRESS_OWNER_OUTCOME" \
|
||||
"lint:plugins:no-register-http-handler|$NO_REGISTER_HTTP_HANDLER_OUTCOME" \
|
||||
"lint:plugins:no-monolithic-plugin-sdk-entry-imports|$NO_MONOLITHIC_PLUGIN_SDK_ENTRY_IMPORTS_OUTCOME" \
|
||||
"lint:plugins:no-extension-src-imports|$NO_EXTENSION_SRC_IMPORTS_OUTCOME" \
|
||||
"lint:plugins:no-extension-test-core-imports|$NO_EXTENSION_TEST_CORE_IMPORTS_OUTCOME" \
|
||||
"lint:plugins:plugin-sdk-subpaths-exported|$PLUGIN_SDK_SUBPATHS_EXPORTED_OUTCOME" \
|
||||
"web-search-provider-boundary|$WEB_SEARCH_PROVIDER_BOUNDARY_OUTCOME" \
|
||||
"extension-src-outside-plugin-sdk-boundary|$EXTENSION_SRC_OUTSIDE_PLUGIN_SDK_BOUNDARY_OUTCOME" \
|
||||
"extension-plugin-sdk-internal-boundary|$EXTENSION_PLUGIN_SDK_INTERNAL_BOUNDARY_OUTCOME" \
|
||||
"extension-relative-outside-package-boundary|$EXTENSION_RELATIVE_OUTSIDE_PACKAGE_BOUNDARY_OUTCOME" \
|
||||
"lint:ui:no-raw-window-open|$NO_RAW_WINDOW_OPEN_OUTCOME" \
|
||||
"gateway-watch-regression|$GATEWAY_WATCH_REGRESSION_OUTCOME" \
|
||||
"config-docs-drift|$CONFIG_DOCS_DRIFT_OUTCOME" \
|
||||
"plugin-sdk-api-drift|$PLUGIN_SDK_API_DRIFT_OUTCOME"; do
|
||||
"gateway-watch-regression|$GATEWAY_WATCH_REGRESSION_OUTCOME"; do
|
||||
name="${result%%|*}"
|
||||
outcome="${result#*|}"
|
||||
if [ "$outcome" != "success" ]; then
|
||||
|
||||
22
AGENTS.md
22
AGENTS.md
@@ -60,7 +60,8 @@
|
||||
- Runtime baseline: Node **22+** (keep Node + Bun paths working).
|
||||
- Install deps: `pnpm install`
|
||||
- If deps are missing (for example `node_modules` missing, `vitest not found`, or `command not found`), run the repo’s package-manager install command (prefer lockfile/README-defined PM), then rerun the exact requested command once. Apply this to test/build/lint/typecheck/dev commands; if retry still fails, report the command and first actionable error.
|
||||
- Pre-commit hooks: `prek install` (runs same checks as CI)
|
||||
- Pre-commit hooks: `prek install`. The hook runs the repo verification flow, including `pnpm check`.
|
||||
- `FAST_COMMIT=1` skips the repo-wide `pnpm format` and `pnpm check` inside the pre-commit hook only. Use it when you intentionally want a faster commit path and are running equivalent targeted verification manually. It does not change CI and does not change what `pnpm check` itself does.
|
||||
- Also supported: `bun install` (keep `pnpm-lock.yaml` + Bun patching in sync when touching deps/patches).
|
||||
- Prefer Bun for TypeScript execution (scripts, dev, tests): `bun <file.ts>` / `bunx <tool>`.
|
||||
- Run CLI in dev: `pnpm openclaw ...` (bun) or `pnpm dev`.
|
||||
@@ -71,16 +72,28 @@
|
||||
- Lint/format: `pnpm check`
|
||||
- Format check: `pnpm format` (oxfmt --check)
|
||||
- Format fix: `pnpm format:fix` (oxfmt --write)
|
||||
- Terminology:
|
||||
- "gate" means a verification command or command set that must be green for the decision you are making.
|
||||
- A local dev gate is the fast default loop, usually `pnpm check` plus any scoped test you actually need.
|
||||
- A landing gate is the broader bar before pushing `main`, usually `pnpm check`, `pnpm test`, and `pnpm build` when the touched surface can affect build output, packaging, lazy-loading/module boundaries, or published surfaces.
|
||||
- A CI gate is whatever the relevant workflow enforces for that lane (for example `check`, `check-additional`, `build-smoke`, or release validation).
|
||||
- Local dev gate: prefer `pnpm check` for the normal edit loop. It keeps the repo-architecture policy guards out of the default local loop.
|
||||
- CI architecture gate: `check-additional` enforces architecture and boundary policy guards that are intentionally kept out of the default local loop.
|
||||
- Formatting gate: the pre-commit hook runs `pnpm format` before `pnpm check`. If you want a formatting-only preflight locally, run `pnpm format` explicitly.
|
||||
- If you need a fast commit loop, `FAST_COMMIT=1 git commit ...` skips the hook’s repo-wide `pnpm format` and `pnpm check`; use that only when you are deliberately covering the touched surface some other way.
|
||||
- Tests: `pnpm test` (vitest); coverage: `pnpm test:coverage`
|
||||
- Generated baseline artifacts live together under `docs/.generated/`.
|
||||
- Config schema drift uses `pnpm config:docs:gen` / `pnpm config:docs:check`.
|
||||
- Plugin SDK API drift uses `pnpm plugin-sdk:api:gen` / `pnpm plugin-sdk:api:check`.
|
||||
- If you change config schema/help or the public Plugin SDK surface, update the matching baseline artifact and keep the two drift-check flows adjacent in scripts/workflows/docs guidance rather than inventing a third pattern.
|
||||
- For narrowly scoped changes, prefer narrowly scoped tests that directly validate the touched behavior. If no meaningful scoped test exists, say so explicitly and use the next most direct validation available.
|
||||
- Preferred landing bar for pushes to `main`: `pnpm check` and `pnpm test`, with a green result when feasible.
|
||||
- Verification modes for work on `main`:
|
||||
- Default mode: `main` is relatively stable. Count pre-commit hook coverage when it already verified the current tree, avoid rerunning the exact same checks just for ceremony, and prefer keeping CI/main green before landing.
|
||||
- Fast-commit mode: `main` is moving fast and you intentionally optimize for shorter commit loops. Prefer explicit local verification close to the final landing point, and it is acceptable to use `--no-verify` for intermediate or catch-up commits after equivalent checks have already run locally.
|
||||
- Preferred landing bar for pushes to `main`: in Default mode, favor `pnpm check` and `pnpm test` near the final rebase/push point when feasible. In fast-commit mode, verify the touched surface locally near landing without insisting every intermediate commit replay the full hook.
|
||||
- Scoped tests prove the change itself. `pnpm test` remains the default `main` landing bar; scoped tests do not replace full-suite gates by default.
|
||||
- Hard gate: if the change can affect build output, packaging, lazy-loading/module boundaries, or published surfaces, `pnpm build` MUST be run and MUST pass before pushing `main`.
|
||||
- Default rule: do not commit or push with failing format, lint, type, build, or required test checks when those failures are caused by the change or plausibly related to the touched surface.
|
||||
- Default rule: do not land changes with failing format, lint, type, build, or required test checks when those failures are caused by the change or plausibly related to the touched surface. Fast-commit mode changes how verification is sequenced; it does not lower the requirement to validate and clean up the touched surface before final landing.
|
||||
- For narrowly scoped changes, if unrelated failures already exist on latest `origin/main`, state that clearly, report the scoped tests you ran, and ask before broadening scope into unrelated fixes or landing despite those failures.
|
||||
- Do not use scoped tests as permission to ignore plausibly related failures.
|
||||
|
||||
@@ -88,7 +101,8 @@
|
||||
|
||||
- Language: TypeScript (ESM). Prefer strict typing; avoid `any`.
|
||||
- Formatting/linting via Oxlint and Oxfmt.
|
||||
- Never add `@ts-nocheck` and do not disable `no-explicit-any`; fix root causes and update Oxlint/Oxfmt config only when required.
|
||||
- Never add `@ts-nocheck` and do not add inline lint suppressions by default. Fix root causes first; only keep a suppression when the code is intentionally correct, the rule cannot express that safely, and the comment explains why.
|
||||
- Do not disable `no-explicit-any`; prefer real types, `unknown`, or a narrow adapter/helper instead. Update Oxlint/Oxfmt config only when required.
|
||||
- Dynamic import guardrail: do not mix `await import("x")` and static `import ... from "x"` for the same module in production code paths. If you need lazy loading, create a dedicated `*.runtime.ts` boundary (that re-exports from `x`) and dynamically import that boundary from lazy callers only.
|
||||
- Dynamic import verification: after refactors that touch lazy-loading/module boundaries, run `pnpm build` and check for `[INEFFECTIVE_DYNAMIC_IMPORT]` warnings before submitting.
|
||||
- Extension SDK self-import guardrail: inside an extension package, do not import that same extension via `openclaw/plugin-sdk/<extension>` from production files. Route internal imports through a local barrel such as `./api.ts` or `./runtime-api.ts`, and keep the `plugin-sdk/<extension>` path as the external contract only.
|
||||
|
||||
33
CHANGELOG.md
33
CHANGELOG.md
@@ -7,11 +7,14 @@ Docs: https://docs.openclaw.ai
|
||||
### Breaking
|
||||
|
||||
- Providers/Qwen: remove the deprecated `qwen-portal-auth` OAuth integration for `portal.qwen.ai`; migrate to Model Studio with `openclaw onboard --auth-choice modelstudio-api-key`. (#52709) Thanks @pomelo-nwu.
|
||||
- Config/Doctor: drop automatic config migrations older than two months; very old legacy keys now fail validation instead of being rewritten on load or by `openclaw doctor`.
|
||||
|
||||
### Changes
|
||||
|
||||
- MiniMax: add image generation provider for `image-01` model, supporting generate and image-to-image editing with aspect ratio control. (#54487) Thanks @liyuan97.
|
||||
- Podman: simplify the container setup around the current rootless user, install the launch helper under `~/.local/bin`, and document the host-CLI `openclaw --container <name> ...` workflow instead of a dedicated `openclaw` service user.
|
||||
- Slack/tool actions: add an explicit `upload-file` Slack action that routes file uploads through the existing Slack upload transport, with optional filename/title/comment overrides for channels and DMs.
|
||||
- Message actions/files: start unifying file-first sends on the canonical `upload-file` action by adding explicit support for Microsoft Teams and Google Chat, and by exposing BlueBubbles file sends through `upload-file` while keeping the legacy `sendAttachment` alias.
|
||||
- Plugins/Matrix TTS: send auto-TTS replies as native Matrix voice bubbles instead of generic audio attachments. (#37080) thanks @Matthew19990919.
|
||||
- Memory/plugins: move the pre-compaction memory flush plan behind the active memory plugin contract so `memory-core` owns flush prompts and target-path policy instead of hardcoded core logic.
|
||||
- MiniMax: trim model catalog to M2.7 only, removing legacy M2, M2.1, M2.5, and VL-01 models. (#54487) Thanks @liyuan97.
|
||||
@@ -21,9 +24,15 @@ Docs: https://docs.openclaw.ai
|
||||
- Agents/compaction: surface safeguard-specific cancel reasons and relabel benign manual `/compact` no-op cases as skipped instead of failed. (#51072) Thanks @afurm.
|
||||
- Plugins/CLI backends: move bundled Claude CLI, Codex CLI, and Gemini CLI inference defaults onto the plugin surface, add bundled Gemini CLI backend support, and replace `gateway run --claude-cli-logs` with generic `--cli-backend-logs` while keeping the old flag as a compatibility alias.
|
||||
- Plugins/startup: auto-load bundled provider and CLI-backend plugins from explicit config refs, so bundled Claude CLI, Codex CLI, and Gemini CLI message-provider setups no longer need manual `plugins.allow` entries.
|
||||
- Config/TTS: auto-migrate legacy speech config on normal reads and secret resolution, keep legacy diagnostics for Doctor, and remove regular-mode runtime fallback for old bundled `tts.<provider>` API-key shapes.
|
||||
- OpenAI/apply_patch: enable `apply_patch` by default for OpenAI and OpenAI Codex models, and align its sandbox policy access with `write` permissions.
|
||||
- Docs: add `pnpm docs:check-links:anchors` for Mintlify anchor validation while keeping `scripts/docs-link-audit.mjs` as the stable link-audit entrypoint. (#55912) Thanks @velvet-shark.
|
||||
- Plugins/hooks: add async `requireApproval` to `before_tool_call` hooks, letting plugins pause tool execution and prompt the user for approval via the exec approval overlay, Telegram buttons, Discord interactions, or the `/approve` command on any channel. The `/approve` command now handles both exec and plugin approvals with automatic fallback. (#55339) Thanks @vaclavbelak and @joshavant.
|
||||
|
||||
### Fixes
|
||||
|
||||
- ACP/ACPX agent registry: align OpenClaw's ACPX built-in agent mirror with the latest `openclaw/acpx` command defaults and built-in aliases, pin versioned `npx` built-ins to exact versions, and stop unknown ACP agent ids from falling through to raw `--agent` command execution on the MCP-proxy path. (#28321) Thanks @m0nkmaster and @vincentkoc.
|
||||
- Control UI/config: keep sensitive raw config hidden by default, replace the blank blocked editor with an explicit reveal-to-edit state, and restore raw JSON editing without auto-exposing secrets. Fixes #55322.
|
||||
- WhatsApp: fix infinite echo loop in self-chat DM mode where the bot's own outbound replies were re-processed as new inbound user messages. (#54570) Thanks @joelnishanth
|
||||
- OpenAI Codex/image tools: register Codex for media understanding and route image prompts through Codex instructions so image analysis no longer fails on missing provider registration or missing `instructions`. (#54829) Thanks @neeravmakwana.
|
||||
- Agents/image tool: restore the generic image-runtime fallback when no provider-specific media-understanding provider is registered, so image analysis works again for providers like `openrouter` and `minimax-portal`. (#54858) Thanks @MonkeyLeeT.
|
||||
@@ -36,8 +45,13 @@ Docs: https://docs.openclaw.ai
|
||||
- CLI/message send: write manual `openclaw message send` deliveries into the resolved agent session transcript again by always threading the default CLI agent through outbound mirroring. (#54187) Thanks @KevInTheCloud5617.
|
||||
- CLI/onboarding: show the Kimi Code API key option again in the Moonshot setup menu so the interactive picker includes all Kimi setup paths together. Fixes #54412 Thanks @sparkyrider
|
||||
- Agents/status: use provider-aware context window lookup for fresh Anthropic 4.6 model overrides so `/status` shows the correct 1.0m window instead of an underreported shared-cache minimum. (#54796) Thanks @neeravmakwana.
|
||||
- OpenAI/WebSocket: preserve reasoning replay metadata and tool-call item ids on WebSocket tool turns, and start a fresh response chain when full-context resend is required. (#53856) Thanks @xujingchen1996.
|
||||
- OpenAI/WS: restore reasoning blocks for Responses WebSocket runs and keep reasoning/tool-call replay metadata intact so resumed sessions do not lose or break follow-up reasoning-capable turns. (#53856) Thanks @xujingchen1996.
|
||||
- Agents/errors: surface provider quota/reset details when available, but keep HTML/Cloudflare rate-limit pages on the generic fallback so raw error pages are not shown to users. (#54512) Thanks @bugkill3r.
|
||||
- Claude CLI: switch the bundled Claude CLI backend to `stream-json` output so watchdogs see progress on long runs, and keep session/usage metadata even when Claude finishes with an empty result line. (#49698) Thanks @felear2022.
|
||||
- Claude CLI/MCP: always pass a strict generated `--mcp-config` overlay for background Claude CLI runs, including the empty-server case, so Claude does not inherit ambient user/global MCP servers. (#54961) Thanks @markojak.
|
||||
- Agents/embedded replies: surface mid-turn 429 and overload failures when embedded runs end without a user-visible reply, while preserving successful media-only replies that still use legacy `mediaUrl`. (#50930) Thanks @infichen.
|
||||
- Chat/UI: move the chat send button onto the shared ghost-button theme styling, while keeping the stop button icon readable on the danger state. (#55075) Thanks @bottenbenny.
|
||||
- WhatsApp/allowFrom: show a specific allowFrom policy error for valid blocked targets instead of the misleading `<E.164|group JID>` format hint. Thanks @mcaxtr.
|
||||
- Agents/cooldowns: scope rate-limit cooldowns per model so one 429 no longer blocks every model on the same auth profile, replace the exponential 1 min -> 1 h escalation with a stepped 30 s / 1 min / 5 min ladder, and surface a user-facing countdown message when all models are rate-limited. (#49834) Thanks @kiranvk-2011.
|
||||
- Agents/embedded transport errors: distinguish common network failures like connection refused, DNS lookup failure, and interrupted sockets from true timeouts in embedded-run user messaging and lifecycle diagnostics. (#51419) Thanks @scoootscooob.
|
||||
@@ -61,6 +75,24 @@ Docs: https://docs.openclaw.ai
|
||||
- Agents/failover: classify Codex accountId token extraction failures as auth errors so model fallback continues to the next configured candidate. (#55206) Thanks @cosmicnet.
|
||||
- Talk/macOS: stop direct system-voice failures from replaying system speech, use app-locale fallback for shared watchdog timing, and add regression coverage for the macOS fallback route and language-aware timeout policy. (#53511) thanks @hongsw.
|
||||
- Discord/gateway cleanup: keep late Carbon reconnect-exhausted errors suppressed through startup/dispose cleanup so Discord monitor shutdown no longer crashes on late gateway close events. (#55373) Thanks @Takhoffman.
|
||||
- Discord/gateway shutdown: treat expected reconnect-exhausted events during intentional lifecycle stop as clean shutdowns so startup-abort cleanup no longer surfaces false gateway failures. (#55324) Thanks @joelnishanth.
|
||||
- Discord/gateway shutdown: suppress reconnect-exhausted events that were already buffered before teardown flips `lifecycleStopping`, so stale-socket Discord restarts no longer crash the whole gateway. Fixes #55403 and #55421. Thanks @lml2468 and @vincentkoc.
|
||||
- GitHub Copilot/auth refresh: treat large `expires_at` values as seconds epochs and clamp far-future runtime auth refresh timers so Copilot token refresh cannot fall into a `setTimeout` overflow hot loop. (#55360) Thanks @michael-abdo.
|
||||
- Agents/status: use the persisted runtime session model in `session_status` when no explicit override exists, and honor per-agent `thinkingDefault` in both `session_status` and `/status`. (#55425) Thanks @scoootscooob, @xaeon2026, and @ysfbsf.
|
||||
- Heartbeat/runner: guarantee the interval timer is re-armed after heartbeat runs and unexpected runner errors so scheduled heartbeats do not silently stop after an interrupted cycle. (#52270) Thanks @MiloStack.
|
||||
- Config/Doctor: rewrite stale bundled plugin load paths from legacy `extensions/*` locations to the packaged bundled path, including directory-name mismatches and slash-suffixed config entries. (#55054) Thanks @SnowSky1.
|
||||
- WhatsApp/mentions: stop treating mentions embedded in quoted messages as direct mentions so replying to a message that @mentioned the bot no longer falsely triggers mention gating. (#52711) Thanks @lurebat.
|
||||
- Agents/ollama fallback: surface non-2xx Ollama HTTP errors with a leading status code so HTTP 503 responses trigger model fallback again. (#55214) Thanks @bugkill3r.
|
||||
- Feishu/tools: stop synthetic agent ids like `agent-spawner` from being treated as Feishu account ids during tool execution, so tools fall back to the configured/default Feishu account unless the contextual id is a real enabled Feishu account. (#55627) Thanks @MonkeyLeeT.
|
||||
- Google/tools: strip empty `required: []` arrays from Gemini tool schemas so optional-only tool parameters no longer trigger Google validator 400s. (#52106) Thanks @oliviareid-svg.
|
||||
- Onboarding/TUI/local gateways: show the resolved gateway port in setup output, clarify no-daemon local health/dashboard messaging, and preserve loopback Control UI auth on reruns and explicit local gateway URLs so local quickstart flows recover cleanly. (#55730) Thanks @shakkernerd.
|
||||
- TUI/chat log: keep system messages as single logical entries and prune overflow at whole-message boundaries so wrapped system spacing stays intact. (#55732) Thanks @shakkernerd.
|
||||
- TUI/activation: validate `/activation` arguments in the TUI and reject invalid values instead of silently coercing them to `mention`. (#55733) Thanks @shakkernerd.
|
||||
- Agents/model switching: apply `/model` changes to active embedded runs at the next safe retry boundary, so overloaded or retrying turns switch to the newly selected model instead of staying pinned to the old provider.
|
||||
- Agents/Codex fallback: classify Codex `server_error` payloads as failoverable, sanitize `Codex error:` payloads before they reach chat, preserve context-overflow guidance for prefixed `invalid_request_error` payloads, and omit provider `request_id` values from user-facing UI copy. (#42892) Thanks @xaeon2026.
|
||||
- Memory/search: share memory embedding provider registrations across split plugin runtimes so memory search no longer fails with unknown provider errors after memory-core registers built-in adapters. (#55945) Thanks @glitch418x.
|
||||
- Discord/Carbon beta: update `@buape/carbon` to the latest beta and pass the new `RateLimitError` request argument so Discord stays compatible with the upstream beta constructor change. (#55980) Thanks @ngutman.
|
||||
- Plugins/inbound claims: pass full inbound attachment arrays through `inbound_claim` hook metadata while keeping the legacy singular media attachment fields for compatibility. (#55452) Thanks @huntharo.
|
||||
|
||||
## 2026.3.24
|
||||
|
||||
@@ -183,6 +215,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Discord/config types: add missing `autoArchiveDuration` to `DiscordGuildChannelConfig` so TypeScript config definitions match the existing schema and runtime support. (#43427) Thanks @davidguttman.
|
||||
- Docs/IRC: fix five `json55` code-fence typos in the IRC channel examples so Mintlify applies JSON5 syntax highlighting correctly. (#50842) Thanks @Hollychou924.
|
||||
- Discord/commands: trim overlong slash-command descriptions to Discord's 100-character limit and map rejected deploy indexes from Discord validation payloads back to command names/descriptions, so deploys stop failing on long descriptions and startup logs identify the rejected commands. (#54118) thanks @huntharo
|
||||
- Media/store: enforce the intended media file mode after writes and redirect downloads so restrictive umasks do not silently narrow saved media permissions.
|
||||
|
||||
## 2026.3.23
|
||||
|
||||
|
||||
@@ -94,6 +94,7 @@ Welcome to the lobster tank! 🦞
|
||||
- `pnpm test:extension --list` to see valid extension ids
|
||||
- If you changed shared plugin or channel surfaces, run `pnpm test:contracts`
|
||||
- For targeted shared-surface work, use `pnpm test:contracts:channels` or `pnpm test:contracts:plugins`
|
||||
- These commands also cover the shared seam/smoke files that the default unit lane skips
|
||||
- If you changed broader runtime behavior, still run the relevant wider lanes (`pnpm test:extensions`, `pnpm test:channels`, or `pnpm test`) before asking for review
|
||||
- If you have access to Codex, run `codex review --base origin/main` locally before opening or updating your PR. Treat this as the current highest standard of AI review, even if GitHub Codex review also runs.
|
||||
- Do not submit refactor-only PRs unless a maintainer explicitly requested that refactor for an active fix or deliverable.
|
||||
|
||||
@@ -65,8 +65,8 @@ android {
|
||||
applicationId = "ai.openclaw.app"
|
||||
minSdk = 31
|
||||
targetSdk = 36
|
||||
versionCode = 2026032500
|
||||
versionName = "2026.3.25"
|
||||
versionCode = 2026032600
|
||||
versionName = "2026.3.26"
|
||||
ndk {
|
||||
// Support all major ABIs — native libs are tiny (~47 KB per ABI)
|
||||
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// Shared iOS version defaults.
|
||||
// Generated overrides live in build/Version.xcconfig (git-ignored).
|
||||
|
||||
OPENCLAW_GATEWAY_VERSION = 2026.3.25
|
||||
OPENCLAW_MARKETING_VERSION = 2026.3.25
|
||||
OPENCLAW_BUILD_VERSION = 202603250
|
||||
OPENCLAW_GATEWAY_VERSION = 2026.3.26
|
||||
OPENCLAW_MARKETING_VERSION = 2026.3.26
|
||||
OPENCLAW_BUILD_VERSION = 202603260
|
||||
|
||||
#include? "../build/Version.xcconfig"
|
||||
|
||||
@@ -65,9 +65,9 @@ Release behavior:
|
||||
- Beta release also switches the app to `OpenClawPushTransport=relay`, `OpenClawPushDistribution=official`, and `OpenClawPushAPNsEnvironment=production`.
|
||||
- The beta flow does not modify `apps/ios/.local-signing.xcconfig` or `apps/ios/LocalSigning.xcconfig`.
|
||||
- Root `package.json.version` is the only version source for iOS.
|
||||
- A root version like `2026.3.22-beta.1` becomes:
|
||||
- `CFBundleShortVersionString = 2026.3.22`
|
||||
- `CFBundleVersion = next TestFlight build number for 2026.3.22`
|
||||
- A root version like `2026.3.26-beta.1` becomes:
|
||||
- `CFBundleShortVersionString = 2026.3.26`
|
||||
- `CFBundleVersion = next TestFlight build number for 2026.3.26`
|
||||
|
||||
Required env for beta builds:
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ enum HostEnvSecurityPolicy {
|
||||
"ENV",
|
||||
"GIT_EXTERNAL_DIFF",
|
||||
"GIT_EXEC_PATH",
|
||||
"GIT_TEMPLATE_DIR",
|
||||
"SHELL",
|
||||
"SHELLOPTS",
|
||||
"PS4",
|
||||
@@ -79,7 +80,8 @@ enum HostEnvSecurityPolicy {
|
||||
"GEM_PATH",
|
||||
"BUNDLE_GEMFILE",
|
||||
"COMPOSER_HOME",
|
||||
"XDG_CONFIG_HOME"
|
||||
"XDG_CONFIG_HOME",
|
||||
"AWS_CONFIG_FILE"
|
||||
]
|
||||
|
||||
static let blockedOverridePrefixes: [String] = [
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2026.3.25</string>
|
||||
<string>2026.3.26</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>202603250</string>
|
||||
<string>202603260</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>OpenClaw</string>
|
||||
<key>CFBundleURLTypes</key>
|
||||
|
||||
@@ -9,6 +9,7 @@ public enum ErrorCode: String, Codable, Sendable {
|
||||
case notPaired = "NOT_PAIRED"
|
||||
case agentTimeout = "AGENT_TIMEOUT"
|
||||
case invalidRequest = "INVALID_REQUEST"
|
||||
case approvalNotFound = "APPROVAL_NOT_FOUND"
|
||||
case unavailable = "UNAVAILABLE"
|
||||
}
|
||||
|
||||
@@ -3434,6 +3435,90 @@ public struct ExecApprovalResolveParams: Codable, Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
public struct PluginApprovalRequestParams: Codable, Sendable {
|
||||
public let pluginid: String?
|
||||
public let title: String
|
||||
public let description: String
|
||||
public let severity: String?
|
||||
public let toolname: String?
|
||||
public let toolcallid: String?
|
||||
public let agentid: String?
|
||||
public let sessionkey: String?
|
||||
public let turnsourcechannel: String?
|
||||
public let turnsourceto: String?
|
||||
public let turnsourceaccountid: String?
|
||||
public let turnsourcethreadid: AnyCodable?
|
||||
public let timeoutms: Int?
|
||||
public let twophase: Bool?
|
||||
|
||||
public init(
|
||||
pluginid: String?,
|
||||
title: String,
|
||||
description: String,
|
||||
severity: String?,
|
||||
toolname: String?,
|
||||
toolcallid: String?,
|
||||
agentid: String?,
|
||||
sessionkey: String?,
|
||||
turnsourcechannel: String?,
|
||||
turnsourceto: String?,
|
||||
turnsourceaccountid: String?,
|
||||
turnsourcethreadid: AnyCodable?,
|
||||
timeoutms: Int?,
|
||||
twophase: Bool?)
|
||||
{
|
||||
self.pluginid = pluginid
|
||||
self.title = title
|
||||
self.description = description
|
||||
self.severity = severity
|
||||
self.toolname = toolname
|
||||
self.toolcallid = toolcallid
|
||||
self.agentid = agentid
|
||||
self.sessionkey = sessionkey
|
||||
self.turnsourcechannel = turnsourcechannel
|
||||
self.turnsourceto = turnsourceto
|
||||
self.turnsourceaccountid = turnsourceaccountid
|
||||
self.turnsourcethreadid = turnsourcethreadid
|
||||
self.timeoutms = timeoutms
|
||||
self.twophase = twophase
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case pluginid = "pluginId"
|
||||
case title
|
||||
case description
|
||||
case severity
|
||||
case toolname = "toolName"
|
||||
case toolcallid = "toolCallId"
|
||||
case agentid = "agentId"
|
||||
case sessionkey = "sessionKey"
|
||||
case turnsourcechannel = "turnSourceChannel"
|
||||
case turnsourceto = "turnSourceTo"
|
||||
case turnsourceaccountid = "turnSourceAccountId"
|
||||
case turnsourcethreadid = "turnSourceThreadId"
|
||||
case timeoutms = "timeoutMs"
|
||||
case twophase = "twoPhase"
|
||||
}
|
||||
}
|
||||
|
||||
public struct PluginApprovalResolveParams: Codable, Sendable {
|
||||
public let id: String
|
||||
public let decision: String
|
||||
|
||||
public init(
|
||||
id: String,
|
||||
decision: String)
|
||||
{
|
||||
self.id = id
|
||||
self.decision = decision
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case id
|
||||
case decision
|
||||
}
|
||||
}
|
||||
|
||||
public struct DevicePairListParams: Codable, Sendable {}
|
||||
|
||||
public struct DevicePairApproveParams: Codable, Sendable {
|
||||
|
||||
@@ -9,6 +9,7 @@ public enum ErrorCode: String, Codable, Sendable {
|
||||
case notPaired = "NOT_PAIRED"
|
||||
case agentTimeout = "AGENT_TIMEOUT"
|
||||
case invalidRequest = "INVALID_REQUEST"
|
||||
case approvalNotFound = "APPROVAL_NOT_FOUND"
|
||||
case unavailable = "UNAVAILABLE"
|
||||
}
|
||||
|
||||
@@ -3434,6 +3435,90 @@ public struct ExecApprovalResolveParams: Codable, Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
public struct PluginApprovalRequestParams: Codable, Sendable {
|
||||
public let pluginid: String?
|
||||
public let title: String
|
||||
public let description: String
|
||||
public let severity: String?
|
||||
public let toolname: String?
|
||||
public let toolcallid: String?
|
||||
public let agentid: String?
|
||||
public let sessionkey: String?
|
||||
public let turnsourcechannel: String?
|
||||
public let turnsourceto: String?
|
||||
public let turnsourceaccountid: String?
|
||||
public let turnsourcethreadid: AnyCodable?
|
||||
public let timeoutms: Int?
|
||||
public let twophase: Bool?
|
||||
|
||||
public init(
|
||||
pluginid: String?,
|
||||
title: String,
|
||||
description: String,
|
||||
severity: String?,
|
||||
toolname: String?,
|
||||
toolcallid: String?,
|
||||
agentid: String?,
|
||||
sessionkey: String?,
|
||||
turnsourcechannel: String?,
|
||||
turnsourceto: String?,
|
||||
turnsourceaccountid: String?,
|
||||
turnsourcethreadid: AnyCodable?,
|
||||
timeoutms: Int?,
|
||||
twophase: Bool?)
|
||||
{
|
||||
self.pluginid = pluginid
|
||||
self.title = title
|
||||
self.description = description
|
||||
self.severity = severity
|
||||
self.toolname = toolname
|
||||
self.toolcallid = toolcallid
|
||||
self.agentid = agentid
|
||||
self.sessionkey = sessionkey
|
||||
self.turnsourcechannel = turnsourcechannel
|
||||
self.turnsourceto = turnsourceto
|
||||
self.turnsourceaccountid = turnsourceaccountid
|
||||
self.turnsourcethreadid = turnsourcethreadid
|
||||
self.timeoutms = timeoutms
|
||||
self.twophase = twophase
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case pluginid = "pluginId"
|
||||
case title
|
||||
case description
|
||||
case severity
|
||||
case toolname = "toolName"
|
||||
case toolcallid = "toolCallId"
|
||||
case agentid = "agentId"
|
||||
case sessionkey = "sessionKey"
|
||||
case turnsourcechannel = "turnSourceChannel"
|
||||
case turnsourceto = "turnSourceTo"
|
||||
case turnsourceaccountid = "turnSourceAccountId"
|
||||
case turnsourcethreadid = "turnSourceThreadId"
|
||||
case timeoutms = "timeoutMs"
|
||||
case twophase = "twoPhase"
|
||||
}
|
||||
}
|
||||
|
||||
public struct PluginApprovalResolveParams: Codable, Sendable {
|
||||
public let id: String
|
||||
public let decision: String
|
||||
|
||||
public init(
|
||||
id: String,
|
||||
decision: String)
|
||||
{
|
||||
self.id = id
|
||||
self.decision = decision
|
||||
}
|
||||
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case id
|
||||
case decision
|
||||
}
|
||||
}
|
||||
|
||||
public struct DevicePairListParams: Codable, Sendable {}
|
||||
|
||||
public struct DevicePairApproveParams: Codable, Sendable {
|
||||
|
||||
@@ -5298,7 +5298,7 @@
|
||||
"advanced"
|
||||
],
|
||||
"label": "Agent ACP Harness Agent",
|
||||
"help": "Optional ACP harness agent id to use for this OpenClaw agent (for example codex, claude).",
|
||||
"help": "Optional ACP harness agent id to use for this OpenClaw agent (for example codex, claude, cursor, gemini, openclaw).",
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
@@ -7124,7 +7124,7 @@
|
||||
"advanced"
|
||||
],
|
||||
"label": "Approvals",
|
||||
"help": "Approval routing controls for forwarding exec approval requests to chat destinations outside the originating session. Keep this disabled unless operators need explicit out-of-band approval visibility.",
|
||||
"help": "Approval routing controls for forwarding exec and plugin approval requests to chat destinations outside the originating session. Keep these disabled unless operators need explicit out-of-band approval visibility.",
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
@@ -7300,6 +7300,179 @@
|
||||
"help": "Destination identifier inside the target channel (channel ID, user ID, or thread root depending on provider). Verify semantics per provider because destination format differs across channel integrations.",
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "approvals.plugin",
|
||||
"kind": "core",
|
||||
"type": "object",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"advanced"
|
||||
],
|
||||
"label": "Plugin Approval Forwarding",
|
||||
"help": "Groups plugin-approval forwarding behavior including enablement, routing mode, filters, and explicit targets. Independent of exec approval forwarding. Configure here when plugin approval prompts must reach operational channels.",
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
"path": "approvals.plugin.agentFilter",
|
||||
"kind": "core",
|
||||
"type": "array",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"advanced"
|
||||
],
|
||||
"label": "Plugin Approval Agent Filter",
|
||||
"help": "Optional allowlist of agent IDs eligible for forwarded plugin approvals, for example `[\"primary\", \"ops-agent\"]`. Use this to limit forwarding blast radius.",
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
"path": "approvals.plugin.agentFilter.*",
|
||||
"kind": "core",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "approvals.plugin.enabled",
|
||||
"kind": "core",
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"advanced"
|
||||
],
|
||||
"label": "Forward Plugin Approvals",
|
||||
"help": "Enables forwarding of plugin approval requests to configured delivery destinations (default: false). Independent of approvals.exec.enabled.",
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "approvals.plugin.mode",
|
||||
"kind": "core",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"advanced"
|
||||
],
|
||||
"label": "Plugin Approval Forwarding Mode",
|
||||
"help": "Controls where plugin approval prompts are sent: \"session\" uses origin chat, \"targets\" uses configured targets, and \"both\" sends to both paths.",
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "approvals.plugin.sessionFilter",
|
||||
"kind": "core",
|
||||
"type": "array",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"storage"
|
||||
],
|
||||
"label": "Plugin Approval Session Filter",
|
||||
"help": "Optional session-key filters matched as substring or regex-style patterns, for example `[\"discord:\", \"^agent:ops:\"]`. Use narrow patterns so only intended approval contexts are forwarded.",
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
"path": "approvals.plugin.sessionFilter.*",
|
||||
"kind": "core",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "approvals.plugin.targets",
|
||||
"kind": "core",
|
||||
"type": "array",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"advanced"
|
||||
],
|
||||
"label": "Plugin Approval Forwarding Targets",
|
||||
"help": "Explicit delivery targets used when plugin approval forwarding mode includes targets, each with channel and destination details.",
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
"path": "approvals.plugin.targets.*",
|
||||
"kind": "core",
|
||||
"type": "object",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
"path": "approvals.plugin.targets.*.accountId",
|
||||
"kind": "core",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"advanced"
|
||||
],
|
||||
"label": "Plugin Approval Target Account ID",
|
||||
"help": "Optional account selector for multi-account channel setups when plugin approvals must route through a specific account context.",
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "approvals.plugin.targets.*.channel",
|
||||
"kind": "core",
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"advanced"
|
||||
],
|
||||
"label": "Plugin Approval Target Channel",
|
||||
"help": "Channel/provider ID used for forwarded plugin approval delivery, such as discord, slack, or a plugin channel id.",
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "approvals.plugin.targets.*.threadId",
|
||||
"kind": "core",
|
||||
"type": [
|
||||
"number",
|
||||
"string"
|
||||
],
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"advanced"
|
||||
],
|
||||
"label": "Plugin Approval Target Thread ID",
|
||||
"help": "Optional thread/topic target for channels that support threaded delivery of forwarded plugin approvals.",
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "approvals.plugin.targets.*.to",
|
||||
"kind": "core",
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"advanced"
|
||||
],
|
||||
"label": "Plugin Approval Target Destination",
|
||||
"help": "Destination identifier inside the target channel (channel ID, user ID, or thread root depending on provider).",
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "audio",
|
||||
"kind": "core",
|
||||
@@ -8401,7 +8574,6 @@
|
||||
{
|
||||
"path": "channels",
|
||||
"kind": "core",
|
||||
"type": "object",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
@@ -14607,8 +14779,13 @@
|
||||
],
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"sensitive": true,
|
||||
"tags": [
|
||||
"auth",
|
||||
"channels",
|
||||
"network",
|
||||
"security"
|
||||
],
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
@@ -14837,8 +15014,12 @@
|
||||
],
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"sensitive": true,
|
||||
"tags": [
|
||||
"channels",
|
||||
"network",
|
||||
"security"
|
||||
],
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
@@ -15436,8 +15617,13 @@
|
||||
],
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"sensitive": true,
|
||||
"tags": [
|
||||
"auth",
|
||||
"channels",
|
||||
"network",
|
||||
"security"
|
||||
],
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
@@ -15562,8 +15748,13 @@
|
||||
],
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"sensitive": true,
|
||||
"tags": [
|
||||
"auth",
|
||||
"channels",
|
||||
"network",
|
||||
"security"
|
||||
],
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
@@ -15854,8 +16045,12 @@
|
||||
],
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"sensitive": true,
|
||||
"tags": [
|
||||
"channels",
|
||||
"network",
|
||||
"security"
|
||||
],
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
@@ -16446,8 +16641,13 @@
|
||||
],
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"sensitive": true,
|
||||
"tags": [
|
||||
"auth",
|
||||
"channels",
|
||||
"network",
|
||||
"security"
|
||||
],
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
@@ -20843,8 +21043,14 @@
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"sensitive": true,
|
||||
"tags": [
|
||||
"access",
|
||||
"auth",
|
||||
"channels",
|
||||
"network",
|
||||
"security"
|
||||
],
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
@@ -20853,8 +21059,13 @@
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"sensitive": true,
|
||||
"tags": [
|
||||
"auth",
|
||||
"channels",
|
||||
"network",
|
||||
"security"
|
||||
],
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
@@ -21052,8 +21263,14 @@
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"sensitive": true,
|
||||
"tags": [
|
||||
"auth",
|
||||
"channels",
|
||||
"network",
|
||||
"security",
|
||||
"storage"
|
||||
],
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
@@ -21105,8 +21322,14 @@
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"sensitive": true,
|
||||
"tags": [
|
||||
"access",
|
||||
"auth",
|
||||
"channels",
|
||||
"network",
|
||||
"security"
|
||||
],
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
@@ -21115,8 +21338,13 @@
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"sensitive": true,
|
||||
"tags": [
|
||||
"auth",
|
||||
"channels",
|
||||
"network",
|
||||
"security"
|
||||
],
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
@@ -21324,8 +21552,14 @@
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"sensitive": true,
|
||||
"tags": [
|
||||
"auth",
|
||||
"channels",
|
||||
"network",
|
||||
"security",
|
||||
"storage"
|
||||
],
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
@@ -21369,8 +21603,14 @@
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"sensitive": true,
|
||||
"tags": [
|
||||
"access",
|
||||
"auth",
|
||||
"channels",
|
||||
"network",
|
||||
"security"
|
||||
],
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
@@ -21510,13 +21750,7 @@
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"access",
|
||||
"channels",
|
||||
"network"
|
||||
],
|
||||
"label": "Matrix Allow Bot Messages",
|
||||
"help": "Allow messages from other configured Matrix bot accounts to trigger replies (default: false). Set \"mentions\" to only accept bot messages that visibly mention this bot.",
|
||||
"tags": [],
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
@@ -22018,8 +22252,13 @@
|
||||
],
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"sensitive": true,
|
||||
"tags": [
|
||||
"auth",
|
||||
"channels",
|
||||
"network",
|
||||
"security"
|
||||
],
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
@@ -22563,8 +22802,13 @@
|
||||
],
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"sensitive": true,
|
||||
"tags": [
|
||||
"auth",
|
||||
"channels",
|
||||
"network",
|
||||
"security"
|
||||
],
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
@@ -23038,12 +23282,7 @@
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"channels",
|
||||
"network"
|
||||
],
|
||||
"label": "Mattermost Base URL",
|
||||
"help": "Base URL for your Mattermost server (e.g., https://chat.example.com).",
|
||||
"tags": [],
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
@@ -23112,8 +23351,6 @@
|
||||
"network",
|
||||
"security"
|
||||
],
|
||||
"label": "Mattermost Bot Token",
|
||||
"help": "Bot token from Mattermost System Console -> Integrations -> Bot Accounts.",
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
@@ -23178,12 +23415,7 @@
|
||||
],
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"channels",
|
||||
"network"
|
||||
],
|
||||
"label": "Mattermost Chat Mode",
|
||||
"help": "Reply to channel messages on mention (\"oncall\"), on trigger chars (\">\" or \"!\") (\"onchar\"), or on every message (\"onmessage\").",
|
||||
"tags": [],
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
@@ -23263,12 +23495,7 @@
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"channels",
|
||||
"network"
|
||||
],
|
||||
"label": "Mattermost Config Writes",
|
||||
"help": "Allow Mattermost to write config in response to channel events/commands (default: true).",
|
||||
"tags": [],
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
@@ -23489,12 +23716,7 @@
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"channels",
|
||||
"network"
|
||||
],
|
||||
"label": "Mattermost Onchar Prefixes",
|
||||
"help": "Trigger prefixes for onchar mode (default: [\">\", \"!\"]).",
|
||||
"tags": [],
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
@@ -23529,12 +23751,7 @@
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"channels",
|
||||
"network"
|
||||
],
|
||||
"label": "Mattermost Require Mention",
|
||||
"help": "Require @mention in channels before responding (default: true).",
|
||||
"tags": [],
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
@@ -24633,8 +24850,13 @@
|
||||
],
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"sensitive": true,
|
||||
"tags": [
|
||||
"auth",
|
||||
"channels",
|
||||
"network",
|
||||
"security"
|
||||
],
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
@@ -24756,8 +24978,13 @@
|
||||
],
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"sensitive": true,
|
||||
"tags": [
|
||||
"auth",
|
||||
"channels",
|
||||
"network",
|
||||
"security"
|
||||
],
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
@@ -24796,8 +25023,14 @@
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"sensitive": true,
|
||||
"tags": [
|
||||
"auth",
|
||||
"channels",
|
||||
"network",
|
||||
"security",
|
||||
"storage"
|
||||
],
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
@@ -25231,8 +25464,13 @@
|
||||
],
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"sensitive": true,
|
||||
"tags": [
|
||||
"auth",
|
||||
"channels",
|
||||
"network",
|
||||
"security"
|
||||
],
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
@@ -25354,8 +25592,13 @@
|
||||
],
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"sensitive": true,
|
||||
"tags": [
|
||||
"auth",
|
||||
"channels",
|
||||
"network",
|
||||
"security"
|
||||
],
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
@@ -25394,8 +25637,14 @@
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"sensitive": true,
|
||||
"tags": [
|
||||
"auth",
|
||||
"channels",
|
||||
"network",
|
||||
"security",
|
||||
"storage"
|
||||
],
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
@@ -36880,8 +37129,13 @@
|
||||
],
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"sensitive": true,
|
||||
"tags": [
|
||||
"auth",
|
||||
"channels",
|
||||
"network",
|
||||
"security"
|
||||
],
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
@@ -37072,8 +37326,13 @@
|
||||
],
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"sensitive": true,
|
||||
"tags": [
|
||||
"auth",
|
||||
"channels",
|
||||
"network",
|
||||
"security"
|
||||
],
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
@@ -37148,8 +37407,13 @@
|
||||
],
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"sensitive": true,
|
||||
"tags": [
|
||||
"auth",
|
||||
"channels",
|
||||
"network",
|
||||
"security"
|
||||
],
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
@@ -37350,8 +37614,13 @@
|
||||
],
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"sensitive": true,
|
||||
"tags": [
|
||||
"auth",
|
||||
"channels",
|
||||
"network",
|
||||
"security"
|
||||
],
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
@@ -49548,6 +49817,127 @@
|
||||
"help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.",
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "plugins.entries.litellm",
|
||||
"kind": "plugin",
|
||||
"type": "object",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"advanced"
|
||||
],
|
||||
"label": "@openclaw/litellm-provider",
|
||||
"help": "OpenClaw LiteLLM provider plugin (plugin: litellm)",
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
"path": "plugins.entries.litellm.config",
|
||||
"kind": "plugin",
|
||||
"type": "object",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"advanced"
|
||||
],
|
||||
"label": "@openclaw/litellm-provider Config",
|
||||
"help": "Plugin-defined config payload for litellm.",
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "plugins.entries.litellm.enabled",
|
||||
"kind": "plugin",
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"advanced"
|
||||
],
|
||||
"label": "Enable @openclaw/litellm-provider",
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "plugins.entries.litellm.hooks",
|
||||
"kind": "plugin",
|
||||
"type": "object",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"advanced"
|
||||
],
|
||||
"label": "Plugin Hook Policy",
|
||||
"help": "Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.",
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
"path": "plugins.entries.litellm.hooks.allowPromptInjection",
|
||||
"kind": "plugin",
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"access"
|
||||
],
|
||||
"label": "Allow Prompt Injection Hooks",
|
||||
"help": "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.",
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "plugins.entries.litellm.subagent",
|
||||
"kind": "plugin",
|
||||
"type": "object",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"advanced"
|
||||
],
|
||||
"label": "Plugin Subagent Policy",
|
||||
"help": "Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.",
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
"path": "plugins.entries.litellm.subagent.allowedModels",
|
||||
"kind": "plugin",
|
||||
"type": "array",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"access"
|
||||
],
|
||||
"label": "Plugin Subagent Allowed Models",
|
||||
"help": "Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.",
|
||||
"hasChildren": true
|
||||
},
|
||||
{
|
||||
"path": "plugins.entries.litellm.subagent.allowedModels.*",
|
||||
"kind": "plugin",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "plugins.entries.litellm.subagent.allowModelOverride",
|
||||
"kind": "plugin",
|
||||
"type": "boolean",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"access"
|
||||
],
|
||||
"label": "Allow Plugin Subagent Model Override",
|
||||
"help": "Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.",
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "plugins.entries.llm-task",
|
||||
"kind": "plugin",
|
||||
@@ -52577,6 +52967,24 @@
|
||||
"help": "Request GPU resources when creating the sandbox.",
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "plugins.entries.openshell.config.mode",
|
||||
"kind": "plugin",
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"enumValues": [
|
||||
"mirror",
|
||||
"remote"
|
||||
],
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [
|
||||
"advanced"
|
||||
],
|
||||
"label": "Mode",
|
||||
"help": "Sandbox mode. Use mirror for the default local-workspace flow or remote for a fully remote workspace.",
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
"path": "plugins.entries.openshell.config.policy",
|
||||
"kind": "plugin",
|
||||
@@ -55259,8 +55667,11 @@
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"deprecated": false,
|
||||
"sensitive": false,
|
||||
"tags": [],
|
||||
"sensitive": true,
|
||||
"tags": [
|
||||
"auth",
|
||||
"security"
|
||||
],
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
@@ -59969,7 +60380,7 @@
|
||||
"tools"
|
||||
],
|
||||
"label": "Enable apply_patch",
|
||||
"help": "Experimental. Enables apply_patch for OpenAI models when allowed by tool policy.",
|
||||
"help": "Enable or disable apply_patch for OpenAI and OpenAI Codex models when allowed by tool policy (default: true).",
|
||||
"hasChildren": false
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{"generatedBy":"scripts/generate-config-doc-baseline.ts","recordType":"meta","totalPaths":5531}
|
||||
{"generatedBy":"scripts/generate-config-doc-baseline.ts","recordType":"meta","totalPaths":5554}
|
||||
{"recordType":"path","path":"acp","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"ACP","help":"ACP runtime controls for enabling dispatch, selecting backends, constraining allowed agent targets, and tuning streamed turn projection behavior.","hasChildren":true}
|
||||
{"recordType":"path","path":"acp.allowedAgents","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"ACP Allowed Agents","help":"Allowlist of ACP target agent ids permitted for ACP runtime sessions. Empty means no additional allowlist restriction.","hasChildren":true}
|
||||
{"recordType":"path","path":"acp.allowedAgents.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
@@ -467,7 +467,7 @@
|
||||
{"recordType":"path","path":"agents.list.*.reasoningDefault","kind":"core","type":"string","required":false,"enumValues":["on","off","stream"],"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Agent Reasoning Default","help":"Optional per-agent default reasoning visibility (on|off|stream). Applies when no per-message or session reasoning override is set.","hasChildren":false}
|
||||
{"recordType":"path","path":"agents.list.*.runtime","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Agent Runtime","help":"Optional runtime descriptor for this agent. Use embedded for default OpenClaw execution or acp for external ACP harness defaults.","hasChildren":true}
|
||||
{"recordType":"path","path":"agents.list.*.runtime.acp","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Agent ACP Runtime","help":"ACP runtime defaults for this agent when runtime.type=acp. Binding-level ACP overrides still take precedence per conversation.","hasChildren":true}
|
||||
{"recordType":"path","path":"agents.list.*.runtime.acp.agent","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Agent ACP Harness Agent","help":"Optional ACP harness agent id to use for this OpenClaw agent (for example codex, claude).","hasChildren":false}
|
||||
{"recordType":"path","path":"agents.list.*.runtime.acp.agent","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Agent ACP Harness Agent","help":"Optional ACP harness agent id to use for this OpenClaw agent (for example codex, claude, cursor, gemini, openclaw).","hasChildren":false}
|
||||
{"recordType":"path","path":"agents.list.*.runtime.acp.backend","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Agent ACP Backend","help":"Optional ACP backend override for this agent's ACP sessions (falls back to global acp.backend).","hasChildren":false}
|
||||
{"recordType":"path","path":"agents.list.*.runtime.acp.cwd","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Agent ACP Working Directory","help":"Optional default working directory for this agent's ACP sessions.","hasChildren":false}
|
||||
{"recordType":"path","path":"agents.list.*.runtime.acp.mode","kind":"core","type":"string","required":false,"enumValues":["persistent","oneshot"],"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Agent ACP Mode","help":"Optional ACP session mode default for this agent (persistent or oneshot).","hasChildren":false}
|
||||
@@ -638,7 +638,7 @@
|
||||
{"recordType":"path","path":"agents.list.*.tools.sandbox.tools.deny","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"agents.list.*.tools.sandbox.tools.deny.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"agents.list.*.workspace","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"approvals","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Approvals","help":"Approval routing controls for forwarding exec approval requests to chat destinations outside the originating session. Keep this disabled unless operators need explicit out-of-band approval visibility.","hasChildren":true}
|
||||
{"recordType":"path","path":"approvals","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Approvals","help":"Approval routing controls for forwarding exec and plugin approval requests to chat destinations outside the originating session. Keep these disabled unless operators need explicit out-of-band approval visibility.","hasChildren":true}
|
||||
{"recordType":"path","path":"approvals.exec","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Exec Approval Forwarding","help":"Groups exec-approval forwarding behavior including enablement, routing mode, filters, and explicit targets. Configure here when approval prompts must reach operational channels instead of only the origin thread.","hasChildren":true}
|
||||
{"recordType":"path","path":"approvals.exec.agentFilter","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Approval Agent Filter","help":"Optional allowlist of agent IDs eligible for forwarded approvals, for example `[\"primary\", \"ops-agent\"]`. Use this to limit forwarding blast radius and avoid notifying channels for unrelated agents.","hasChildren":true}
|
||||
{"recordType":"path","path":"approvals.exec.agentFilter.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
@@ -652,6 +652,19 @@
|
||||
{"recordType":"path","path":"approvals.exec.targets.*.channel","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Approval Target Channel","help":"Channel/provider ID used for forwarded approval delivery, such as discord, slack, or a plugin channel id. Use valid channel IDs only so approvals do not silently fail due to unknown routes.","hasChildren":false}
|
||||
{"recordType":"path","path":"approvals.exec.targets.*.threadId","kind":"core","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Approval Target Thread ID","help":"Optional thread/topic target for channels that support threaded delivery of forwarded approvals. Use this to keep approval traffic contained in operational threads instead of main channels.","hasChildren":false}
|
||||
{"recordType":"path","path":"approvals.exec.targets.*.to","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Approval Target Destination","help":"Destination identifier inside the target channel (channel ID, user ID, or thread root depending on provider). Verify semantics per provider because destination format differs across channel integrations.","hasChildren":false}
|
||||
{"recordType":"path","path":"approvals.plugin","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Approval Forwarding","help":"Groups plugin-approval forwarding behavior including enablement, routing mode, filters, and explicit targets. Independent of exec approval forwarding. Configure here when plugin approval prompts must reach operational channels.","hasChildren":true}
|
||||
{"recordType":"path","path":"approvals.plugin.agentFilter","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Approval Agent Filter","help":"Optional allowlist of agent IDs eligible for forwarded plugin approvals, for example `[\"primary\", \"ops-agent\"]`. Use this to limit forwarding blast radius.","hasChildren":true}
|
||||
{"recordType":"path","path":"approvals.plugin.agentFilter.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"approvals.plugin.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Forward Plugin Approvals","help":"Enables forwarding of plugin approval requests to configured delivery destinations (default: false). Independent of approvals.exec.enabled.","hasChildren":false}
|
||||
{"recordType":"path","path":"approvals.plugin.mode","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Approval Forwarding Mode","help":"Controls where plugin approval prompts are sent: \"session\" uses origin chat, \"targets\" uses configured targets, and \"both\" sends to both paths.","hasChildren":false}
|
||||
{"recordType":"path","path":"approvals.plugin.sessionFilter","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Plugin Approval Session Filter","help":"Optional session-key filters matched as substring or regex-style patterns, for example `[\"discord:\", \"^agent:ops:\"]`. Use narrow patterns so only intended approval contexts are forwarded.","hasChildren":true}
|
||||
{"recordType":"path","path":"approvals.plugin.sessionFilter.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"approvals.plugin.targets","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Approval Forwarding Targets","help":"Explicit delivery targets used when plugin approval forwarding mode includes targets, each with channel and destination details.","hasChildren":true}
|
||||
{"recordType":"path","path":"approvals.plugin.targets.*","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"approvals.plugin.targets.*.accountId","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Approval Target Account ID","help":"Optional account selector for multi-account channel setups when plugin approvals must route through a specific account context.","hasChildren":false}
|
||||
{"recordType":"path","path":"approvals.plugin.targets.*.channel","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Approval Target Channel","help":"Channel/provider ID used for forwarded plugin approval delivery, such as discord, slack, or a plugin channel id.","hasChildren":false}
|
||||
{"recordType":"path","path":"approvals.plugin.targets.*.threadId","kind":"core","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Approval Target Thread ID","help":"Optional thread/topic target for channels that support threaded delivery of forwarded plugin approvals.","hasChildren":false}
|
||||
{"recordType":"path","path":"approvals.plugin.targets.*.to","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Approval Target Destination","help":"Destination identifier inside the target channel (channel ID, user ID, or thread root depending on provider).","hasChildren":false}
|
||||
{"recordType":"path","path":"audio","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Audio","help":"Global audio ingestion settings used before higher-level tools process speech or media content. Configure this when you need deterministic transcription behavior for voice notes and clips.","hasChildren":true}
|
||||
{"recordType":"path","path":"audio.transcription","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["media"],"label":"Audio Transcription","help":"Command-based transcription settings for converting audio files into text before agent handling. Keep a simple, deterministic command path here so failures are easy to diagnose in logs.","hasChildren":true}
|
||||
{"recordType":"path","path":"audio.transcription.command","kind":"core","type":"array","required":true,"deprecated":false,"sensitive":false,"tags":["media"],"label":"Audio Transcription Command","help":"Executable + args used to transcribe audio (first token must be a safe binary/path), for example `[\"whisper-cli\", \"--model\", \"small\", \"{input}\"]`. Prefer a pinned command so runtime environments behave consistently.","hasChildren":true}
|
||||
@@ -734,7 +747,7 @@
|
||||
{"recordType":"path","path":"canvasHost.liveReload","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["reliability"],"label":"Canvas Host Live Reload","help":"Enables automatic live-reload behavior for canvas assets during development workflows. Keep disabled in production-like environments where deterministic output is preferred.","hasChildren":false}
|
||||
{"recordType":"path","path":"canvasHost.port","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Canvas Host Port","help":"TCP port used by the canvas host HTTP server when canvas hosting is enabled. Choose a non-conflicting port and align firewall/proxy policy accordingly.","hasChildren":false}
|
||||
{"recordType":"path","path":"canvasHost.root","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Canvas Host Root Directory","help":"Filesystem root directory served by canvas host for canvas content and static assets. Use a dedicated directory and avoid broad repo roots for least-privilege file exposure.","hasChildren":false}
|
||||
{"recordType":"path","path":"channels","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Channels","help":"Channel provider configurations plus shared defaults that control access policies, heartbeat visibility, and per-surface behavior. Keep defaults centralized and override per provider only where required.","hasChildren":true}
|
||||
{"recordType":"path","path":"channels","kind":"core","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Channels","help":"Channel provider configurations plus shared defaults that control access policies, heartbeat visibility, and per-surface behavior. Keep defaults centralized and override per provider only where required.","hasChildren":true}
|
||||
{"recordType":"path","path":"channels.bluebubbles","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"BlueBubbles","help":"iMessage via the BlueBubbles mac app + REST API.","hasChildren":true}
|
||||
{"recordType":"path","path":"channels.bluebubbles.accounts","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.bluebubbles.accounts.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
@@ -1287,7 +1300,7 @@
|
||||
{"recordType":"path","path":"channels.feishu.accounts.*.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.feishu.accounts.*.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.feishu.accounts.*.appId","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.feishu.accounts.*.appSecret","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.feishu.accounts.*.appSecret","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","network","security"],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.feishu.accounts.*.appSecret.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.feishu.accounts.*.appSecret.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.feishu.accounts.*.appSecret.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
@@ -1308,7 +1321,7 @@
|
||||
{"recordType":"path","path":"channels.feishu.accounts.*.dms.*.systemPrompt","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.feishu.accounts.*.domain","kind":"channel","type":"string","required":false,"enumValues":["feishu","lark"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.feishu.accounts.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.feishu.accounts.*.encryptKey","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.feishu.accounts.*.encryptKey","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["channels","network","security"],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.feishu.accounts.*.encryptKey.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.feishu.accounts.*.encryptKey.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.feishu.accounts.*.encryptKey.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
@@ -1361,7 +1374,7 @@
|
||||
{"recordType":"path","path":"channels.feishu.accounts.*.tools.wiki","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.feishu.accounts.*.topicSessionMode","kind":"channel","type":"string","required":false,"enumValues":["disabled","enabled"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.feishu.accounts.*.typingIndicator","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.feishu.accounts.*.verificationToken","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.feishu.accounts.*.verificationToken","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","network","security"],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.feishu.accounts.*.verificationToken.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.feishu.accounts.*.verificationToken.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.feishu.accounts.*.verificationToken.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
@@ -1373,7 +1386,7 @@
|
||||
{"recordType":"path","path":"channels.feishu.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.feishu.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.feishu.appId","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.feishu.appSecret","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.feishu.appSecret","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","network","security"],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.feishu.appSecret.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.feishu.appSecret.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.feishu.appSecret.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
@@ -1400,7 +1413,7 @@
|
||||
{"recordType":"path","path":"channels.feishu.dynamicAgentCreation.maxAgents","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.feishu.dynamicAgentCreation.workspaceTemplate","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.feishu.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.feishu.encryptKey","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.feishu.encryptKey","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["channels","network","security"],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.feishu.encryptKey.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.feishu.encryptKey.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.feishu.encryptKey.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
@@ -1452,7 +1465,7 @@
|
||||
{"recordType":"path","path":"channels.feishu.tools.wiki","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.feishu.topicSessionMode","kind":"channel","type":"string","required":false,"enumValues":["disabled","enabled"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.feishu.typingIndicator","kind":"channel","type":"boolean","required":true,"defaultValue":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.feishu.verificationToken","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.feishu.verificationToken","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","network","security"],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.feishu.verificationToken.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.feishu.verificationToken.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.feishu.verificationToken.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
@@ -1860,8 +1873,8 @@
|
||||
{"recordType":"path","path":"channels.line.accounts.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.line.accounts.*.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.line.accounts.*.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.line.accounts.*.channelAccessToken","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.line.accounts.*.channelSecret","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.line.accounts.*.channelAccessToken","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":true,"tags":["access","auth","channels","network","security"],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.line.accounts.*.channelSecret","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","network","security"],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.line.accounts.*.dmPolicy","kind":"channel","type":"string","required":true,"enumValues":["open","allowlist","pairing","disabled"],"defaultValue":"pairing","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.line.accounts.*.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.line.accounts.*.groupAllowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
@@ -1879,13 +1892,13 @@
|
||||
{"recordType":"path","path":"channels.line.accounts.*.mediaMaxMb","kind":"channel","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.line.accounts.*.name","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.line.accounts.*.responsePrefix","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.line.accounts.*.secretFile","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.line.accounts.*.secretFile","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","network","security","storage"],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.line.accounts.*.tokenFile","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.line.accounts.*.webhookPath","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.line.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.line.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.line.channelAccessToken","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.line.channelSecret","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.line.channelAccessToken","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":true,"tags":["access","auth","channels","network","security"],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.line.channelSecret","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","network","security"],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.line.defaultAccount","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.line.dmPolicy","kind":"channel","type":"string","required":true,"enumValues":["open","allowlist","pairing","disabled"],"defaultValue":"pairing","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.line.enabled","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
@@ -1904,11 +1917,11 @@
|
||||
{"recordType":"path","path":"channels.line.mediaMaxMb","kind":"channel","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.line.name","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.line.responsePrefix","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.line.secretFile","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.line.secretFile","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","network","security","storage"],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.line.tokenFile","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.line.webhookPath","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.matrix","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Matrix","help":"open protocol; install the plugin to enable.","hasChildren":true}
|
||||
{"recordType":"path","path":"channels.matrix.accessToken","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.matrix.accessToken","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":true,"tags":["access","auth","channels","network","security"],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.matrix.accounts","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.matrix.accounts.*","kind":"channel","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.matrix.ackReaction","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
@@ -1921,7 +1934,7 @@
|
||||
{"recordType":"path","path":"channels.matrix.actions.profile","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.matrix.actions.reactions","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.matrix.actions.verification","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.matrix.allowBots","kind":"channel","type":["boolean","string"],"required":false,"deprecated":false,"sensitive":false,"tags":["access","channels","network"],"label":"Matrix Allow Bot Messages","help":"Allow messages from other configured Matrix bot accounts to trigger replies (default: false). Set \"mentions\" to only accept bot messages that visibly mention this bot.","hasChildren":false}
|
||||
{"recordType":"path","path":"channels.matrix.allowBots","kind":"channel","type":["boolean","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.matrix.allowlistOnly","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.matrix.allowPrivateNetwork","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.matrix.autoJoin","kind":"channel","type":"string","required":false,"enumValues":["always","allowlist","off"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
@@ -1967,7 +1980,7 @@
|
||||
{"recordType":"path","path":"channels.matrix.markdown.tables","kind":"channel","type":"string","required":false,"enumValues":["off","bullets","code"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.matrix.mediaMaxMb","kind":"channel","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.matrix.name","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.matrix.password","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.matrix.password","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","network","security"],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.matrix.password.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.matrix.password.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.matrix.password.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
@@ -2018,7 +2031,7 @@
|
||||
{"recordType":"path","path":"channels.mattermost.accounts.*.blockStreamingCoalesce.idleMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.mattermost.accounts.*.blockStreamingCoalesce.maxChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.mattermost.accounts.*.blockStreamingCoalesce.minChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.mattermost.accounts.*.botToken","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.mattermost.accounts.*.botToken","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","network","security"],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.mattermost.accounts.*.botToken.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.mattermost.accounts.*.botToken.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.mattermost.accounts.*.botToken.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
@@ -2061,26 +2074,26 @@
|
||||
{"recordType":"path","path":"channels.mattermost.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.mattermost.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.mattermost.allowPrivateNetwork","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.mattermost.baseUrl","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Mattermost Base URL","help":"Base URL for your Mattermost server (e.g., https://chat.example.com).","hasChildren":false}
|
||||
{"recordType":"path","path":"channels.mattermost.baseUrl","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.mattermost.blockStreaming","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.mattermost.blockStreamingCoalesce","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.mattermost.blockStreamingCoalesce.idleMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.mattermost.blockStreamingCoalesce.maxChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.mattermost.blockStreamingCoalesce.minChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.mattermost.botToken","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","network","security"],"label":"Mattermost Bot Token","help":"Bot token from Mattermost System Console -> Integrations -> Bot Accounts.","hasChildren":true}
|
||||
{"recordType":"path","path":"channels.mattermost.botToken","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","network","security"],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.mattermost.botToken.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.mattermost.botToken.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.mattermost.botToken.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.mattermost.capabilities","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.mattermost.capabilities.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.mattermost.chatmode","kind":"channel","type":"string","required":false,"enumValues":["oncall","onmessage","onchar"],"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Mattermost Chat Mode","help":"Reply to channel messages on mention (\"oncall\"), on trigger chars (\">\" or \"!\") (\"onchar\"), or on every message (\"onmessage\").","hasChildren":false}
|
||||
{"recordType":"path","path":"channels.mattermost.chatmode","kind":"channel","type":"string","required":false,"enumValues":["oncall","onmessage","onchar"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.mattermost.chunkMode","kind":"channel","type":"string","required":false,"enumValues":["length","newline"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.mattermost.commands","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.mattermost.commands.callbackPath","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.mattermost.commands.callbackUrl","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.mattermost.commands.native","kind":"channel","type":["boolean","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.mattermost.commands.nativeSkills","kind":"channel","type":["boolean","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.mattermost.configWrites","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Mattermost Config Writes","help":"Allow Mattermost to write config in response to channel events/commands (default: true).","hasChildren":false}
|
||||
{"recordType":"path","path":"channels.mattermost.configWrites","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.mattermost.dangerouslyAllowNameMatching","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.mattermost.defaultAccount","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.mattermost.dmChannelRetry","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
@@ -2100,10 +2113,10 @@
|
||||
{"recordType":"path","path":"channels.mattermost.markdown","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.mattermost.markdown.tables","kind":"channel","type":"string","required":false,"enumValues":["off","bullets","code"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.mattermost.name","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.mattermost.oncharPrefixes","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Mattermost Onchar Prefixes","help":"Trigger prefixes for onchar mode (default: [\">\", \"!\"]).","hasChildren":true}
|
||||
{"recordType":"path","path":"channels.mattermost.oncharPrefixes","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.mattermost.oncharPrefixes.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.mattermost.replyToMode","kind":"channel","type":"string","required":false,"enumValues":["off","first","all"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.mattermost.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Mattermost Require Mention","help":"Require @mention in channels before responding (default: true).","hasChildren":false}
|
||||
{"recordType":"path","path":"channels.mattermost.requireMention","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.mattermost.responsePrefix","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.mattermost.textChunkLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.msteams","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["channels","network"],"label":"Microsoft Teams","help":"Teams SDK; enterprise support.","hasChildren":true}
|
||||
@@ -2207,7 +2220,7 @@
|
||||
{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.allowFrom.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.allowPrivateNetwork","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.apiPassword","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.apiPassword","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","network","security"],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.apiPassword.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.apiPassword.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.apiPassword.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
@@ -2219,11 +2232,11 @@
|
||||
{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.blockStreamingCoalesce.idleMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.blockStreamingCoalesce.maxChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.blockStreamingCoalesce.minChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.botSecret","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.botSecret","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","network","security"],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.botSecret.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.botSecret.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.botSecret.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.botSecretFile","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.botSecretFile","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","network","security","storage"],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.chunkMode","kind":"channel","type":"string","required":false,"enumValues":["length","newline"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.dmHistoryLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.nextcloud-talk.accounts.*.dmPolicy","kind":"channel","type":"string","required":true,"enumValues":["pairing","allowlist","open","disabled"],"defaultValue":"pairing","deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
@@ -2264,7 +2277,7 @@
|
||||
{"recordType":"path","path":"channels.nextcloud-talk.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.nextcloud-talk.allowFrom.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.nextcloud-talk.allowPrivateNetwork","kind":"channel","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.nextcloud-talk.apiPassword","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.nextcloud-talk.apiPassword","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","network","security"],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.nextcloud-talk.apiPassword.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.nextcloud-talk.apiPassword.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.nextcloud-talk.apiPassword.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
@@ -2276,11 +2289,11 @@
|
||||
{"recordType":"path","path":"channels.nextcloud-talk.blockStreamingCoalesce.idleMs","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.nextcloud-talk.blockStreamingCoalesce.maxChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.nextcloud-talk.blockStreamingCoalesce.minChars","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.nextcloud-talk.botSecret","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.nextcloud-talk.botSecret","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","network","security"],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.nextcloud-talk.botSecret.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.nextcloud-talk.botSecret.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.nextcloud-talk.botSecret.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.nextcloud-talk.botSecretFile","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.nextcloud-talk.botSecretFile","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","network","security","storage"],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.nextcloud-talk.chunkMode","kind":"channel","type":"string","required":false,"enumValues":["length","newline"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.nextcloud-talk.defaultAccount","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.nextcloud-talk.dmHistoryLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
@@ -3324,7 +3337,7 @@
|
||||
{"recordType":"path","path":"channels.zalo.accounts.*","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.zalo.accounts.*.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.zalo.accounts.*.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.zalo.accounts.*.botToken","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.zalo.accounts.*.botToken","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","network","security"],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.zalo.accounts.*.botToken.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.zalo.accounts.*.botToken.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.zalo.accounts.*.botToken.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
@@ -3341,14 +3354,14 @@
|
||||
{"recordType":"path","path":"channels.zalo.accounts.*.responsePrefix","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.zalo.accounts.*.tokenFile","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.zalo.accounts.*.webhookPath","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.zalo.accounts.*.webhookSecret","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.zalo.accounts.*.webhookSecret","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","network","security"],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.zalo.accounts.*.webhookSecret.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.zalo.accounts.*.webhookSecret.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.zalo.accounts.*.webhookSecret.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.zalo.accounts.*.webhookUrl","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.zalo.allowFrom","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.zalo.allowFrom.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.zalo.botToken","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.zalo.botToken","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","network","security"],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.zalo.botToken.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.zalo.botToken.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.zalo.botToken.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
@@ -3366,7 +3379,7 @@
|
||||
{"recordType":"path","path":"channels.zalo.responsePrefix","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.zalo.tokenFile","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.zalo.webhookPath","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.zalo.webhookSecret","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.zalo.webhookSecret","kind":"channel","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","channels","network","security"],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.zalo.webhookSecret.id","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.zalo.webhookSecret.provider","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.zalo.webhookSecret.source","kind":"channel","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
@@ -4313,6 +4326,15 @@
|
||||
{"recordType":"path","path":"plugins.entries.line.subagent.allowedModels","kind":"plugin","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Plugin Subagent Allowed Models","help":"Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.","hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.line.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.line.subagent.allowModelOverride","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Allow Plugin Subagent Model Override","help":"Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.litellm","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/litellm-provider","help":"OpenClaw LiteLLM provider plugin (plugin: litellm)","hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.litellm.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/litellm-provider Config","help":"Plugin-defined config payload for litellm.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.litellm.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/litellm-provider","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.litellm.hooks","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Hook Policy","help":"Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.","hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.litellm.hooks.allowPromptInjection","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Allow Prompt Injection Hooks","help":"Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.litellm.subagent","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Subagent Policy","help":"Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.","hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.litellm.subagent.allowedModels","kind":"plugin","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Plugin Subagent Allowed Models","help":"Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.","hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.litellm.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.litellm.subagent.allowModelOverride","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Allow Plugin Subagent Model Override","help":"Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.llm-task","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"LLM Task","help":"Generic JSON-only LLM tool for structured tasks callable from workflows. (plugin: llm-task)","hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.llm-task.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"LLM Task Config","help":"Plugin-defined config payload for llm-task.","hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.llm-task.config.allowedModels","kind":"plugin","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
@@ -4539,6 +4561,7 @@
|
||||
{"recordType":"path","path":"plugins.entries.openshell.config.gateway","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Gateway Name","help":"Optional OpenShell gateway name passed as --gateway.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.openshell.config.gatewayEndpoint","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Gateway Endpoint","help":"Optional OpenShell gateway endpoint passed as --gateway-endpoint.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.openshell.config.gpu","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"GPU","help":"Request GPU resources when creating the sandbox.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.openshell.config.mode","kind":"plugin","type":"string","required":false,"enumValues":["mirror","remote"],"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Mode","help":"Sandbox mode. Use mirror for the default local-workspace flow or remote for a fully remote workspace.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.openshell.config.policy","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Policy File","help":"Optional path to a custom OpenShell sandbox policy YAML.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.openshell.config.providers","kind":"plugin","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Providers","help":"Provider names to attach when a sandbox is created.","hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.openshell.config.providers.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
@@ -4739,7 +4762,7 @@
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.outbound.notifyHangupDelaySec","kind":"plugin","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Notify Hangup Delay (sec)","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.plivo","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.plivo.authId","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.plivo.authToken","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.plivo.authToken","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":true,"tags":["auth","security"],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.provider","kind":"plugin","type":"string","required":false,"enumValues":["telnyx","twilio","plivo","mock"],"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Provider","help":"Use twilio, telnyx, or mock for dev/no-network.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.publicUrl","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Public Webhook URL","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.responseModel","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Response Model","hasChildren":false}
|
||||
@@ -5122,7 +5145,7 @@
|
||||
{"recordType":"path","path":"tools.exec.applyPatch","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"tools.exec.applyPatch.allowModels","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["access","tools"],"label":"apply_patch Model Allowlist","help":"Optional allowlist of model ids (e.g. \"gpt-5.2\" or \"openai/gpt-5.2\").","hasChildren":true}
|
||||
{"recordType":"path","path":"tools.exec.applyPatch.allowModels.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.exec.applyPatch.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Enable apply_patch","help":"Experimental. Enables apply_patch for OpenAI models when allowed by tool policy.","hasChildren":false}
|
||||
{"recordType":"path","path":"tools.exec.applyPatch.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Enable apply_patch","help":"Enable or disable apply_patch for OpenAI and OpenAI Codex models when allowed by tool policy (default: true).","hasChildren":false}
|
||||
{"recordType":"path","path":"tools.exec.applyPatch.workspaceOnly","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access","advanced","security","tools"],"label":"apply_patch Workspace-Only","help":"Restrict apply_patch paths to the workspace directory (default: true). Set false to allow writing outside the workspace (dangerous).","hasChildren":false}
|
||||
{"recordType":"path","path":"tools.exec.ask","kind":"core","type":"string","required":false,"enumValues":["off","on-miss","always"],"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Exec Ask","help":"Approval strategy for when exec commands require human confirmation before running. Use stricter ask behavior in shared channels and lower-friction settings in private operator contexts.","hasChildren":false}
|
||||
{"recordType":"path","path":"tools.exec.backgroundMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -459,6 +459,68 @@ These hooks are not event-stream listeners; they let plugins synchronously adjus
|
||||
|
||||
### Plugin Hook Events
|
||||
|
||||
#### before_tool_call
|
||||
|
||||
Runs before each tool call. Plugins can modify parameters, block the call, or request user approval.
|
||||
|
||||
Return fields:
|
||||
|
||||
- **`params`**: Override tool parameters (merged with original params)
|
||||
- **`block`**: Set to `true` to block the tool call
|
||||
- **`blockReason`**: Reason shown to the agent when blocked
|
||||
- **`requireApproval`**: Pause execution and wait for user approval via channels
|
||||
|
||||
The `requireApproval` field triggers native platform approval (Telegram buttons, Discord components, `/approve` command) instead of relying on the agent to cooperate:
|
||||
|
||||
```typescript
|
||||
{
|
||||
requireApproval: {
|
||||
title: "Sensitive operation",
|
||||
description: "This tool call modifies production data",
|
||||
severity: "warning", // "info" | "warning" | "critical"
|
||||
timeoutMs: 120000, // default: 120s
|
||||
timeoutBehavior: "deny", // "allow" | "deny" (default)
|
||||
onResolution: async (decision) => {
|
||||
// Called after the user resolves: "allow-once", "allow-always", "deny", "timeout", or "cancelled"
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The `onResolution` callback is invoked with the final decision string after the approval resolves, times out, or is cancelled. It runs in-process within the plugin (not sent to the gateway). Use it to persist decisions, update caches, or perform cleanup.
|
||||
|
||||
The `pluginId` field is stamped automatically by the hook runner from the plugin registration. When multiple plugins return `requireApproval`, the first one (highest priority) wins.
|
||||
|
||||
`block` takes precedence over `requireApproval`: if the merged hook result has both `block: true` and a `requireApproval` field, the tool call is blocked immediately without triggering the approval flow. This ensures a higher-priority plugin's block cannot be overridden by a lower-priority plugin's approval request.
|
||||
|
||||
If the gateway is unavailable or does not support plugin approvals, the tool call falls back to a soft block using the `description` as the block reason.
|
||||
|
||||
#### before_skill_install
|
||||
|
||||
Runs after the built-in install security scan and before installation continues. OpenClaw fires this hook for interactive skill installs as well as plugin bundle/package installs.
|
||||
|
||||
Return fields:
|
||||
|
||||
- **`findings`**: Additional scan findings to surface as warnings
|
||||
- **`block`**: Set to `true` to block the install
|
||||
- **`blockReason`**: Human-readable reason shown when blocked
|
||||
|
||||
Event fields:
|
||||
|
||||
- **`skillName`**: Human-readable skill name or plugin id for the install target
|
||||
- **`sourceDir`**: Absolute path to the source directory being scanned
|
||||
- **`source`**: Skill origin when available (for example `openclaw-bundled` or `openclaw-workspace`)
|
||||
- **`builtinFindings`**: Findings already produced by the built-in scanner
|
||||
|
||||
Decision semantics:
|
||||
|
||||
- `before_skill_install`: `{ block: true }` is terminal and stops lower-priority handlers.
|
||||
- `before_skill_install`: `{ block: false }` is treated as no decision.
|
||||
|
||||
Use this hook for external security scanners, policy engines, or enterprise approval gates that need to audit install sources before they are installed.
|
||||
|
||||
#### Compaction lifecycle
|
||||
|
||||
Compaction lifecycle hooks exposed through the plugin hook runner:
|
||||
|
||||
- **`before_compaction`**: Runs before compaction with count/token metadata
|
||||
|
||||
@@ -266,8 +266,9 @@ Available actions:
|
||||
- **addParticipant**: Add someone to a group (`chatGuid`, `address`)
|
||||
- **removeParticipant**: Remove someone from a group (`chatGuid`, `address`)
|
||||
- **leaveGroup**: Leave a group chat (`chatGuid`)
|
||||
- **sendAttachment**: Send media/files (`to`, `buffer`, `filename`, `asVoice`)
|
||||
- **upload-file**: Send media/files (`to`, `buffer`, `filename`, `asVoice`)
|
||||
- Voice memos: set `asVoice: true` with **MP3** or **CAF** audio to send as an iMessage voice message. BlueBubbles converts MP3 → CAF when sending voice memos.
|
||||
- Legacy alias: `sendAttachment` still works, but `upload-file` is the canonical action name.
|
||||
|
||||
### Message IDs (short vs full)
|
||||
|
||||
|
||||
@@ -201,6 +201,7 @@ Notes:
|
||||
- Default webhook path is `/googlechat` if `webhookPath` isn’t set.
|
||||
- `dangerouslyAllowNameMatching` re-enables mutable email principal matching for allowlists (break-glass compatibility mode).
|
||||
- Reactions are available via the `reactions` tool and `channels action` when `actions.reactions` is enabled.
|
||||
- Message actions expose `send` for text and `upload-file` for explicit attachment sends. `upload-file` accepts `media` / `filePath` / `path` plus optional `message`, `filename`, and thread targeting.
|
||||
- `typingIndicator` supports `none`, `message` (default), and `reaction` (reaction requires user OAuth).
|
||||
- Attachments are downloaded through the Chat API and stored in the media pipeline (size capped by `mediaMaxMb`).
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ title: "Microsoft Teams"
|
||||
|
||||
Updated: 2026-01-21
|
||||
|
||||
Status: text + DM attachments are supported; channel/group file sending requires `sharePointSiteId` + Graph permissions (see [Sending files in group chats](#sending-files-in-group-chats)). Polls are sent via Adaptive Cards.
|
||||
Status: text + DM attachments are supported; channel/group file sending requires `sharePointSiteId` + Graph permissions (see [Sending files in group chats](#sending-files-in-group-chats)). Polls are sent via Adaptive Cards. Message actions expose explicit `upload-file` for file-first sends.
|
||||
|
||||
## Plugin required
|
||||
|
||||
@@ -527,6 +527,7 @@ Teams recently introduced two channel UI styles over the same underlying data mo
|
||||
|
||||
- **DMs:** Images and file attachments work via Teams bot file APIs.
|
||||
- **Channels/groups:** Attachments live in M365 storage (SharePoint/OneDrive). The webhook payload only includes an HTML stub, not the actual file bytes. **Graph API permissions are required** to download channel attachments.
|
||||
- For explicit file-first sends, use `action=upload-file` with `media` / `filePath` / `path`; optional `message` becomes the accompanying text/comment, and `filename` overrides the uploaded name.
|
||||
|
||||
Without Graph permissions, channel messages with images will be received as text-only (the image content is not accessible to the bot).
|
||||
By default, OpenClaw only downloads media from Microsoft/Teams hostnames. Override with `channels.msteams.mediaAllowHosts` (use `["*"]` to allow any host).
|
||||
|
||||
@@ -87,6 +87,7 @@ These run inside the agent loop or gateway pipeline:
|
||||
- **`agent_end`**: inspect the final message list and run metadata after completion.
|
||||
- **`before_compaction` / `after_compaction`**: observe or annotate compaction cycles.
|
||||
- **`before_tool_call` / `after_tool_call`**: intercept tool params/results.
|
||||
- **`before_skill_install`**: inspect built-in scan findings and optionally block skill or plugin installs.
|
||||
- **`tool_result_persist`**: synchronously transform tool results before they are written to the session transcript.
|
||||
- **`message_received` / `message_sending` / `message_sent`**: inbound + outbound message hooks.
|
||||
- **`session_start` / `session_end`**: session lifecycle boundaries.
|
||||
@@ -96,6 +97,8 @@ Hook decision rules for outbound/tool guards:
|
||||
|
||||
- `before_tool_call`: `{ block: true }` is terminal and stops lower-priority handlers.
|
||||
- `before_tool_call`: `{ block: false }` is a no-op and does not clear a prior block.
|
||||
- `before_skill_install`: `{ block: true }` is terminal and stops lower-priority handlers.
|
||||
- `before_skill_install`: `{ block: false }` is a no-op and does not clear a prior block.
|
||||
- `message_sending`: `{ cancel: true }` is terminal and stops lower-priority handlers.
|
||||
- `message_sending`: `{ cancel: false }` is a no-op and does not clear a prior cancel.
|
||||
|
||||
@@ -145,7 +148,7 @@ See [Plugin hooks](/plugins/architecture#provider-runtime-hooks) for the hook AP
|
||||
## Timeouts
|
||||
|
||||
- `agent.wait` default: 30s (just the wait). `timeoutMs` param overrides.
|
||||
- Agent runtime: `agents.defaults.timeoutSeconds` default 600s; enforced in `runEmbeddedPiAgent` abort timer.
|
||||
- Agent runtime: `agents.defaults.timeoutSeconds` default 172800s (48 hours); enforced in `runEmbeddedPiAgent` abort timer.
|
||||
|
||||
## Where things can end early
|
||||
|
||||
|
||||
@@ -100,9 +100,10 @@ semantic queries can find related notes even when wording differs. Hybrid search
|
||||
(BM25 + vector) is available for combining semantic matching with exact keyword
|
||||
lookups.
|
||||
|
||||
Memory search supports multiple embedding providers (OpenAI, Gemini, Voyage,
|
||||
Mistral, Ollama, and local GGUF models), an optional QMD sidecar backend for
|
||||
advanced retrieval, and post-processing features like MMR diversity re-ranking
|
||||
Memory search adapter ids come from the active memory plugin. The default
|
||||
`memory-core` plugin ships built-ins for OpenAI, Gemini, Voyage, Mistral,
|
||||
Ollama, and local GGUF models, plus an optional QMD sidecar backend for
|
||||
advanced retrieval and post-processing features like MMR diversity re-ranking
|
||||
and temporal decay.
|
||||
|
||||
For the full configuration reference -- including embedding provider setup, QMD
|
||||
|
||||
@@ -402,7 +402,7 @@ Docker installs and the containerized gateway live here:
|
||||
For Docker gateway deployments, `scripts/docker/setup.sh` can bootstrap sandbox config.
|
||||
Set `OPENCLAW_SANDBOX=1` (or `true`/`yes`/`on`) to enable that path. You can
|
||||
override socket location with `OPENCLAW_DOCKER_SOCKET`. Full setup and env
|
||||
reference: [Docker](/install/docker#enable-agent-sandbox-for-docker-gateway).
|
||||
reference: [Docker](/install/docker#agent-sandbox).
|
||||
|
||||
## setupCommand (one-time container setup)
|
||||
|
||||
|
||||
@@ -169,7 +169,7 @@ Look for:
|
||||
|
||||
Common signatures:
|
||||
|
||||
- `Gateway start blocked: set gateway.mode=local` → local gateway mode is not enabled. Fix: set `gateway.mode="local"` in your config (or run `openclaw configure`). If you are running OpenClaw via Podman using the dedicated `openclaw` user, the config lives at `~openclaw/.openclaw/openclaw.json`.
|
||||
- `Gateway start blocked: set gateway.mode=local` → local gateway mode is not enabled. Fix: set `gateway.mode="local"` in your config (or run `openclaw configure`). If you are running OpenClaw via Podman, the default config path is `~/.openclaw/openclaw.json`.
|
||||
- `refusing to bind gateway ... without auth` → non-loopback bind without token/password.
|
||||
- `another gateway instance is already listening` / `EADDRINUSE` → port conflict.
|
||||
|
||||
|
||||
@@ -92,8 +92,8 @@ Think of the suites as “increasing realism” (and increasing flakiness/cost):
|
||||
- `pnpm test:max` exposes that same planner profile for a full local run.
|
||||
- On supported local Node versions, including Node 25, the normal profile can use top-level lane parallelism. `pnpm test:max` still pushes the planner harder when you want a more aggressive local run.
|
||||
- The base Vitest config marks the wrapper manifests/config files as `forceRerunTriggers` so changed-mode reruns stay correct when scheduler inputs change.
|
||||
- Vitest's filesystem module cache is now enabled by default for Node-side test reruns.
|
||||
- Opt out with `OPENCLAW_VITEST_FS_MODULE_CACHE=0` or `OPENCLAW_VITEST_FS_MODULE_CACHE=false` if you suspect stale transform cache behavior.
|
||||
- The wrapper keeps `OPENCLAW_VITEST_FS_MODULE_CACHE` enabled on supported hosts, but assigns a lane-local `OPENCLAW_VITEST_FS_MODULE_CACHE_PATH` so concurrent Vitest processes do not race on one shared experimental cache directory.
|
||||
- Set `OPENCLAW_VITEST_FS_MODULE_CACHE_PATH=/abs/path` if you want one explicit cache location for direct single-run profiling.
|
||||
- Perf-debug note:
|
||||
- `pnpm test:perf:imports` enables Vitest import-duration reporting plus import-breakdown output.
|
||||
- `pnpm test:perf:imports:changed` scopes the same profiling view to files changed since `origin/main`.
|
||||
@@ -506,7 +506,8 @@ Useful env vars:
|
||||
|
||||
## Docs sanity
|
||||
|
||||
Run docs checks after doc edits: `pnpm docs:list`.
|
||||
Run docs checks after doc edits: `pnpm check:docs`.
|
||||
Run full Mintlify anchor validation when you need in-page heading checks too: `pnpm docs:check-links:anchors`.
|
||||
|
||||
## Offline regression (CI-safe)
|
||||
|
||||
@@ -538,7 +539,9 @@ Future evals should stay deterministic first:
|
||||
|
||||
Contract tests verify that every registered plugin and channel conforms to its
|
||||
interface contract. They iterate over all discovered plugins and run a suite of
|
||||
shape and behavior assertions.
|
||||
shape and behavior assertions. The default `pnpm test` unit lane intentionally
|
||||
skips these shared seam and smoke files; run the contract commands explicitly
|
||||
when you touch shared channel or provider surfaces.
|
||||
|
||||
### Commands
|
||||
|
||||
@@ -559,6 +562,11 @@ Located in `src/channels/plugins/contracts/*.contract.test.ts`:
|
||||
- **threading** - Thread ID handling
|
||||
- **directory** - Directory/roster API
|
||||
- **group-policy** - Group policy enforcement
|
||||
|
||||
### Provider status contracts
|
||||
|
||||
Located in `src/plugins/contracts/*.contract.test.ts`.
|
||||
|
||||
- **status** - Channel status probes
|
||||
- **registry** - Plugin registry shape
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ update **without** changing your persisted channel:
|
||||
|
||||
```bash
|
||||
# Install a specific version
|
||||
openclaw update --tag 2026.3.22
|
||||
openclaw update --tag 2026.3.26
|
||||
|
||||
# Install from the beta dist-tag (one-off, does not persist)
|
||||
openclaw update --tag beta
|
||||
@@ -57,7 +57,7 @@ openclaw update --tag beta
|
||||
openclaw update --tag main
|
||||
|
||||
# Install a specific npm package spec
|
||||
openclaw update --tag openclaw@2026.3.22
|
||||
openclaw update --tag openclaw@2026.3.26
|
||||
```
|
||||
|
||||
Notes:
|
||||
@@ -75,7 +75,7 @@ Preview what `openclaw update` would do without making changes:
|
||||
```bash
|
||||
openclaw update --dry-run
|
||||
openclaw update --channel beta --dry-run
|
||||
openclaw update --tag 2026.3.22 --dry-run
|
||||
openclaw update --tag 2026.3.26 --dry-run
|
||||
openclaw update --dry-run --json
|
||||
```
|
||||
|
||||
|
||||
@@ -91,7 +91,7 @@ Custom profiles use `~/.openclaw-<profile>/` or a path set via `OPENCLAW_STATE_D
|
||||
|
||||
<Accordion title="Remote mode">
|
||||
If your UI points at a **remote** gateway, the remote host owns sessions and workspace.
|
||||
Migrate the gateway host itself, not your local laptop. See [FAQ](/help/faq#where-does-openclaw-store-its-data).
|
||||
Migrate the gateway host itself, not your local laptop. See [FAQ](/help/faq#where-things-live-on-disk).
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Secrets in backups">
|
||||
|
||||
@@ -7,127 +7,261 @@ title: "Podman"
|
||||
|
||||
# Podman
|
||||
|
||||
Run the OpenClaw Gateway in a **rootless** Podman container. Uses the same image as Docker (built from the repo [Dockerfile](https://github.com/openclaw/openclaw/blob/main/Dockerfile)).
|
||||
Run the OpenClaw Gateway in a rootless Podman container, managed by your current non-root user.
|
||||
|
||||
The intended model is:
|
||||
|
||||
- Podman runs the gateway container.
|
||||
- Your host `openclaw` CLI is the control plane.
|
||||
- Persistent state lives on the host under `~/.openclaw` by default.
|
||||
- Day-to-day management uses `openclaw --container <name> ...` instead of `sudo -u openclaw`, `podman exec`, or a separate service user.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Podman** (rootless mode)
|
||||
- **sudo** access for one-time setup (creating the dedicated user and building the image)
|
||||
- **Podman** in rootless mode
|
||||
- **OpenClaw CLI** installed on the host
|
||||
- **Optional:** `systemd --user` if you want Quadlet-managed auto-start
|
||||
- **Optional:** `sudo` only if you want `loginctl enable-linger "$(whoami)"` for boot persistence on a headless host
|
||||
|
||||
## Quick start
|
||||
|
||||
<Steps>
|
||||
<Step title="One-time setup">
|
||||
From the repo root, run the setup script. It creates a dedicated `openclaw` user, builds the container image, and installs the launch script:
|
||||
|
||||
```bash
|
||||
./scripts/podman/setup.sh
|
||||
```
|
||||
|
||||
This also creates a minimal config at `~openclaw/.openclaw/openclaw.json` (sets `gateway.mode` to `"local"`) so the Gateway can start without running the wizard.
|
||||
|
||||
By default the container is **not** installed as a systemd service -- you start it manually in the next step. For a production-style setup with auto-start and restarts, pass `--quadlet` instead:
|
||||
|
||||
```bash
|
||||
./scripts/podman/setup.sh --quadlet
|
||||
```
|
||||
|
||||
(Or set `OPENCLAW_PODMAN_QUADLET=1`. Use `--container` to install only the container and launch script.)
|
||||
|
||||
**Optional build-time env vars** (set before running `scripts/podman/setup.sh`):
|
||||
|
||||
- `OPENCLAW_DOCKER_APT_PACKAGES` -- install extra apt packages during image build.
|
||||
- `OPENCLAW_EXTENSIONS` -- pre-install extension dependencies (space-separated names, e.g. `diagnostics-otel matrix`).
|
||||
|
||||
From the repo root, run `./scripts/podman/setup.sh`.
|
||||
</Step>
|
||||
|
||||
<Step title="Start the Gateway">
|
||||
For a quick manual launch:
|
||||
|
||||
```bash
|
||||
./scripts/run-openclaw-podman.sh launch
|
||||
```
|
||||
|
||||
<Step title="Start the Gateway container">
|
||||
Start the container with `./scripts/run-openclaw-podman.sh launch`.
|
||||
</Step>
|
||||
|
||||
<Step title="Run the onboarding wizard">
|
||||
To add channels or providers interactively:
|
||||
|
||||
```bash
|
||||
./scripts/run-openclaw-podman.sh launch setup
|
||||
```
|
||||
|
||||
Then open `http://127.0.0.1:18789/` and use the token from `~openclaw/.openclaw/.env` (or the value printed by setup).
|
||||
<Step title="Run onboarding inside the container">
|
||||
Run `./scripts/run-openclaw-podman.sh launch setup`, then open `http://127.0.0.1:18789/`.
|
||||
</Step>
|
||||
|
||||
<Step title="Manage the running container from the host CLI">
|
||||
Set `OPENCLAW_CONTAINER=openclaw`, then use normal `openclaw` commands from the host.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
Setup details:
|
||||
|
||||
- `./scripts/podman/setup.sh` builds `openclaw:local` in your rootless Podman store by default, or uses `OPENCLAW_IMAGE` / `OPENCLAW_PODMAN_IMAGE` if you set one.
|
||||
- It creates `~/.openclaw/openclaw.json` with `gateway.mode: "local"` if missing.
|
||||
- It creates `~/.openclaw/.env` with `OPENCLAW_GATEWAY_TOKEN` if missing.
|
||||
- For manual launches, the helper reads only a small allowlist of Podman-related keys from `~/.openclaw/.env` and passes explicit runtime env vars to the container; it does not hand the full env file to Podman.
|
||||
|
||||
Quadlet-managed setup:
|
||||
|
||||
```bash
|
||||
./scripts/podman/setup.sh --quadlet
|
||||
```
|
||||
|
||||
Quadlet is a Linux-only option because it depends on systemd user services.
|
||||
|
||||
You can also set `OPENCLAW_PODMAN_QUADLET=1`.
|
||||
|
||||
Optional build/setup env vars:
|
||||
|
||||
- `OPENCLAW_IMAGE` or `OPENCLAW_PODMAN_IMAGE` -- use an existing/pulled image instead of building `openclaw:local`
|
||||
- `OPENCLAW_DOCKER_APT_PACKAGES` -- install extra apt packages during image build
|
||||
- `OPENCLAW_EXTENSIONS` -- pre-install extension dependencies at build time
|
||||
|
||||
Container start:
|
||||
|
||||
```bash
|
||||
./scripts/run-openclaw-podman.sh launch
|
||||
```
|
||||
|
||||
The script starts the container as your current uid/gid with `--userns=keep-id` and bind-mounts your OpenClaw state into the container.
|
||||
|
||||
Onboarding:
|
||||
|
||||
```bash
|
||||
./scripts/run-openclaw-podman.sh launch setup
|
||||
```
|
||||
|
||||
Then open `http://127.0.0.1:18789/` and use the token from `~/.openclaw/.env`.
|
||||
|
||||
Host CLI default:
|
||||
|
||||
```bash
|
||||
export OPENCLAW_CONTAINER=openclaw
|
||||
```
|
||||
|
||||
Then commands such as these will run inside that container automatically:
|
||||
|
||||
```bash
|
||||
openclaw dashboard --no-open
|
||||
openclaw gateway status --deep
|
||||
openclaw doctor
|
||||
openclaw channels login
|
||||
```
|
||||
|
||||
On macOS, Podman machine may make the browser appear non-local to the gateway.
|
||||
If the Control UI reports device-auth errors after launch, prefer the SSH
|
||||
tunnel flow in [macOS Podman SSH tunnel](#macos-podman-ssh-tunnel). For
|
||||
remote HTTPS access, use the Tailscale guidance in
|
||||
[Podman + Tailscale](#podman--tailscale).
|
||||
|
||||
## macOS Podman SSH tunnel
|
||||
|
||||
On macOS, Podman machine can make the browser appear non-local to the gateway even when the published port is only on `127.0.0.1`.
|
||||
|
||||
For local browser access, use an SSH tunnel into the Podman VM and open the tunneled localhost port instead.
|
||||
|
||||
Recommended local tunnel port:
|
||||
|
||||
- `28889` on the Mac host
|
||||
- forwarded to `127.0.0.1:18789` inside the Podman VM
|
||||
|
||||
Start the tunnel in a separate terminal:
|
||||
|
||||
```bash
|
||||
ssh -N \
|
||||
-i ~/.local/share/containers/podman/machine/machine \
|
||||
-p <podman-vm-ssh-port> \
|
||||
-L 28889:127.0.0.1:18789 \
|
||||
core@127.0.0.1
|
||||
```
|
||||
|
||||
In that command, `<podman-vm-ssh-port>` is the Podman VM's SSH port on the Mac host. Check your current value with:
|
||||
|
||||
```bash
|
||||
podman system connection list
|
||||
```
|
||||
|
||||
Allow the tunneled browser origin once. This is required the first time you use the tunnel because the launcher can auto-seed the Podman-published port, but it cannot infer your chosen browser tunnel port:
|
||||
|
||||
```bash
|
||||
OPENCLAW_CONTAINER=openclaw openclaw config set gateway.controlUi.allowedOrigins \
|
||||
'["http://127.0.0.1:18789","http://localhost:18789","http://127.0.0.1:28889","http://localhost:28889"]' \
|
||||
--strict-json
|
||||
podman restart openclaw
|
||||
```
|
||||
|
||||
That is a one-time step for the default `28889` tunnel.
|
||||
|
||||
Then open:
|
||||
|
||||
```text
|
||||
http://127.0.0.1:28889/
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- `18789` is usually already occupied on the Mac host by the Podman-published gateway port, so the tunnel uses `28889` as the local browser port.
|
||||
- If the UI asks for pairing approval, prefer explicit container-targeted or explicit-URL commands so the host CLI does not fall back to local pairing files:
|
||||
|
||||
```bash
|
||||
openclaw --container openclaw devices list
|
||||
openclaw --container openclaw devices approve --latest
|
||||
```
|
||||
|
||||
- Equivalent explicit-URL form:
|
||||
|
||||
```bash
|
||||
openclaw devices list \
|
||||
--url ws://127.0.0.1:28889 \
|
||||
--token "$(sed -n 's/^OPENCLAW_GATEWAY_TOKEN=//p' ~/.openclaw/.env | head -n1)"
|
||||
```
|
||||
|
||||
## Podman + Tailscale
|
||||
|
||||
For HTTPS or remote browser access, follow the main Tailscale docs.
|
||||
|
||||
Podman-specific note:
|
||||
|
||||
- Keep the Podman publish host at `127.0.0.1`.
|
||||
- Prefer host-managed `tailscale serve` over `openclaw gateway --tailscale serve`.
|
||||
- For local macOS browser access without HTTPS, prefer the SSH tunnel section above.
|
||||
|
||||
See:
|
||||
|
||||
- [Tailscale](/gateway/tailscale)
|
||||
- [Control UI](/web/control-ui)
|
||||
|
||||
## Systemd (Quadlet, optional)
|
||||
|
||||
If you ran `./scripts/podman/setup.sh --quadlet` (or `OPENCLAW_PODMAN_QUADLET=1`), a [Podman Quadlet](https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html) unit is installed so the gateway runs as a systemd user service for the openclaw user. The service is enabled and started at the end of setup.
|
||||
If you ran `./scripts/podman/setup.sh --quadlet`, setup installs a Quadlet file at:
|
||||
|
||||
- **Start:** `sudo systemctl --machine openclaw@ --user start openclaw.service`
|
||||
- **Stop:** `sudo systemctl --machine openclaw@ --user stop openclaw.service`
|
||||
- **Status:** `sudo systemctl --machine openclaw@ --user status openclaw.service`
|
||||
- **Logs:** `sudo journalctl --machine openclaw@ --user -u openclaw.service -f`
|
||||
```bash
|
||||
~/.config/containers/systemd/openclaw.container
|
||||
```
|
||||
|
||||
The quadlet file lives at `~openclaw/.config/containers/systemd/openclaw.container`. To change ports or env, edit that file (or the `.env` it sources), then `sudo systemctl --machine openclaw@ --user daemon-reload` and restart the service. On boot, the service starts automatically if lingering is enabled for openclaw (setup does this when loginctl is available).
|
||||
Useful commands:
|
||||
|
||||
To add quadlet **after** an initial setup that did not use it, re-run: `./scripts/podman/setup.sh --quadlet`.
|
||||
- **Start:** `systemctl --user start openclaw.service`
|
||||
- **Stop:** `systemctl --user stop openclaw.service`
|
||||
- **Status:** `systemctl --user status openclaw.service`
|
||||
- **Logs:** `journalctl --user -u openclaw.service -f`
|
||||
|
||||
## The openclaw user (non-login)
|
||||
After editing the Quadlet file:
|
||||
|
||||
`scripts/podman/setup.sh` creates a dedicated system user `openclaw`:
|
||||
```bash
|
||||
systemctl --user daemon-reload
|
||||
systemctl --user restart openclaw.service
|
||||
```
|
||||
|
||||
- **Shell:** `nologin` — no interactive login; reduces attack surface.
|
||||
- **Home:** e.g. `/home/openclaw` — holds `~/.openclaw` (config, workspace) and the launch script `run-openclaw-podman.sh`.
|
||||
- **Rootless Podman:** The user must have a **subuid** and **subgid** range. Many distros assign these automatically when the user is created. If setup prints a warning, add lines to `/etc/subuid` and `/etc/subgid`:
|
||||
For boot persistence on SSH/headless hosts, enable lingering for your current user:
|
||||
|
||||
```text
|
||||
openclaw:100000:65536
|
||||
```
|
||||
```bash
|
||||
sudo loginctl enable-linger "$(whoami)"
|
||||
```
|
||||
|
||||
Then start the gateway as that user (e.g. from cron or systemd):
|
||||
## Config, env, and storage
|
||||
|
||||
```bash
|
||||
sudo -u openclaw /home/openclaw/run-openclaw-podman.sh
|
||||
sudo -u openclaw /home/openclaw/run-openclaw-podman.sh setup
|
||||
```
|
||||
- **Config dir:** `~/.openclaw`
|
||||
- **Workspace dir:** `~/.openclaw/workspace`
|
||||
- **Token file:** `~/.openclaw/.env`
|
||||
- **Launch helper:** `./scripts/run-openclaw-podman.sh`
|
||||
|
||||
- **Config:** Only `openclaw` and root can access `/home/openclaw/.openclaw`. To edit config: use the Control UI once the gateway is running, or `sudo -u openclaw $EDITOR /home/openclaw/.openclaw/openclaw.json`.
|
||||
The launch script and Quadlet bind-mount host state into the container:
|
||||
|
||||
## Environment and config
|
||||
- `OPENCLAW_CONFIG_DIR` -> `/home/node/.openclaw`
|
||||
- `OPENCLAW_WORKSPACE_DIR` -> `/home/node/.openclaw/workspace`
|
||||
|
||||
- **Token:** Stored in `~openclaw/.openclaw/.env` as `OPENCLAW_GATEWAY_TOKEN`. `scripts/podman/setup.sh` and `run-openclaw-podman.sh` generate it if missing (uses `openssl`, `python3`, or `od`).
|
||||
- **Optional:** In that `.env` you can set provider keys (e.g. `GROQ_API_KEY`, `OLLAMA_API_KEY`) and other OpenClaw env vars.
|
||||
- **Host ports:** By default the script maps `18789` (gateway) and `18790` (bridge). Override the **host** port mapping with `OPENCLAW_PODMAN_GATEWAY_HOST_PORT` and `OPENCLAW_PODMAN_BRIDGE_HOST_PORT` when launching.
|
||||
- **Gateway bind:** By default, `run-openclaw-podman.sh` starts the gateway with `--bind loopback` for safe local access. To expose on LAN, set `OPENCLAW_GATEWAY_BIND=lan` and configure `gateway.controlUi.allowedOrigins` (or explicitly enable host-header fallback) in `openclaw.json`.
|
||||
- **Paths:** Host config and workspace default to `~openclaw/.openclaw` and `~openclaw/.openclaw/workspace`. Override the host paths used by the launch script with `OPENCLAW_CONFIG_DIR` and `OPENCLAW_WORKSPACE_DIR`.
|
||||
By default those are host directories, not anonymous container state, so config and workspace survive container replacement.
|
||||
The Podman setup also seeds `gateway.controlUi.allowedOrigins` for `127.0.0.1` and `localhost` on the published gateway port so the local dashboard works with the container's non-loopback bind.
|
||||
|
||||
## Storage model
|
||||
Useful env vars for the manual launcher:
|
||||
|
||||
- **Persistent host data:** `OPENCLAW_CONFIG_DIR` and `OPENCLAW_WORKSPACE_DIR` are bind-mounted into the container and retain state on the host.
|
||||
- **Ephemeral sandbox tmpfs:** if you enable `agents.defaults.sandbox`, the tool sandbox containers mount `tmpfs` at `/tmp`, `/var/tmp`, and `/run`. Those paths are memory-backed and disappear with the sandbox container; the top-level Podman container setup does not add its own tmpfs mounts.
|
||||
- **Disk growth hotspots:** the main paths to watch are `media/`, `agents/<agentId>/sessions/sessions.json`, transcript JSONL files, `cron/runs/*.jsonl`, and rolling file logs under `/tmp/openclaw/` (or your configured `logging.file`).
|
||||
- `OPENCLAW_PODMAN_CONTAINER` -- container name (`openclaw` by default)
|
||||
- `OPENCLAW_PODMAN_IMAGE` / `OPENCLAW_IMAGE` -- image to run
|
||||
- `OPENCLAW_PODMAN_GATEWAY_HOST_PORT` -- host port mapped to container `18789`
|
||||
- `OPENCLAW_PODMAN_BRIDGE_HOST_PORT` -- host port mapped to container `18790`
|
||||
- `OPENCLAW_PODMAN_PUBLISH_HOST` -- host interface for published ports; default is `127.0.0.1`
|
||||
- `OPENCLAW_GATEWAY_BIND` -- gateway bind mode inside the container; default is `lan`
|
||||
- `OPENCLAW_PODMAN_USERNS` -- `keep-id` (default), `auto`, or `host`
|
||||
|
||||
`scripts/podman/setup.sh` now stages the image tar in a private temp directory and prints the chosen base dir during setup. For non-root runs it accepts `TMPDIR` only when that base is safe to use; otherwise it falls back to `/var/tmp`, then `/tmp`. The saved tar stays owner-only and is streamed into the target user’s `podman load`, so private caller temp dirs do not block setup.
|
||||
The manual launcher reads `~/.openclaw/.env` before finalizing container/image defaults, so you can persist these there.
|
||||
|
||||
If you use a non-default `OPENCLAW_CONFIG_DIR` or `OPENCLAW_WORKSPACE_DIR`, set the same variables for both `./scripts/podman/setup.sh` and later `./scripts/run-openclaw-podman.sh launch` commands. The repo-local launcher does not persist custom path overrides across shells.
|
||||
|
||||
Quadlet note:
|
||||
|
||||
- The generated Quadlet service intentionally keeps a fixed, hardened default shape: `127.0.0.1` published ports, `--bind lan` inside the container, and `keep-id` user namespace.
|
||||
- It still reads `~/.openclaw/.env` for gateway runtime env such as `OPENCLAW_GATEWAY_TOKEN`, but it does not consume the manual launcher's Podman-specific override allowlist.
|
||||
- If you need custom publish ports, publish host, or other container-run flags, use the manual launcher or edit `~/.config/containers/systemd/openclaw.container` directly, then reload and restart the service.
|
||||
|
||||
## Useful commands
|
||||
|
||||
- **Logs:** With quadlet: `sudo journalctl --machine openclaw@ --user -u openclaw.service -f`. With script: `sudo -u openclaw podman logs -f openclaw`
|
||||
- **Stop:** With quadlet: `sudo systemctl --machine openclaw@ --user stop openclaw.service`. With script: `sudo -u openclaw podman stop openclaw`
|
||||
- **Start again:** With quadlet: `sudo systemctl --machine openclaw@ --user start openclaw.service`. With script: re-run the launch script or `podman start openclaw`
|
||||
- **Remove container:** `sudo -u openclaw podman rm -f openclaw` — config and workspace on the host are kept
|
||||
- **Container logs:** `podman logs -f openclaw`
|
||||
- **Stop container:** `podman stop openclaw`
|
||||
- **Remove container:** `podman rm -f openclaw`
|
||||
- **Open dashboard URL from host CLI:** `openclaw dashboard --no-open`
|
||||
- **Health/status via host CLI:** `openclaw gateway status --deep`
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- **Permission denied (EACCES) on config or auth-profiles:** The container defaults to `--userns=keep-id` and runs as the same uid/gid as the host user running the script. Ensure your host `OPENCLAW_CONFIG_DIR` and `OPENCLAW_WORKSPACE_DIR` are owned by that user.
|
||||
- **Gateway start blocked (missing `gateway.mode=local`):** Ensure `~openclaw/.openclaw/openclaw.json` exists and sets `gateway.mode="local"`. `scripts/podman/setup.sh` creates this file if missing.
|
||||
- **Rootless Podman fails for user openclaw:** Check `/etc/subuid` and `/etc/subgid` contain a line for `openclaw` (e.g. `openclaw:100000:65536`). Add it if missing and restart.
|
||||
- **Container name in use:** The launch script uses `podman run --replace`, so the existing container is replaced when you start again. To clean up manually: `podman rm -f openclaw`.
|
||||
- **Script not found when running as openclaw:** Ensure `scripts/podman/setup.sh` was run so that `run-openclaw-podman.sh` is copied to openclaw’s home (e.g. `/home/openclaw/run-openclaw-podman.sh`).
|
||||
- **Quadlet service not found or fails to start:** Run `sudo systemctl --machine openclaw@ --user daemon-reload` after editing the `.container` file. Quadlet requires cgroups v2: `podman info --format '{{.Host.CgroupsVersion}}'` should show `2`.
|
||||
- **Permission denied (EACCES) on config or workspace:** The container runs with `--userns=keep-id` and `--user <your uid>:<your gid>` by default. Ensure the host config/workspace paths are owned by your current user.
|
||||
- **Gateway start blocked (missing `gateway.mode=local`):** Ensure `~/.openclaw/openclaw.json` exists and sets `gateway.mode="local"`. `scripts/podman/setup.sh` creates this if missing.
|
||||
- **Container CLI commands hit the wrong target:** Use `openclaw --container <name> ...` explicitly, or export `OPENCLAW_CONTAINER=<name>` in your shell.
|
||||
- **`openclaw update` fails with `--container`:** Expected. Rebuild/pull the image, then restart the container or the Quadlet service.
|
||||
- **Quadlet service does not start:** Run `systemctl --user daemon-reload`, then `systemctl --user start openclaw.service`. On headless systems you may also need `sudo loginctl enable-linger "$(whoami)"`.
|
||||
- **SELinux blocks bind mounts:** Leave the default mount behavior alone; the launcher auto-adds `:Z` on Linux when SELinux is enforcing or permissive.
|
||||
|
||||
## Optional: run as your own user
|
||||
## Related
|
||||
|
||||
To run the gateway as your normal user (no dedicated openclaw user): build the image, create `~/.openclaw/.env` with `OPENCLAW_GATEWAY_TOKEN`, and run the container with `--userns=keep-id` and mounts to your `~/.openclaw`. The launch script is designed for the openclaw-user flow; for a single-user setup you can instead run the `podman run` command from the script manually, pointing config and workspace to your home. Recommended for most users: use `scripts/podman/setup.sh` and run as the openclaw user so config and process are isolated.
|
||||
- [Docker](/install/docker)
|
||||
- [Gateway background process](/gateway/background-process)
|
||||
- [Gateway troubleshooting](/gateway/troubleshooting)
|
||||
|
||||
@@ -149,6 +149,8 @@ Hook guard semantics to keep in mind:
|
||||
|
||||
- `before_tool_call`: `{ block: true }` is terminal and stops lower-priority handlers.
|
||||
- `before_tool_call`: `{ block: false }` is treated as no decision.
|
||||
- `before_skill_install`: `{ block: true }` is terminal and stops lower-priority handlers.
|
||||
- `before_skill_install`: `{ block: false }` is treated as no decision.
|
||||
- `message_sending`: `{ cancel: true }` is terminal and stops lower-priority handlers.
|
||||
- `message_sending`: `{ cancel: false }` is treated as no decision.
|
||||
|
||||
|
||||
@@ -46,6 +46,8 @@ Use it for:
|
||||
- config validation
|
||||
- auth and onboarding metadata that should be available without booting plugin
|
||||
runtime
|
||||
- static capability ownership snapshots used for bundled compat wiring and
|
||||
contract coverage
|
||||
- config UI hints
|
||||
|
||||
Do not use it for:
|
||||
@@ -129,6 +131,7 @@ Those belong in your plugin code and `package.json`.
|
||||
| `cliBackends` | No | `string[]` | CLI inference backend ids owned by this plugin. Used for startup auto-activation from explicit config refs. |
|
||||
| `providerAuthEnvVars` | No | `Record<string, string[]>` | Cheap provider-auth env metadata that OpenClaw can inspect without loading plugin code. |
|
||||
| `providerAuthChoices` | No | `object[]` | Cheap auth-choice metadata for onboarding pickers, preferred-provider resolution, and simple CLI flag wiring. |
|
||||
| `contracts` | No | `object` | Static bundled capability snapshot for speech, media-understanding, image-generation, web search, and tool ownership. |
|
||||
| `skills` | No | `string[]` | Skill directories to load, relative to the plugin root. |
|
||||
| `name` | No | `string` | Human-readable plugin name. |
|
||||
| `description` | No | `string` | Short summary shown in plugin surfaces. |
|
||||
@@ -184,6 +187,38 @@ Each field hint can include:
|
||||
| `sensitive` | `boolean` | Marks the field as secret or sensitive. |
|
||||
| `placeholder` | `string` | Placeholder text for form inputs. |
|
||||
|
||||
## contracts reference
|
||||
|
||||
Use `contracts` only for static capability ownership metadata that OpenClaw can
|
||||
read without importing the plugin runtime.
|
||||
|
||||
```json
|
||||
{
|
||||
"contracts": {
|
||||
"speechProviders": ["openai"],
|
||||
"mediaUnderstandingProviders": ["openai", "openai-codex"],
|
||||
"imageGenerationProviders": ["openai"],
|
||||
"webSearchProviders": ["gemini"],
|
||||
"tools": ["firecrawl_search", "firecrawl_scrape"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Each list is optional:
|
||||
|
||||
| Field | Type | What it means |
|
||||
| ----------------------------- | ---------- | -------------------------------------------------------------- |
|
||||
| `speechProviders` | `string[]` | Speech provider ids this plugin owns. |
|
||||
| `mediaUnderstandingProviders` | `string[]` | Media-understanding provider ids this plugin owns. |
|
||||
| `imageGenerationProviders` | `string[]` | Image-generation provider ids this plugin owns. |
|
||||
| `webSearchProviders` | `string[]` | Web-search provider ids this plugin owns. |
|
||||
| `tools` | `string[]` | Agent tool names this plugin owns for bundled contract checks. |
|
||||
|
||||
Legacy top-level `speechProviders`, `mediaUnderstandingProviders`, and
|
||||
`imageGenerationProviders` are deprecated. Use `openclaw doctor --fix` to move
|
||||
them under `contracts`; normal manifest loading no longer treats them as
|
||||
capability ownership.
|
||||
|
||||
## Manifest versus package.json
|
||||
|
||||
The two files serve different jobs:
|
||||
|
||||
@@ -159,6 +159,22 @@ AI CLI backend such as `claude-cli` or `codex-cli`.
|
||||
| `api.registerContextEngine(id, factory)` | Context engine (one active at a time) |
|
||||
| `api.registerMemoryPromptSection(builder)` | Memory prompt section builder |
|
||||
| `api.registerMemoryFlushPlan(resolver)` | Memory flush plan resolver |
|
||||
| `api.registerMemoryRuntime(runtime)` | Memory runtime adapter |
|
||||
|
||||
### Memory embedding adapters
|
||||
|
||||
| Method | What it registers |
|
||||
| ---------------------------------------------- | ---------------------------------------------- |
|
||||
| `api.registerMemoryEmbeddingProvider(adapter)` | Memory embedding adapter for the active plugin |
|
||||
|
||||
- `registerMemoryPromptSection`, `registerMemoryFlushPlan`, and
|
||||
`registerMemoryRuntime` are exclusive to memory plugins.
|
||||
- `registerMemoryEmbeddingProvider` lets the active memory plugin register one
|
||||
or more embedding adapter ids (for example `openai`, `gemini`, or a custom
|
||||
plugin-defined id).
|
||||
- User config such as `agents.defaults.memorySearch.provider` and
|
||||
`agents.defaults.memorySearch.fallback` resolves against those registered
|
||||
adapter ids.
|
||||
|
||||
### Events and lifecycle
|
||||
|
||||
@@ -171,6 +187,8 @@ AI CLI backend such as `claude-cli` or `codex-cli`.
|
||||
|
||||
- `before_tool_call`: returning `{ block: true }` is terminal. Once any handler sets it, lower-priority handlers are skipped.
|
||||
- `before_tool_call`: returning `{ block: false }` is treated as no decision (same as omitting `block`), not as an override.
|
||||
- `before_skill_install`: returning `{ block: true }` is terminal. Once any handler sets it, lower-priority handlers are skipped.
|
||||
- `before_skill_install`: returning `{ block: false }` is treated as no decision (same as omitting `block`), not as an override.
|
||||
- `message_sending`: returning `{ cancel: true }` is terminal. Once any handler sets it, lower-priority handlers are skipped.
|
||||
- `message_sending`: returning `{ cancel: false }` is treated as no decision (same as omitting `cancel`), not as an override.
|
||||
|
||||
|
||||
@@ -20,7 +20,12 @@ automatic flush), see [Memory](/concepts/memory).
|
||||
- Watches memory files for changes (debounced).
|
||||
- Configure memory search under `agents.defaults.memorySearch` (not top-level
|
||||
`memorySearch`).
|
||||
- Uses remote embeddings by default. If `memorySearch.provider` is not set, OpenClaw auto-selects:
|
||||
- `memorySearch.provider` and `memorySearch.fallback` accept **adapter ids**
|
||||
registered by the active memory plugin.
|
||||
- The default `memory-core` plugin registers these built-in adapter ids:
|
||||
`local`, `openai`, `gemini`, `voyage`, `mistral`, and `ollama`.
|
||||
- With the default `memory-core` plugin, if `memorySearch.provider` is not set,
|
||||
OpenClaw auto-selects:
|
||||
1. `local` if a `memorySearch.local.modelPath` is configured and the file exists.
|
||||
2. `openai` if an OpenAI key can be resolved.
|
||||
3. `gemini` if a Gemini key can be resolved.
|
||||
@@ -29,8 +34,9 @@ automatic flush), see [Memory](/concepts/memory).
|
||||
6. Otherwise memory search stays disabled until configured.
|
||||
- Local mode uses node-llama-cpp and may require `pnpm approve-builds`.
|
||||
- Uses sqlite-vec (when available) to accelerate vector search inside SQLite.
|
||||
- `memorySearch.provider = "ollama"` is also supported for local/self-hosted
|
||||
Ollama embeddings (`/api/embeddings`), but it is not auto-selected.
|
||||
- With the default `memory-core` plugin, `memorySearch.provider = "ollama"` is
|
||||
also supported for local/self-hosted Ollama embeddings (`/api/embeddings`),
|
||||
but it is not auto-selected.
|
||||
|
||||
Remote embeddings **require** an API key for the embedding provider. OpenClaw
|
||||
resolves keys from auth profiles, `models.providers.*.apiKey`, or environment
|
||||
@@ -317,15 +323,16 @@ If you don't want to set an API key, use `memorySearch.provider = "local"` or se
|
||||
|
||||
### Fallbacks
|
||||
|
||||
- `memorySearch.fallback` can be `openai`, `gemini`, `voyage`, `mistral`, `ollama`, `local`, or `none`.
|
||||
- `memorySearch.fallback` can be any registered memory embedding adapter id, or `none`.
|
||||
- With the default `memory-core` plugin, valid built-in fallback ids are `openai`, `gemini`, `voyage`, `mistral`, `ollama`, and `local`.
|
||||
- The fallback provider is only used when the primary embedding provider fails.
|
||||
|
||||
### Batch indexing (OpenAI + Gemini + Voyage)
|
||||
### Batch indexing
|
||||
|
||||
- Disabled by default. Set `agents.defaults.memorySearch.remote.batch.enabled = true` to enable for large-corpus indexing (OpenAI, Gemini, and Voyage).
|
||||
- Disabled by default. Set `agents.defaults.memorySearch.remote.batch.enabled = true` to enable batch indexing for providers whose adapter exposes batch support.
|
||||
- Default behavior waits for batch completion; tune `remote.batch.wait`, `remote.batch.pollIntervalMs`, and `remote.batch.timeoutMinutes` if needed.
|
||||
- Set `remote.batch.concurrency` to control how many batch jobs we submit in parallel (default: 2).
|
||||
- Batch mode applies when `memorySearch.provider = "openai"` or `"gemini"` and uses the corresponding API key.
|
||||
- With the default `memory-core` plugin, batch indexing is available for `openai`, `gemini`, and `voyage`.
|
||||
- Gemini batch jobs use the async embeddings batch endpoint and require Gemini Batch API availability.
|
||||
|
||||
Why OpenAI batch is fast and cheap:
|
||||
|
||||
@@ -40,7 +40,7 @@ For local PR land/gate checks, run:
|
||||
If `pnpm test` flakes on a loaded host, rerun once before treating it as a regression, then isolate with `pnpm vitest run <path/to/test>`. For memory-constrained hosts, use:
|
||||
|
||||
- `OPENCLAW_TEST_PROFILE=low OPENCLAW_TEST_SERIAL_GATEWAY=1 pnpm test`
|
||||
- `OPENCLAW_VITEST_FS_MODULE_CACHE=0 pnpm test:changed`
|
||||
- `OPENCLAW_VITEST_FS_MODULE_CACHE_PATH=/tmp/openclaw-vitest-cache pnpm test:changed`
|
||||
|
||||
## Model latency bench (local keys)
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
summary: "Use ACP runtime sessions for Pi, Claude Code, Codex, OpenCode, Gemini CLI, and other harness agents"
|
||||
summary: "Use ACP runtime sessions for Codex, Claude Code, Cursor, Gemini CLI, OpenClaw ACP, and other harness agents"
|
||||
read_when:
|
||||
- Running coding harnesses through ACP
|
||||
- Setting up thread-bound ACP sessions on thread-capable channels
|
||||
@@ -11,7 +11,7 @@ title: "ACP Agents"
|
||||
|
||||
# ACP agents
|
||||
|
||||
[Agent Client Protocol (ACP)](https://agentclientprotocol.com/) sessions let OpenClaw run external coding harnesses (for example Pi, Claude Code, Codex, OpenCode, and Gemini CLI) through an ACP backend plugin.
|
||||
[Agent Client Protocol (ACP)](https://agentclientprotocol.com/) sessions let OpenClaw run external coding harnesses (for example Pi, Claude Code, Codex, Cursor, Copilot, OpenClaw ACP, OpenCode, Gemini CLI, and other supported ACPX harnesses) through an ACP backend plugin.
|
||||
|
||||
If you ask OpenClaw in plain language to "run this in Codex" or "start Claude Code in a thread", OpenClaw should route that request to the ACP runtime (not the native sub-agent runtime).
|
||||
|
||||
@@ -441,14 +441,23 @@ Equivalent operations:
|
||||
|
||||
Current acpx built-in harness aliases:
|
||||
|
||||
- `pi`
|
||||
- `claude`
|
||||
- `codex`
|
||||
- `opencode`
|
||||
- `copilot`
|
||||
- `cursor` (Cursor CLI: `cursor-agent acp`)
|
||||
- `droid`
|
||||
- `gemini`
|
||||
- `iflow`
|
||||
- `kilocode`
|
||||
- `kimi`
|
||||
- `kiro`
|
||||
- `openclaw`
|
||||
- `opencode`
|
||||
- `pi`
|
||||
- `qwen`
|
||||
|
||||
When OpenClaw uses the acpx backend, prefer these values for `agentId` unless your acpx config defines custom agent aliases.
|
||||
If your local Cursor install still exposes ACP as `agent acp`, override the `cursor` agent command in your acpx config instead of changing the built-in default.
|
||||
|
||||
Direct acpx CLI usage can also target arbitrary adapters via `--agent <command>`, but that raw escape hatch is an acpx CLI feature (not the normal OpenClaw `agentId` path).
|
||||
|
||||
@@ -464,7 +473,22 @@ Core ACP baseline:
|
||||
dispatch: { enabled: true },
|
||||
backend: "acpx",
|
||||
defaultAgent: "codex",
|
||||
allowedAgents: ["pi", "claude", "codex", "opencode", "gemini", "kimi"],
|
||||
allowedAgents: [
|
||||
"claude",
|
||||
"codex",
|
||||
"copilot",
|
||||
"cursor",
|
||||
"droid",
|
||||
"gemini",
|
||||
"iflow",
|
||||
"kilocode",
|
||||
"kimi",
|
||||
"kiro",
|
||||
"openclaw",
|
||||
"opencode",
|
||||
"pi",
|
||||
"qwen",
|
||||
],
|
||||
maxConcurrentSessions: 8,
|
||||
stream: {
|
||||
coalesceIdleMs: 300,
|
||||
|
||||
@@ -36,8 +36,9 @@ The tool accepts a single `input` string that wraps one or more file operations:
|
||||
- `tools.exec.applyPatch.workspaceOnly` defaults to `true` (workspace-contained). Set it to `false` only if you intentionally want `apply_patch` to write/delete outside the workspace directory.
|
||||
- Use `*** Move to:` within an `*** Update File:` hunk to rename files.
|
||||
- `*** End of File` marks an EOF-only insert when needed.
|
||||
- Experimental and disabled by default. Enable with `tools.exec.applyPatch.enabled`.
|
||||
- OpenAI-only (including OpenAI Codex). Optionally gate by model via
|
||||
- Available by default for OpenAI and OpenAI Codex models. Set
|
||||
`tools.exec.applyPatch.enabled: false` to disable it.
|
||||
- Optionally gate by model via
|
||||
`tools.exec.applyPatch.allowModels`.
|
||||
- Config is only under `tools.exec`.
|
||||
|
||||
|
||||
@@ -231,8 +231,9 @@ Notes:
|
||||
## Browserless (hosted remote CDP)
|
||||
|
||||
[Browserless](https://browserless.io) is a hosted Chromium service that exposes
|
||||
CDP endpoints over HTTPS. You can point an OpenClaw browser profile at a
|
||||
Browserless region endpoint and authenticate with your API key.
|
||||
CDP connection URLs over HTTPS and WebSocket. OpenClaw can use either form, but
|
||||
for a remote browser profile the simplest option is the direct WebSocket URL
|
||||
from Browserless' connection docs.
|
||||
|
||||
Example:
|
||||
|
||||
@@ -245,7 +246,7 @@ Example:
|
||||
remoteCdpHandshakeTimeoutMs: 4000,
|
||||
profiles: {
|
||||
browserless: {
|
||||
cdpUrl: "https://production-sfo.browserless.io?token=<BROWSERLESS_API_KEY>",
|
||||
cdpUrl: "wss://production-sfo.browserless.io?token=<BROWSERLESS_API_KEY>",
|
||||
color: "#00AA00",
|
||||
},
|
||||
},
|
||||
@@ -257,17 +258,21 @@ Notes:
|
||||
|
||||
- Replace `<BROWSERLESS_API_KEY>` with your real Browserless token.
|
||||
- Choose the region endpoint that matches your Browserless account (see their docs).
|
||||
- If Browserless gives you an HTTPS base URL, you can either convert it to
|
||||
`wss://` for a direct CDP connection or keep the HTTPS URL and let OpenClaw
|
||||
discover `/json/version`.
|
||||
|
||||
## Direct WebSocket CDP providers
|
||||
|
||||
Some hosted browser services expose a **direct WebSocket** endpoint rather than
|
||||
the standard HTTP-based CDP discovery (`/json/version`). OpenClaw supports both:
|
||||
|
||||
- **HTTP(S) endpoints** (e.g. Browserless) — OpenClaw calls `/json/version` to
|
||||
discover the WebSocket debugger URL, then connects.
|
||||
- **HTTP(S) endpoints** — OpenClaw calls `/json/version` to discover the
|
||||
WebSocket debugger URL, then connects.
|
||||
- **WebSocket endpoints** (`ws://` / `wss://`) — OpenClaw connects directly,
|
||||
skipping `/json/version`. Use this for services like
|
||||
[Browserbase](https://www.browserbase.com) or any provider that hands you a
|
||||
[Browserless](https://browserless.io),
|
||||
[Browserbase](https://www.browserbase.com), or any provider that hands you a
|
||||
WebSocket URL.
|
||||
|
||||
### Browserbase
|
||||
|
||||
@@ -361,6 +361,35 @@ Reply in chat:
|
||||
/approve <id> deny
|
||||
```
|
||||
|
||||
The `/approve` command handles both exec approvals and plugin approvals. If the ID does not match a pending exec approval, it automatically checks plugin approvals.
|
||||
|
||||
### Plugin approval forwarding
|
||||
|
||||
Plugin approval forwarding uses the same delivery pipeline as exec approvals but has its own
|
||||
independent config under `approvals.plugin`. Enabling or disabling one does not affect the other.
|
||||
|
||||
```json5
|
||||
{
|
||||
approvals: {
|
||||
plugin: {
|
||||
enabled: true,
|
||||
mode: "targets",
|
||||
agentFilter: ["main"],
|
||||
targets: [
|
||||
{ channel: "slack", to: "U12345678" },
|
||||
{ channel: "telegram", to: "123456789" },
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
The config shape is identical to `approvals.exec`: `enabled`, `mode`, `agentFilter`,
|
||||
`sessionFilter`, and `targets` work the same way.
|
||||
|
||||
Channels that support interactive exec approval buttons (such as Telegram) also render buttons for
|
||||
plugin approvals. Channels without adapter support fall back to plain text with `/approve` instructions.
|
||||
|
||||
### Built-in chat approval clients
|
||||
|
||||
Discord and Telegram can also act as explicit exec approval clients with channel-specific config.
|
||||
@@ -384,8 +413,8 @@ topics, OpenClaw preserves the topic for the approval prompt and the post-approv
|
||||
|
||||
See:
|
||||
|
||||
- [Discord](/channels/discord#exec-approvals-in-discord)
|
||||
- [Telegram](/channels/telegram#exec-approvals-in-telegram)
|
||||
- [Discord](/channels/discord)
|
||||
- [Telegram](/channels/telegram)
|
||||
|
||||
### macOS IPC flow
|
||||
|
||||
|
||||
@@ -184,16 +184,17 @@ Paste (bracketed by default):
|
||||
{ "tool": "process", "action": "paste", "sessionId": "<id>", "text": "line1\nline2\n" }
|
||||
```
|
||||
|
||||
## apply_patch (experimental)
|
||||
## apply_patch
|
||||
|
||||
`apply_patch` is a subtool of `exec` for structured multi-file edits.
|
||||
Enable it explicitly:
|
||||
It is enabled by default for OpenAI and OpenAI Codex models. Use config only
|
||||
when you want to disable it or restrict it to specific models:
|
||||
|
||||
```json5
|
||||
{
|
||||
tools: {
|
||||
exec: {
|
||||
applyPatch: { enabled: true, workspaceOnly: true, allowModels: ["gpt-5.2"] },
|
||||
applyPatch: { workspaceOnly: true, allowModels: ["gpt-5.2"] },
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -202,6 +203,7 @@ Enable it explicitly:
|
||||
Notes:
|
||||
|
||||
- Only available for OpenAI/OpenAI Codex models.
|
||||
- Tool policy still applies; `allow: ["exec"]` implicitly allows `apply_patch`.
|
||||
- Tool policy still applies; `allow: ["write"]` implicitly allows `apply_patch`.
|
||||
- Config lives under `tools.exec.applyPatch`.
|
||||
- `tools.exec.applyPatch.enabled` defaults to `true`; set it to `false` to disable the tool for OpenAI models.
|
||||
- `tools.exec.applyPatch.workspaceOnly` defaults to `true` (workspace-contained). Set it to `false` only if you intentionally want `apply_patch` to write/delete outside the workspace directory.
|
||||
|
||||
@@ -263,6 +263,8 @@ Hook guard behavior for typed lifecycle hooks:
|
||||
|
||||
- `before_tool_call`: `{ block: true }` is terminal; lower-priority handlers are skipped.
|
||||
- `before_tool_call`: `{ block: false }` is a no-op and does not clear an earlier block.
|
||||
- `before_skill_install`: `{ block: true }` is terminal; lower-priority handlers are skipped.
|
||||
- `before_skill_install`: `{ block: false }` is a no-op and does not clear an earlier block.
|
||||
- `message_sending`: `{ cancel: true }` is terminal; lower-priority handlers are skipped.
|
||||
- `message_sending`: `{ cancel: false }` is a no-op and does not clear an earlier cancel.
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ It works anywhere OpenClaw can send audio.
|
||||
## Supported services
|
||||
|
||||
- **ElevenLabs** (primary or fallback provider)
|
||||
- **Microsoft** (primary or fallback provider; current bundled implementation uses `node-edge-tts`, default when no API keys)
|
||||
- **Microsoft** (primary or fallback provider; current bundled implementation uses `node-edge-tts`)
|
||||
- **OpenAI** (primary or fallback provider; also used for summaries)
|
||||
|
||||
### Microsoft speech notes
|
||||
@@ -38,9 +38,7 @@ If you want OpenAI or ElevenLabs:
|
||||
- `ELEVENLABS_API_KEY` (or `XI_API_KEY`)
|
||||
- `OPENAI_API_KEY`
|
||||
|
||||
Microsoft speech does **not** require an API key. If no API keys are found,
|
||||
OpenClaw defaults to Microsoft (unless disabled via
|
||||
`messages.tts.microsoft.enabled=false` or `messages.tts.edge.enabled=false`).
|
||||
Microsoft speech does **not** require an API key.
|
||||
|
||||
If multiple providers are configured, the selected provider is used first and the others are fallback options.
|
||||
Auto-summary uses the configured `summaryModel` (or `agents.defaults.model.primary`),
|
||||
@@ -60,8 +58,8 @@ so that provider must also be authenticated if you enable summaries.
|
||||
No. Auto‑TTS is **off** by default. Enable it in config with
|
||||
`messages.tts.auto` or per session with `/tts always` (alias: `/tts on`).
|
||||
|
||||
Microsoft speech **is** enabled by default once TTS is on, and is used automatically
|
||||
when no OpenAI or ElevenLabs API keys are available.
|
||||
When `messages.tts.provider` is unset, OpenClaw picks the first configured
|
||||
speech provider in registry auto-select order.
|
||||
|
||||
## Config
|
||||
|
||||
@@ -93,26 +91,28 @@ Full schema is in [Gateway configuration](/gateway/configuration).
|
||||
modelOverrides: {
|
||||
enabled: true,
|
||||
},
|
||||
openai: {
|
||||
apiKey: "openai_api_key",
|
||||
baseUrl: "https://api.openai.com/v1",
|
||||
model: "gpt-4o-mini-tts",
|
||||
voice: "alloy",
|
||||
},
|
||||
elevenlabs: {
|
||||
apiKey: "elevenlabs_api_key",
|
||||
baseUrl: "https://api.elevenlabs.io",
|
||||
voiceId: "voice_id",
|
||||
modelId: "eleven_multilingual_v2",
|
||||
seed: 42,
|
||||
applyTextNormalization: "auto",
|
||||
languageCode: "en",
|
||||
voiceSettings: {
|
||||
stability: 0.5,
|
||||
similarityBoost: 0.75,
|
||||
style: 0.0,
|
||||
useSpeakerBoost: true,
|
||||
speed: 1.0,
|
||||
providers: {
|
||||
openai: {
|
||||
apiKey: "openai_api_key",
|
||||
baseUrl: "https://api.openai.com/v1",
|
||||
model: "gpt-4o-mini-tts",
|
||||
voice: "alloy",
|
||||
},
|
||||
elevenlabs: {
|
||||
apiKey: "elevenlabs_api_key",
|
||||
baseUrl: "https://api.elevenlabs.io",
|
||||
voiceId: "voice_id",
|
||||
modelId: "eleven_multilingual_v2",
|
||||
seed: 42,
|
||||
applyTextNormalization: "auto",
|
||||
languageCode: "en",
|
||||
voiceSettings: {
|
||||
stability: 0.5,
|
||||
similarityBoost: 0.75,
|
||||
style: 0.0,
|
||||
useSpeakerBoost: true,
|
||||
speed: 1.0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -128,13 +128,15 @@ Full schema is in [Gateway configuration](/gateway/configuration).
|
||||
tts: {
|
||||
auto: "always",
|
||||
provider: "microsoft",
|
||||
microsoft: {
|
||||
enabled: true,
|
||||
voice: "en-US-MichelleNeural",
|
||||
lang: "en-US",
|
||||
outputFormat: "audio-24khz-48kbitrate-mono-mp3",
|
||||
rate: "+10%",
|
||||
pitch: "-5%",
|
||||
providers: {
|
||||
microsoft: {
|
||||
enabled: true,
|
||||
voice: "en-US-MichelleNeural",
|
||||
lang: "en-US",
|
||||
outputFormat: "audio-24khz-48kbitrate-mono-mp3",
|
||||
rate: "+10%",
|
||||
pitch: "-5%",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -147,8 +149,10 @@ Full schema is in [Gateway configuration](/gateway/configuration).
|
||||
{
|
||||
messages: {
|
||||
tts: {
|
||||
microsoft: {
|
||||
enabled: false,
|
||||
providers: {
|
||||
microsoft: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -208,37 +212,37 @@ Then run:
|
||||
- `enabled`: legacy toggle (doctor migrates this to `auto`).
|
||||
- `mode`: `"final"` (default) or `"all"` (includes tool/block replies).
|
||||
- `provider`: speech provider id such as `"elevenlabs"`, `"microsoft"`, or `"openai"` (fallback is automatic).
|
||||
- If `provider` is **unset**, OpenClaw prefers `openai` (if key), then `elevenlabs` (if key),
|
||||
otherwise `microsoft`.
|
||||
- If `provider` is **unset**, OpenClaw uses the first configured speech provider in registry auto-select order.
|
||||
- Legacy `provider: "edge"` still works and is normalized to `microsoft`.
|
||||
- `summaryModel`: optional cheap model for auto-summary; defaults to `agents.defaults.model.primary`.
|
||||
- Accepts `provider/model` or a configured model alias.
|
||||
- `modelOverrides`: allow the model to emit TTS directives (on by default).
|
||||
- `allowProvider` defaults to `false` (provider switching is opt-in).
|
||||
- `providers.<id>`: provider-owned settings keyed by speech provider id.
|
||||
- `maxTextLength`: hard cap for TTS input (chars). `/tts audio` fails if exceeded.
|
||||
- `timeoutMs`: request timeout (ms).
|
||||
- `prefsPath`: override the local prefs JSON path (provider/limit/summary).
|
||||
- `apiKey` values fall back to env vars (`ELEVENLABS_API_KEY`/`XI_API_KEY`, `OPENAI_API_KEY`).
|
||||
- `elevenlabs.baseUrl`: override ElevenLabs API base URL.
|
||||
- `openai.baseUrl`: override the OpenAI TTS endpoint.
|
||||
- Resolution order: `messages.tts.openai.baseUrl` -> `OPENAI_TTS_BASE_URL` -> `https://api.openai.com/v1`
|
||||
- `providers.elevenlabs.baseUrl`: override ElevenLabs API base URL.
|
||||
- `providers.openai.baseUrl`: override the OpenAI TTS endpoint.
|
||||
- Resolution order: `messages.tts.providers.openai.baseUrl` -> `OPENAI_TTS_BASE_URL` -> `https://api.openai.com/v1`
|
||||
- Non-default values are treated as OpenAI-compatible TTS endpoints, so custom model and voice names are accepted.
|
||||
- `elevenlabs.voiceSettings`:
|
||||
- `providers.elevenlabs.voiceSettings`:
|
||||
- `stability`, `similarityBoost`, `style`: `0..1`
|
||||
- `useSpeakerBoost`: `true|false`
|
||||
- `speed`: `0.5..2.0` (1.0 = normal)
|
||||
- `elevenlabs.applyTextNormalization`: `auto|on|off`
|
||||
- `elevenlabs.languageCode`: 2-letter ISO 639-1 (e.g. `en`, `de`)
|
||||
- `elevenlabs.seed`: integer `0..4294967295` (best-effort determinism)
|
||||
- `microsoft.enabled`: allow Microsoft speech usage (default `true`; no API key).
|
||||
- `microsoft.voice`: Microsoft neural voice name (e.g. `en-US-MichelleNeural`).
|
||||
- `microsoft.lang`: language code (e.g. `en-US`).
|
||||
- `microsoft.outputFormat`: Microsoft output format (e.g. `audio-24khz-48kbitrate-mono-mp3`).
|
||||
- `providers.elevenlabs.applyTextNormalization`: `auto|on|off`
|
||||
- `providers.elevenlabs.languageCode`: 2-letter ISO 639-1 (e.g. `en`, `de`)
|
||||
- `providers.elevenlabs.seed`: integer `0..4294967295` (best-effort determinism)
|
||||
- `providers.microsoft.enabled`: allow Microsoft speech usage (default `true`; no API key).
|
||||
- `providers.microsoft.voice`: Microsoft neural voice name (e.g. `en-US-MichelleNeural`).
|
||||
- `providers.microsoft.lang`: language code (e.g. `en-US`).
|
||||
- `providers.microsoft.outputFormat`: Microsoft output format (e.g. `audio-24khz-48kbitrate-mono-mp3`).
|
||||
- See Microsoft Speech output formats for valid values; not all formats are supported by the bundled Edge-backed transport.
|
||||
- `microsoft.rate` / `microsoft.pitch` / `microsoft.volume`: percent strings (e.g. `+10%`, `-5%`).
|
||||
- `microsoft.saveSubtitles`: write JSON subtitles alongside the audio file.
|
||||
- `microsoft.proxy`: proxy URL for Microsoft speech requests.
|
||||
- `microsoft.timeoutMs`: request timeout override (ms).
|
||||
- `providers.microsoft.rate` / `providers.microsoft.pitch` / `providers.microsoft.volume`: percent strings (e.g. `+10%`, `-5%`).
|
||||
- `providers.microsoft.saveSubtitles`: write JSON subtitles alongside the audio file.
|
||||
- `providers.microsoft.proxy`: proxy URL for Microsoft speech requests.
|
||||
- `providers.microsoft.timeoutMs`: request timeout override (ms).
|
||||
- `edge.*`: legacy alias for the same Microsoft settings.
|
||||
|
||||
## Model-driven overrides (default on)
|
||||
|
||||
108
docs/tts.md
108
docs/tts.md
@@ -15,7 +15,7 @@ It works anywhere OpenClaw can send audio.
|
||||
## Supported services
|
||||
|
||||
- **ElevenLabs** (primary or fallback provider)
|
||||
- **Microsoft** (primary or fallback provider; current bundled implementation uses `node-edge-tts`, default when no API keys)
|
||||
- **Microsoft** (primary or fallback provider; current bundled implementation uses `node-edge-tts`)
|
||||
- **OpenAI** (primary or fallback provider; also used for summaries)
|
||||
|
||||
### Microsoft speech notes
|
||||
@@ -38,9 +38,7 @@ If you want OpenAI or ElevenLabs:
|
||||
- `ELEVENLABS_API_KEY` (or `XI_API_KEY`)
|
||||
- `OPENAI_API_KEY`
|
||||
|
||||
Microsoft speech does **not** require an API key. If no API keys are found,
|
||||
OpenClaw defaults to Microsoft (unless disabled via
|
||||
`messages.tts.microsoft.enabled=false` or `messages.tts.edge.enabled=false`).
|
||||
Microsoft speech does **not** require an API key.
|
||||
|
||||
If multiple providers are configured, the selected provider is used first and the others are fallback options.
|
||||
Auto-summary uses the configured `summaryModel` (or `agents.defaults.model.primary`),
|
||||
@@ -60,8 +58,8 @@ so that provider must also be authenticated if you enable summaries.
|
||||
No. Auto‑TTS is **off** by default. Enable it in config with
|
||||
`messages.tts.auto` or per session with `/tts always` (alias: `/tts on`).
|
||||
|
||||
Microsoft speech **is** enabled by default once TTS is on, and is used automatically
|
||||
when no OpenAI or ElevenLabs API keys are available.
|
||||
When `messages.tts.provider` is unset, OpenClaw picks the first configured
|
||||
speech provider in registry auto-select order.
|
||||
|
||||
## Config
|
||||
|
||||
@@ -93,26 +91,28 @@ Full schema is in [Gateway configuration](/gateway/configuration).
|
||||
modelOverrides: {
|
||||
enabled: true,
|
||||
},
|
||||
openai: {
|
||||
apiKey: "openai_api_key",
|
||||
baseUrl: "https://api.openai.com/v1",
|
||||
model: "gpt-4o-mini-tts",
|
||||
voice: "alloy",
|
||||
},
|
||||
elevenlabs: {
|
||||
apiKey: "elevenlabs_api_key",
|
||||
baseUrl: "https://api.elevenlabs.io",
|
||||
voiceId: "voice_id",
|
||||
modelId: "eleven_multilingual_v2",
|
||||
seed: 42,
|
||||
applyTextNormalization: "auto",
|
||||
languageCode: "en",
|
||||
voiceSettings: {
|
||||
stability: 0.5,
|
||||
similarityBoost: 0.75,
|
||||
style: 0.0,
|
||||
useSpeakerBoost: true,
|
||||
speed: 1.0,
|
||||
providers: {
|
||||
openai: {
|
||||
apiKey: "openai_api_key",
|
||||
baseUrl: "https://api.openai.com/v1",
|
||||
model: "gpt-4o-mini-tts",
|
||||
voice: "alloy",
|
||||
},
|
||||
elevenlabs: {
|
||||
apiKey: "elevenlabs_api_key",
|
||||
baseUrl: "https://api.elevenlabs.io",
|
||||
voiceId: "voice_id",
|
||||
modelId: "eleven_multilingual_v2",
|
||||
seed: 42,
|
||||
applyTextNormalization: "auto",
|
||||
languageCode: "en",
|
||||
voiceSettings: {
|
||||
stability: 0.5,
|
||||
similarityBoost: 0.75,
|
||||
style: 0.0,
|
||||
useSpeakerBoost: true,
|
||||
speed: 1.0,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -128,13 +128,15 @@ Full schema is in [Gateway configuration](/gateway/configuration).
|
||||
tts: {
|
||||
auto: "always",
|
||||
provider: "microsoft",
|
||||
microsoft: {
|
||||
enabled: true,
|
||||
voice: "en-US-MichelleNeural",
|
||||
lang: "en-US",
|
||||
outputFormat: "audio-24khz-48kbitrate-mono-mp3",
|
||||
rate: "+10%",
|
||||
pitch: "-5%",
|
||||
providers: {
|
||||
microsoft: {
|
||||
enabled: true,
|
||||
voice: "en-US-MichelleNeural",
|
||||
lang: "en-US",
|
||||
outputFormat: "audio-24khz-48kbitrate-mono-mp3",
|
||||
rate: "+10%",
|
||||
pitch: "-5%",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -147,8 +149,10 @@ Full schema is in [Gateway configuration](/gateway/configuration).
|
||||
{
|
||||
messages: {
|
||||
tts: {
|
||||
microsoft: {
|
||||
enabled: false,
|
||||
providers: {
|
||||
microsoft: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -208,37 +212,37 @@ Then run:
|
||||
- `enabled`: legacy toggle (doctor migrates this to `auto`).
|
||||
- `mode`: `"final"` (default) or `"all"` (includes tool/block replies).
|
||||
- `provider`: speech provider id such as `"elevenlabs"`, `"microsoft"`, or `"openai"` (fallback is automatic).
|
||||
- If `provider` is **unset**, OpenClaw prefers `openai` (if key), then `elevenlabs` (if key),
|
||||
otherwise `microsoft`.
|
||||
- If `provider` is **unset**, OpenClaw uses the first configured speech provider in registry auto-select order.
|
||||
- Legacy `provider: "edge"` still works and is normalized to `microsoft`.
|
||||
- `summaryModel`: optional cheap model for auto-summary; defaults to `agents.defaults.model.primary`.
|
||||
- Accepts `provider/model` or a configured model alias.
|
||||
- `modelOverrides`: allow the model to emit TTS directives (on by default).
|
||||
- `allowProvider` defaults to `false` (provider switching is opt-in).
|
||||
- `providers.<id>`: provider-owned settings keyed by speech provider id.
|
||||
- `maxTextLength`: hard cap for TTS input (chars). `/tts audio` fails if exceeded.
|
||||
- `timeoutMs`: request timeout (ms).
|
||||
- `prefsPath`: override the local prefs JSON path (provider/limit/summary).
|
||||
- `apiKey` values fall back to env vars (`ELEVENLABS_API_KEY`/`XI_API_KEY`, `OPENAI_API_KEY`).
|
||||
- `elevenlabs.baseUrl`: override ElevenLabs API base URL.
|
||||
- `openai.baseUrl`: override the OpenAI TTS endpoint.
|
||||
- Resolution order: `messages.tts.openai.baseUrl` -> `OPENAI_TTS_BASE_URL` -> `https://api.openai.com/v1`
|
||||
- `providers.elevenlabs.baseUrl`: override ElevenLabs API base URL.
|
||||
- `providers.openai.baseUrl`: override the OpenAI TTS endpoint.
|
||||
- Resolution order: `messages.tts.providers.openai.baseUrl` -> `OPENAI_TTS_BASE_URL` -> `https://api.openai.com/v1`
|
||||
- Non-default values are treated as OpenAI-compatible TTS endpoints, so custom model and voice names are accepted.
|
||||
- `elevenlabs.voiceSettings`:
|
||||
- `providers.elevenlabs.voiceSettings`:
|
||||
- `stability`, `similarityBoost`, `style`: `0..1`
|
||||
- `useSpeakerBoost`: `true|false`
|
||||
- `speed`: `0.5..2.0` (1.0 = normal)
|
||||
- `elevenlabs.applyTextNormalization`: `auto|on|off`
|
||||
- `elevenlabs.languageCode`: 2-letter ISO 639-1 (e.g. `en`, `de`)
|
||||
- `elevenlabs.seed`: integer `0..4294967295` (best-effort determinism)
|
||||
- `microsoft.enabled`: allow Microsoft speech usage (default `true`; no API key).
|
||||
- `microsoft.voice`: Microsoft neural voice name (e.g. `en-US-MichelleNeural`).
|
||||
- `microsoft.lang`: language code (e.g. `en-US`).
|
||||
- `microsoft.outputFormat`: Microsoft output format (e.g. `audio-24khz-48kbitrate-mono-mp3`).
|
||||
- `providers.elevenlabs.applyTextNormalization`: `auto|on|off`
|
||||
- `providers.elevenlabs.languageCode`: 2-letter ISO 639-1 (e.g. `en`, `de`)
|
||||
- `providers.elevenlabs.seed`: integer `0..4294967295` (best-effort determinism)
|
||||
- `providers.microsoft.enabled`: allow Microsoft speech usage (default `true`; no API key).
|
||||
- `providers.microsoft.voice`: Microsoft neural voice name (e.g. `en-US-MichelleNeural`).
|
||||
- `providers.microsoft.lang`: language code (e.g. `en-US`).
|
||||
- `providers.microsoft.outputFormat`: Microsoft output format (e.g. `audio-24khz-48kbitrate-mono-mp3`).
|
||||
- See Microsoft Speech output formats for valid values; not all formats are supported by the bundled Edge-backed transport.
|
||||
- `microsoft.rate` / `microsoft.pitch` / `microsoft.volume`: percent strings (e.g. `+10%`, `-5%`).
|
||||
- `microsoft.saveSubtitles`: write JSON subtitles alongside the audio file.
|
||||
- `microsoft.proxy`: proxy URL for Microsoft speech requests.
|
||||
- `microsoft.timeoutMs`: request timeout override (ms).
|
||||
- `providers.microsoft.rate` / `providers.microsoft.pitch` / `providers.microsoft.volume`: percent strings (e.g. `+10%`, `-5%`).
|
||||
- `providers.microsoft.saveSubtitles`: write JSON subtitles alongside the audio file.
|
||||
- `providers.microsoft.proxy`: proxy URL for Microsoft speech requests.
|
||||
- `providers.microsoft.timeoutMs`: request timeout override (ms).
|
||||
- `edge.*`: legacy alias for the same Microsoft settings.
|
||||
|
||||
## Model-driven overrides (default on)
|
||||
|
||||
@@ -136,7 +136,7 @@ OpenClaw 有两个钩子系统:
|
||||
## 超时
|
||||
|
||||
- `agent.wait` 默认:30 秒(仅等待)。`timeoutMs` 参数可覆盖。
|
||||
- 智能体运行时:`agents.defaults.timeoutSeconds` 默认 600 秒;在 `runEmbeddedPiAgent` 中止计时器中强制执行。
|
||||
- 智能体运行时:`agents.defaults.timeoutSeconds` 默认 172800 秒(48 小时);在 `runEmbeddedPiAgent` 中止计时器中强制执行。使用 `0` 可完全禁用超时。
|
||||
|
||||
## 可能提前结束的情况
|
||||
|
||||
|
||||
@@ -8,13 +8,16 @@
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"command": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"expectedVersion": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"cwd": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"permissionMode": {
|
||||
"type": "string",
|
||||
@@ -42,6 +45,7 @@
|
||||
"properties": {
|
||||
"command": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"description": "Command to run the MCP server"
|
||||
},
|
||||
"args": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/acpx",
|
||||
"version": "2026.3.22",
|
||||
"version": "2026.3.26",
|
||||
"description": "OpenClaw ACP runtime backend via acpx",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
---
|
||||
name: acp-router
|
||||
description: Route plain-language requests for Pi, Claude Code, Codex, OpenCode, Gemini CLI, or ACP harness work into either OpenClaw ACP runtime sessions or direct acpx-driven sessions ("telephone game" flow). For coding-agent thread requests, read this skill first, then use only `sessions_spawn` for thread creation.
|
||||
description: Route plain-language requests for Pi, Claude Code, Codex, Cursor, Copilot, OpenClaw ACP, OpenCode, Gemini CLI, Qwen, Kiro, Kimi, iFlow, Factory Droid, Kilocode, or ACP harness work into either OpenClaw ACP runtime sessions or direct acpx-driven sessions ("telephone game" flow). For coding-agent thread requests, read this skill first, then use only `sessions_spawn` for thread creation.
|
||||
user-invocable: false
|
||||
---
|
||||
|
||||
# ACP Harness Router
|
||||
|
||||
When user intent is "run this in Pi/Claude Code/Codex/OpenCode/Gemini/Kimi (ACP harness)", do not use subagent runtime or PTY scraping. Route through ACP-aware flows.
|
||||
When user intent is "run this in Pi/Claude Code/Codex/Cursor/Copilot/OpenClaw/OpenCode/Gemini/Qwen/Kiro/Kimi/iFlow/Droid/Kilocode (ACP harness)", do not use subagent runtime or PTY scraping. Route through ACP-aware flows.
|
||||
|
||||
## Intent detection
|
||||
|
||||
Trigger this skill when the user asks OpenClaw to:
|
||||
|
||||
- run something in Pi / Claude Code / Codex / OpenCode / Gemini
|
||||
- run something in Pi / Claude Code / Codex / Cursor / Copilot / OpenClaw / OpenCode / Gemini / Qwen / Kiro / Kimi / iFlow / Droid / Kilocode
|
||||
- continue existing harness work
|
||||
- relay instructions to an external coding harness
|
||||
- keep an external harness conversation in a thread-like conversation
|
||||
|
||||
Mandatory preflight for coding-agent thread requests:
|
||||
|
||||
- Before creating any thread for Pi/Claude/Codex/OpenCode/Gemini work, read this skill first in the same turn.
|
||||
- Before creating any thread for ACP harness work, read this skill first in the same turn.
|
||||
- After reading, follow `OpenClaw ACP runtime path` below; do not use `message(action="thread-create")` for ACP harness thread spawn.
|
||||
|
||||
## Mode selection
|
||||
@@ -39,18 +39,26 @@ Do not use:
|
||||
|
||||
- `subagents` runtime for harness control
|
||||
- `/acp` command delegation as a requirement for the user
|
||||
- PTY scraping of pi/claude/codex/opencode/gemini/kimi CLIs when `acpx` is available
|
||||
- PTY scraping of supported ACP harness CLIs when `acpx` is available
|
||||
|
||||
## AgentId mapping
|
||||
|
||||
Use these defaults when user names a harness directly:
|
||||
|
||||
- "pi" -> `agentId: "pi"`
|
||||
- "openclaw" -> `agentId: "openclaw"`
|
||||
- "claude" or "claude code" -> `agentId: "claude"`
|
||||
- "codex" -> `agentId: "codex"`
|
||||
- "copilot" or "github copilot" -> `agentId: "copilot"`
|
||||
- "cursor" or "cursor cli" -> `agentId: "cursor"`
|
||||
- "droid" or "factory droid" -> `agentId: "droid"`
|
||||
- "opencode" -> `agentId: "opencode"`
|
||||
- "gemini" or "gemini cli" -> `agentId: "gemini"`
|
||||
- "iflow" -> `agentId: "iflow"`
|
||||
- "kilocode" -> `agentId: "kilocode"`
|
||||
- "kimi" or "kimi cli" -> `agentId: "kimi"`
|
||||
- "kiro" or "kiro cli" -> `agentId: "kiro"`
|
||||
- "qwen" or "qwen code" -> `agentId: "qwen"`
|
||||
|
||||
These defaults match current acpx built-in aliases.
|
||||
|
||||
@@ -88,7 +96,7 @@ Call:
|
||||
|
||||
## Thread spawn recovery policy
|
||||
|
||||
When the user asks to start a coding harness in a thread (for example "start a codex/claude/pi/kimi thread"), treat that as an ACP runtime request and try to satisfy it end-to-end.
|
||||
When the user asks to start a coding harness in a thread, treat that as an ACP runtime request and try to satisfy it end-to-end.
|
||||
|
||||
Required behavior when ACP backend is unavailable:
|
||||
|
||||
@@ -179,25 +187,42 @@ ${ACPX_CMD} codex sessions close oc-codex-<conversationId>
|
||||
|
||||
### Harness aliases in acpx
|
||||
|
||||
- `pi`
|
||||
- `claude`
|
||||
- `codex`
|
||||
- `opencode`
|
||||
- `copilot`
|
||||
- `cursor`
|
||||
- `droid`
|
||||
- `gemini`
|
||||
- `iflow`
|
||||
- `kilocode`
|
||||
- `kimi`
|
||||
- `kiro`
|
||||
- `openclaw`
|
||||
- `opencode`
|
||||
- `pi`
|
||||
- `qwen`
|
||||
|
||||
### Built-in adapter commands in acpx
|
||||
|
||||
Defaults are:
|
||||
|
||||
- `pi -> npx pi-acp`
|
||||
- `claude -> npx -y @zed-industries/claude-agent-acp`
|
||||
- `codex -> npx @zed-industries/codex-acp`
|
||||
- `opencode -> npx -y opencode-ai acp`
|
||||
- `gemini -> gemini`
|
||||
- `openclaw -> openclaw acp`
|
||||
- `claude -> npx -y @zed-industries/claude-agent-acp@0.21.0`
|
||||
- `codex -> npx @zed-industries/codex-acp@^0.9.5`
|
||||
- `copilot -> copilot --acp --stdio`
|
||||
- `cursor -> cursor-agent acp`
|
||||
- `droid -> droid exec --output-format acp`
|
||||
- `gemini -> gemini --acp`
|
||||
- `iflow -> iflow --experimental-acp`
|
||||
- `kilocode -> npx -y @kilocode/cli acp`
|
||||
- `kimi -> kimi acp`
|
||||
- `kiro -> kiro-cli acp`
|
||||
- `opencode -> npx -y opencode-ai acp`
|
||||
- `pi -> npx pi-acp@^0.0.22`
|
||||
- `qwen -> qwen --acp`
|
||||
|
||||
If `~/.acpx/config.json` overrides `agents`, those overrides replace defaults.
|
||||
If your local Cursor install still exposes ACP as `agent acp`, set that as the `cursor` agent override explicitly.
|
||||
|
||||
### Failure handling
|
||||
|
||||
|
||||
@@ -187,4 +187,12 @@ describe("acpx plugin config parsing", () => {
|
||||
}),
|
||||
).toThrow("strictWindowsCmdWrapper must be a boolean");
|
||||
});
|
||||
|
||||
it("keeps the runtime json schema in sync with the manifest config schema", () => {
|
||||
const manifest = JSON.parse(
|
||||
fs.readFileSync(new URL("../openclaw.plugin.json", import.meta.url), "utf8"),
|
||||
) as { configSchema?: unknown };
|
||||
|
||||
expect(createAcpxPluginConfigSchema().jsonSchema).toEqual(manifest.configSchema);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { buildPluginConfigSchema } from "openclaw/plugin-sdk/core";
|
||||
import { z } from "openclaw/plugin-sdk/zod";
|
||||
import type { OpenClawPluginConfigSchema } from "../runtime-api.js";
|
||||
|
||||
export const ACPX_PERMISSION_MODES = ["approve-all", "approve-reads", "deny-all"] as const;
|
||||
@@ -111,169 +113,79 @@ type ParseResult =
|
||||
| { ok: true; value: AcpxPluginConfig | undefined }
|
||||
| { ok: false; message: string };
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null && !Array.isArray(value);
|
||||
}
|
||||
const nonEmptyTrimmedString = (message: string) =>
|
||||
z.string({ error: message }).trim().min(1, { error: message });
|
||||
|
||||
function isPermissionMode(value: string): value is AcpxPermissionMode {
|
||||
return ACPX_PERMISSION_MODES.includes(value as AcpxPermissionMode);
|
||||
}
|
||||
const McpServerConfigSchema = z.object({
|
||||
command: nonEmptyTrimmedString("command must be a non-empty string").describe(
|
||||
"Command to run the MCP server",
|
||||
),
|
||||
args: z
|
||||
.array(z.string({ error: "args must be an array of strings" }), {
|
||||
error: "args must be an array of strings",
|
||||
})
|
||||
.optional()
|
||||
.describe("Arguments to pass to the command"),
|
||||
env: z
|
||||
.record(z.string(), z.string({ error: "env values must be strings" }), {
|
||||
error: "env must be an object of strings",
|
||||
})
|
||||
.optional()
|
||||
.describe("Environment variables for the MCP server"),
|
||||
});
|
||||
|
||||
function isNonInteractivePermissionPolicy(
|
||||
value: string,
|
||||
): value is AcpxNonInteractivePermissionPolicy {
|
||||
return ACPX_NON_INTERACTIVE_POLICIES.includes(value as AcpxNonInteractivePermissionPolicy);
|
||||
}
|
||||
const AcpxPluginConfigSchema = z.strictObject({
|
||||
command: nonEmptyTrimmedString("command must be a non-empty string").optional(),
|
||||
expectedVersion: nonEmptyTrimmedString("expectedVersion must be a non-empty string").optional(),
|
||||
cwd: nonEmptyTrimmedString("cwd must be a non-empty string").optional(),
|
||||
permissionMode: z
|
||||
.enum(ACPX_PERMISSION_MODES, {
|
||||
error: `permissionMode must be one of: ${ACPX_PERMISSION_MODES.join(", ")}`,
|
||||
})
|
||||
.optional(),
|
||||
nonInteractivePermissions: z
|
||||
.enum(ACPX_NON_INTERACTIVE_POLICIES, {
|
||||
error: `nonInteractivePermissions must be one of: ${ACPX_NON_INTERACTIVE_POLICIES.join(", ")}`,
|
||||
})
|
||||
.optional(),
|
||||
strictWindowsCmdWrapper: z
|
||||
.boolean({ error: "strictWindowsCmdWrapper must be a boolean" })
|
||||
.optional(),
|
||||
timeoutSeconds: z
|
||||
.number({ error: "timeoutSeconds must be a number >= 0.001" })
|
||||
.min(0.001, { error: "timeoutSeconds must be a number >= 0.001" })
|
||||
.optional(),
|
||||
queueOwnerTtlSeconds: z
|
||||
.number({ error: "queueOwnerTtlSeconds must be a number >= 0" })
|
||||
.min(0, { error: "queueOwnerTtlSeconds must be a number >= 0" })
|
||||
.optional(),
|
||||
mcpServers: z.record(z.string(), McpServerConfigSchema).optional(),
|
||||
});
|
||||
|
||||
function isMcpServerConfig(value: unknown): value is McpServerConfig {
|
||||
if (!isRecord(value)) {
|
||||
return false;
|
||||
function formatAcpxConfigIssue(issue: z.ZodIssue | undefined): string {
|
||||
if (!issue) {
|
||||
return "invalid config";
|
||||
}
|
||||
if (typeof value.command !== "string" || value.command.trim() === "") {
|
||||
return false;
|
||||
if (issue.code === "unrecognized_keys" && issue.keys.length > 0) {
|
||||
return `unknown config key: ${issue.keys[0]}`;
|
||||
}
|
||||
if (value.args !== undefined) {
|
||||
if (!Array.isArray(value.args)) {
|
||||
return false;
|
||||
}
|
||||
for (const arg of value.args) {
|
||||
if (typeof arg !== "string") {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (issue.code === "invalid_type" && issue.path.length === 0) {
|
||||
return "expected config object";
|
||||
}
|
||||
if (value.env !== undefined) {
|
||||
if (!isRecord(value.env)) {
|
||||
return false;
|
||||
}
|
||||
for (const envValue of Object.values(value.env)) {
|
||||
if (typeof envValue !== "string") {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return issue.message;
|
||||
}
|
||||
|
||||
function parseAcpxPluginConfig(value: unknown): ParseResult {
|
||||
if (value === undefined) {
|
||||
return { ok: true, value: undefined };
|
||||
}
|
||||
if (!isRecord(value)) {
|
||||
return { ok: false, message: "expected config object" };
|
||||
const parsed = AcpxPluginConfigSchema.safeParse(value);
|
||||
if (!parsed.success) {
|
||||
return { ok: false, message: formatAcpxConfigIssue(parsed.error.issues[0]) };
|
||||
}
|
||||
const allowedKeys = new Set([
|
||||
"command",
|
||||
"expectedVersion",
|
||||
"cwd",
|
||||
"permissionMode",
|
||||
"nonInteractivePermissions",
|
||||
"strictWindowsCmdWrapper",
|
||||
"timeoutSeconds",
|
||||
"queueOwnerTtlSeconds",
|
||||
"mcpServers",
|
||||
]);
|
||||
for (const key of Object.keys(value)) {
|
||||
if (!allowedKeys.has(key)) {
|
||||
return { ok: false, message: `unknown config key: ${key}` };
|
||||
}
|
||||
}
|
||||
|
||||
const command = value.command;
|
||||
if (command !== undefined && (typeof command !== "string" || command.trim() === "")) {
|
||||
return { ok: false, message: "command must be a non-empty string" };
|
||||
}
|
||||
|
||||
const expectedVersion = value.expectedVersion;
|
||||
if (
|
||||
expectedVersion !== undefined &&
|
||||
(typeof expectedVersion !== "string" || expectedVersion.trim() === "")
|
||||
) {
|
||||
return { ok: false, message: "expectedVersion must be a non-empty string" };
|
||||
}
|
||||
|
||||
const cwd = value.cwd;
|
||||
if (cwd !== undefined && (typeof cwd !== "string" || cwd.trim() === "")) {
|
||||
return { ok: false, message: "cwd must be a non-empty string" };
|
||||
}
|
||||
|
||||
const permissionMode = value.permissionMode;
|
||||
if (
|
||||
permissionMode !== undefined &&
|
||||
(typeof permissionMode !== "string" || !isPermissionMode(permissionMode))
|
||||
) {
|
||||
return {
|
||||
ok: false,
|
||||
message: `permissionMode must be one of: ${ACPX_PERMISSION_MODES.join(", ")}`,
|
||||
};
|
||||
}
|
||||
|
||||
const nonInteractivePermissions = value.nonInteractivePermissions;
|
||||
if (
|
||||
nonInteractivePermissions !== undefined &&
|
||||
(typeof nonInteractivePermissions !== "string" ||
|
||||
!isNonInteractivePermissionPolicy(nonInteractivePermissions))
|
||||
) {
|
||||
return {
|
||||
ok: false,
|
||||
message: `nonInteractivePermissions must be one of: ${ACPX_NON_INTERACTIVE_POLICIES.join(", ")}`,
|
||||
};
|
||||
}
|
||||
|
||||
const timeoutSeconds = value.timeoutSeconds;
|
||||
if (
|
||||
timeoutSeconds !== undefined &&
|
||||
(typeof timeoutSeconds !== "number" || !Number.isFinite(timeoutSeconds) || timeoutSeconds <= 0)
|
||||
) {
|
||||
return { ok: false, message: "timeoutSeconds must be a positive number" };
|
||||
}
|
||||
|
||||
const strictWindowsCmdWrapper = value.strictWindowsCmdWrapper;
|
||||
if (strictWindowsCmdWrapper !== undefined && typeof strictWindowsCmdWrapper !== "boolean") {
|
||||
return { ok: false, message: "strictWindowsCmdWrapper must be a boolean" };
|
||||
}
|
||||
|
||||
const queueOwnerTtlSeconds = value.queueOwnerTtlSeconds;
|
||||
if (
|
||||
queueOwnerTtlSeconds !== undefined &&
|
||||
(typeof queueOwnerTtlSeconds !== "number" ||
|
||||
!Number.isFinite(queueOwnerTtlSeconds) ||
|
||||
queueOwnerTtlSeconds < 0)
|
||||
) {
|
||||
return { ok: false, message: "queueOwnerTtlSeconds must be a non-negative number" };
|
||||
}
|
||||
|
||||
const mcpServers = value.mcpServers;
|
||||
if (mcpServers !== undefined) {
|
||||
if (!isRecord(mcpServers)) {
|
||||
return { ok: false, message: "mcpServers must be an object" };
|
||||
}
|
||||
for (const [key, serverConfig] of Object.entries(mcpServers)) {
|
||||
if (!isMcpServerConfig(serverConfig)) {
|
||||
return {
|
||||
ok: false,
|
||||
message: `mcpServers.${key} must have a command string, optional args array, and optional env object`,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
value: {
|
||||
command: typeof command === "string" ? command.trim() : undefined,
|
||||
expectedVersion: typeof expectedVersion === "string" ? expectedVersion.trim() : undefined,
|
||||
cwd: typeof cwd === "string" ? cwd.trim() : undefined,
|
||||
permissionMode: typeof permissionMode === "string" ? permissionMode : undefined,
|
||||
nonInteractivePermissions:
|
||||
typeof nonInteractivePermissions === "string" ? nonInteractivePermissions : undefined,
|
||||
strictWindowsCmdWrapper:
|
||||
typeof strictWindowsCmdWrapper === "boolean" ? strictWindowsCmdWrapper : undefined,
|
||||
timeoutSeconds: typeof timeoutSeconds === "number" ? timeoutSeconds : undefined,
|
||||
queueOwnerTtlSeconds:
|
||||
typeof queueOwnerTtlSeconds === "number" ? queueOwnerTtlSeconds : undefined,
|
||||
mcpServers: mcpServers as Record<string, McpServerConfig> | undefined,
|
||||
},
|
||||
value: parsed.data as AcpxPluginConfig,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -290,63 +202,7 @@ function resolveConfiguredCommand(params: { configured?: string; workspaceDir?:
|
||||
}
|
||||
|
||||
export function createAcpxPluginConfigSchema(): OpenClawPluginConfigSchema {
|
||||
return {
|
||||
safeParse(value: unknown):
|
||||
| { success: true; data?: unknown }
|
||||
| {
|
||||
success: false;
|
||||
error: { issues: Array<{ path: Array<string | number>; message: string }> };
|
||||
} {
|
||||
const parsed = parseAcpxPluginConfig(value);
|
||||
if (parsed.ok) {
|
||||
return { success: true, data: parsed.value };
|
||||
}
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
issues: [{ path: [], message: parsed.message }],
|
||||
},
|
||||
};
|
||||
},
|
||||
jsonSchema: {
|
||||
type: "object",
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
command: { type: "string" },
|
||||
expectedVersion: { type: "string" },
|
||||
cwd: { type: "string" },
|
||||
permissionMode: {
|
||||
type: "string",
|
||||
enum: [...ACPX_PERMISSION_MODES],
|
||||
},
|
||||
nonInteractivePermissions: {
|
||||
type: "string",
|
||||
enum: [...ACPX_NON_INTERACTIVE_POLICIES],
|
||||
},
|
||||
strictWindowsCmdWrapper: { type: "boolean" },
|
||||
timeoutSeconds: { type: "number", minimum: 0.001 },
|
||||
queueOwnerTtlSeconds: { type: "number", minimum: 0 },
|
||||
mcpServers: {
|
||||
type: "object",
|
||||
additionalProperties: {
|
||||
type: "object",
|
||||
properties: {
|
||||
command: { type: "string" },
|
||||
args: {
|
||||
type: "array",
|
||||
items: { type: "string" },
|
||||
},
|
||||
env: {
|
||||
type: "object",
|
||||
additionalProperties: { type: "string" },
|
||||
},
|
||||
},
|
||||
required: ["command"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
return buildPluginConfigSchema(AcpxPluginConfigSchema);
|
||||
}
|
||||
|
||||
export function toAcpMcpServers(mcpServers: Record<string, McpServerConfig>): AcpxMcpServer[] {
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { safeParseJsonWithSchema } from "openclaw/plugin-sdk/extension-shared";
|
||||
import { z } from "zod";
|
||||
import type { AcpRuntimeEvent, AcpSessionUpdateTag } from "../../runtime-api.js";
|
||||
import {
|
||||
asOptionalBoolean,
|
||||
@@ -9,18 +11,18 @@ import {
|
||||
isRecord,
|
||||
} from "./shared.js";
|
||||
|
||||
const AcpxJsonObjectSchema = z.record(z.string(), z.unknown());
|
||||
|
||||
const AcpxErrorEventSchema = z.object({
|
||||
type: z.literal("error"),
|
||||
message: z.string().trim().min(1).catch("acpx reported an error"),
|
||||
code: z.string().optional(),
|
||||
retryable: z.boolean().optional(),
|
||||
});
|
||||
|
||||
export function toAcpxErrorEvent(value: unknown): AcpxErrorEvent | null {
|
||||
if (!isRecord(value)) {
|
||||
return null;
|
||||
}
|
||||
if (asTrimmedString(value.type) !== "error") {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
message: asTrimmedString(value.message) || "acpx reported an error",
|
||||
code: asOptionalString(value.code),
|
||||
retryable: asOptionalBoolean(value.retryable),
|
||||
};
|
||||
const parsed = AcpxErrorEventSchema.safeParse(value);
|
||||
return parsed.success ? parsed.data : null;
|
||||
}
|
||||
|
||||
export function parseJsonLines(value: string): AcpxJsonObject[] {
|
||||
@@ -30,13 +32,9 @@ export function parseJsonLines(value: string): AcpxJsonObject[] {
|
||||
if (!trimmed) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
const parsed = JSON.parse(trimmed) as unknown;
|
||||
if (isRecord(parsed)) {
|
||||
events.push(parsed);
|
||||
}
|
||||
} catch {
|
||||
// Ignore malformed lines; callers handle missing typed events via exit code.
|
||||
const parsed = safeParseJsonWithSchema(AcpxJsonObjectSchema, trimmed);
|
||||
if (parsed) {
|
||||
events.push(parsed);
|
||||
}
|
||||
}
|
||||
return events;
|
||||
@@ -200,20 +198,14 @@ export function parsePromptEventLine(line: string): AcpRuntimeEvent | null {
|
||||
if (!trimmed) {
|
||||
return null;
|
||||
}
|
||||
let parsed: unknown;
|
||||
try {
|
||||
parsed = JSON.parse(trimmed);
|
||||
} catch {
|
||||
const parsed = safeParseJsonWithSchema(AcpxJsonObjectSchema, trimmed);
|
||||
if (!parsed) {
|
||||
return {
|
||||
type: "status",
|
||||
text: trimmed,
|
||||
};
|
||||
}
|
||||
|
||||
if (!isRecord(parsed)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const structured = resolveStructuredPromptPayload(parsed);
|
||||
const type = structured.type;
|
||||
const payload = structured.payload;
|
||||
|
||||
@@ -11,6 +11,48 @@ vi.mock("./process.js", () => ({
|
||||
import { __testing, resolveAcpxAgentCommand } from "./mcp-agent-command.js";
|
||||
|
||||
describe("resolveAcpxAgentCommand", () => {
|
||||
it.each([
|
||||
["cursor", "cursor-agent acp"],
|
||||
["gemini", "gemini --acp"],
|
||||
["openclaw", "openclaw acp"],
|
||||
["copilot", "copilot --acp --stdio"],
|
||||
["pi", "npx -y pi-acp@0.0.22"],
|
||||
["codex", "npx -y @zed-industries/codex-acp@0.9.5"],
|
||||
["claude", "npx -y @zed-industries/claude-agent-acp@0.21.0"],
|
||||
])("uses the current acpx built-in for %s by default", async (agent, expected) => {
|
||||
spawnAndCollectMock.mockResolvedValueOnce({
|
||||
stdout: JSON.stringify({ agents: {} }),
|
||||
stderr: "",
|
||||
code: 0,
|
||||
error: null,
|
||||
});
|
||||
|
||||
const command = await resolveAcpxAgentCommand({
|
||||
acpxCommand: "/plugin/node_modules/.bin/acpx",
|
||||
cwd: "/plugin",
|
||||
agent,
|
||||
});
|
||||
|
||||
expect(command).toBe(expected);
|
||||
});
|
||||
|
||||
it("returns null for unknown agent ids instead of falling back to raw commands", async () => {
|
||||
spawnAndCollectMock.mockResolvedValueOnce({
|
||||
stdout: JSON.stringify({ agents: {} }),
|
||||
stderr: "",
|
||||
code: 0,
|
||||
error: null,
|
||||
});
|
||||
|
||||
const command = await resolveAcpxAgentCommand({
|
||||
acpxCommand: "/plugin/node_modules/.bin/acpx",
|
||||
cwd: "/plugin",
|
||||
agent: "sh -c whoami",
|
||||
});
|
||||
|
||||
expect(command).toBeNull();
|
||||
});
|
||||
|
||||
it("threads stripProviderAuthEnvVars through the config show probe", async () => {
|
||||
spawnAndCollectMock.mockResolvedValueOnce({
|
||||
stdout: JSON.stringify({
|
||||
|
||||
@@ -2,12 +2,22 @@ import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { spawnAndCollect, type SpawnCommandOptions } from "./process.js";
|
||||
|
||||
// Keep this mirror aligned with openclaw/acpx src/agent-registry.ts built-ins.
|
||||
const ACPX_BUILTIN_AGENT_COMMANDS: Record<string, string> = {
|
||||
codex: "npx @zed-industries/codex-acp",
|
||||
claude: "npx -y @zed-industries/claude-agent-acp",
|
||||
gemini: "gemini",
|
||||
pi: "npx -y pi-acp@0.0.22",
|
||||
openclaw: "openclaw acp",
|
||||
codex: "npx -y @zed-industries/codex-acp@0.9.5",
|
||||
claude: "npx -y @zed-industries/claude-agent-acp@0.21.0",
|
||||
gemini: "gemini --acp",
|
||||
cursor: "cursor-agent acp",
|
||||
copilot: "copilot --acp --stdio",
|
||||
droid: "droid exec --output-format acp",
|
||||
iflow: "iflow --experimental-acp",
|
||||
kilocode: "npx -y @kilocode/cli acp",
|
||||
kimi: "kimi acp",
|
||||
kiro: "kiro-cli acp",
|
||||
opencode: "npx -y opencode-ai acp",
|
||||
pi: "npx pi-acp",
|
||||
qwen: "qwen --acp",
|
||||
};
|
||||
|
||||
const MCP_PROXY_PATH = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "mcp-proxy.mjs");
|
||||
@@ -95,7 +105,7 @@ export async function resolveAcpxAgentCommand(params: {
|
||||
agent: string;
|
||||
stripProviderAuthEnvVars?: boolean;
|
||||
spawnOptions?: SpawnCommandOptions;
|
||||
}): Promise<string> {
|
||||
}): Promise<string | null> {
|
||||
const normalizedAgent = normalizeAgentName(params.agent);
|
||||
const overrides = await loadAgentOverrides({
|
||||
acpxCommand: params.acpxCommand,
|
||||
@@ -103,7 +113,7 @@ export async function resolveAcpxAgentCommand(params: {
|
||||
stripProviderAuthEnvVars: params.stripProviderAuthEnvVars,
|
||||
spawnOptions: params.spawnOptions,
|
||||
});
|
||||
return overrides[normalizedAgent] ?? ACPX_BUILTIN_AGENT_COMMANDS[normalizedAgent] ?? params.agent;
|
||||
return overrides[normalizedAgent] ?? ACPX_BUILTIN_AGENT_COMMANDS[normalizedAgent] ?? null;
|
||||
}
|
||||
|
||||
export function buildMcpProxyAgentCommand(params: {
|
||||
|
||||
@@ -551,6 +551,31 @@ describe("AcpxRuntime", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("does not pass unknown agent ids through acpx --agent when MCP servers are configured", async () => {
|
||||
const { runtime, logPath } = await createMockRuntimeFixture({
|
||||
mcpServers: {
|
||||
canva: {
|
||||
command: "npx",
|
||||
args: ["-y", "mcp-remote@latest", "https://mcp.canva.com/mcp"],
|
||||
env: {
|
||||
CANVA_TOKEN: "secret", // pragma: allowlist secret
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await runtime.ensureSession({
|
||||
sessionKey: "agent:sh:acp:mcp",
|
||||
agent: "sh -c whoami",
|
||||
mode: "persistent",
|
||||
});
|
||||
|
||||
const logs = await readMockRuntimeLogEntries(logPath);
|
||||
const ensureArgs = (logs.find((entry) => entry.kind === "ensure")?.args as string[]) ?? [];
|
||||
expect(ensureArgs).not.toContain("--agent");
|
||||
expect(ensureArgs).toContain("sh -c whoami");
|
||||
});
|
||||
|
||||
it("skips prompt execution when runTurn starts with an already-aborted signal", async () => {
|
||||
const { runtime, logPath } = await createMockRuntimeFixture();
|
||||
const handle = await runtime.ensureSession({
|
||||
|
||||
@@ -936,6 +936,9 @@ export class AcpxRuntime implements AcpRuntime {
|
||||
stripProviderAuthEnvVars: this.config.stripProviderAuthEnvVars,
|
||||
spawnOptions: this.spawnCommandOptions,
|
||||
});
|
||||
if (!targetCommand) {
|
||||
return null;
|
||||
}
|
||||
const resolved = buildMcpProxyAgentCommand({
|
||||
targetCommand,
|
||||
mcpServers: toAcpMcpServers(this.config.mcpServers),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"id": "amazon-bedrock",
|
||||
"enabledByDefault": true,
|
||||
"providers": ["amazon-bedrock"],
|
||||
"configSchema": {
|
||||
"type": "object",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/amazon-bedrock-provider",
|
||||
"version": "2026.3.22",
|
||||
"version": "2026.3.26",
|
||||
"private": true,
|
||||
"description": "OpenClaw Amazon Bedrock provider plugin",
|
||||
"type": "module",
|
||||
|
||||
3
extensions/amazon-bedrock/provider.contract.test.ts
Normal file
3
extensions/amazon-bedrock/provider.contract.test.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { describeProviderContracts } from "../../test/helpers/extensions/provider-contract.js";
|
||||
|
||||
describeProviderContracts("amazon-bedrock");
|
||||
5
extensions/anthropic-vertex/api.ts
Normal file
5
extensions/anthropic-vertex/api.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export {
|
||||
ANTHROPIC_VERTEX_DEFAULT_MODEL_ID,
|
||||
buildAnthropicVertexProvider,
|
||||
} from "./provider-catalog.js";
|
||||
export { resolveAnthropicVertexRegion } from "./region.js";
|
||||
@@ -2,7 +2,7 @@ import type {
|
||||
ModelDefinitionConfig,
|
||||
ModelProviderConfig,
|
||||
} from "openclaw/plugin-sdk/provider-models";
|
||||
import { resolveAnthropicVertexRegion } from "openclaw/plugin-sdk/provider-models";
|
||||
import { resolveAnthropicVertexRegion } from "./region.js";
|
||||
export const ANTHROPIC_VERTEX_DEFAULT_MODEL_ID = "claude-sonnet-4-6";
|
||||
const ANTHROPIC_VERTEX_DEFAULT_CONTEXT_WINDOW = 1_000_000;
|
||||
const GCP_VERTEX_CREDENTIALS_MARKER = "gcp-vertex-credentials";
|
||||
|
||||
20
extensions/anthropic-vertex/region.ts
Normal file
20
extensions/anthropic-vertex/region.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
const ANTHROPIC_VERTEX_DEFAULT_REGION = "global";
|
||||
const ANTHROPIC_VERTEX_REGION_RE = /^[a-z0-9-]+$/;
|
||||
|
||||
function normalizeOptionalSecretInput(value: unknown): string | undefined {
|
||||
if (typeof value !== "string") {
|
||||
return undefined;
|
||||
}
|
||||
const trimmed = value.trim();
|
||||
return trimmed || undefined;
|
||||
}
|
||||
|
||||
export function resolveAnthropicVertexRegion(env: NodeJS.ProcessEnv = process.env): string {
|
||||
const region =
|
||||
normalizeOptionalSecretInput(env.GOOGLE_CLOUD_LOCATION) ||
|
||||
normalizeOptionalSecretInput(env.CLOUD_ML_REGION);
|
||||
|
||||
return region && ANTHROPIC_VERTEX_REGION_RE.test(region)
|
||||
? region
|
||||
: ANTHROPIC_VERTEX_DEFAULT_REGION;
|
||||
}
|
||||
1
extensions/anthropic/api.ts
Normal file
1
extensions/anthropic/api.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { CLAUDE_CLI_BACKEND_ID, isClaudeCliProvider } from "./cli-shared.js";
|
||||
@@ -3,102 +3,49 @@ import {
|
||||
CLI_FRESH_WATCHDOG_DEFAULTS,
|
||||
CLI_RESUME_WATCHDOG_DEFAULTS,
|
||||
} from "openclaw/plugin-sdk/cli-backend";
|
||||
|
||||
const CLAUDE_MODEL_ALIASES: Record<string, string> = {
|
||||
opus: "opus",
|
||||
"opus-4.6": "opus",
|
||||
"opus-4.5": "opus",
|
||||
"opus-4": "opus",
|
||||
"claude-opus-4-6": "opus",
|
||||
"claude-opus-4-5": "opus",
|
||||
"claude-opus-4": "opus",
|
||||
sonnet: "sonnet",
|
||||
"sonnet-4.6": "sonnet",
|
||||
"sonnet-4.5": "sonnet",
|
||||
"sonnet-4.1": "sonnet",
|
||||
"sonnet-4.0": "sonnet",
|
||||
"claude-sonnet-4-6": "sonnet",
|
||||
"claude-sonnet-4-5": "sonnet",
|
||||
"claude-sonnet-4-1": "sonnet",
|
||||
"claude-sonnet-4-0": "sonnet",
|
||||
haiku: "haiku",
|
||||
"haiku-3.5": "haiku",
|
||||
"claude-haiku-3-5": "haiku",
|
||||
};
|
||||
|
||||
const CLAUDE_LEGACY_SKIP_PERMISSIONS_ARG = "--dangerously-skip-permissions";
|
||||
const CLAUDE_PERMISSION_MODE_ARG = "--permission-mode";
|
||||
const CLAUDE_BYPASS_PERMISSIONS_MODE = "bypassPermissions";
|
||||
|
||||
function normalizeClaudePermissionArgs(args?: string[]): string[] | undefined {
|
||||
if (!args) {
|
||||
return args;
|
||||
}
|
||||
const normalized: string[] = [];
|
||||
let sawLegacySkip = false;
|
||||
let hasPermissionMode = false;
|
||||
for (let i = 0; i < args.length; i += 1) {
|
||||
const arg = args[i];
|
||||
if (arg === CLAUDE_LEGACY_SKIP_PERMISSIONS_ARG) {
|
||||
sawLegacySkip = true;
|
||||
continue;
|
||||
}
|
||||
if (arg === CLAUDE_PERMISSION_MODE_ARG) {
|
||||
hasPermissionMode = true;
|
||||
normalized.push(arg);
|
||||
const maybeValue = args[i + 1];
|
||||
if (typeof maybeValue === "string") {
|
||||
normalized.push(maybeValue);
|
||||
i += 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (arg.startsWith(`${CLAUDE_PERMISSION_MODE_ARG}=`)) {
|
||||
hasPermissionMode = true;
|
||||
}
|
||||
normalized.push(arg);
|
||||
}
|
||||
if (sawLegacySkip && !hasPermissionMode) {
|
||||
normalized.push(CLAUDE_PERMISSION_MODE_ARG, CLAUDE_BYPASS_PERMISSIONS_MODE);
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function normalizeClaudeBackendConfig(config: CliBackendConfig): CliBackendConfig {
|
||||
return {
|
||||
...config,
|
||||
args: normalizeClaudePermissionArgs(config.args),
|
||||
resumeArgs: normalizeClaudePermissionArgs(config.resumeArgs),
|
||||
};
|
||||
}
|
||||
import {
|
||||
CLAUDE_CLI_BACKEND_ID,
|
||||
CLAUDE_CLI_CLEAR_ENV,
|
||||
CLAUDE_CLI_MODEL_ALIASES,
|
||||
CLAUDE_CLI_SESSION_ID_FIELDS,
|
||||
normalizeClaudeBackendConfig,
|
||||
} from "./cli-shared.js";
|
||||
|
||||
export function buildAnthropicCliBackend(): CliBackendPlugin {
|
||||
return {
|
||||
id: "claude-cli",
|
||||
id: CLAUDE_CLI_BACKEND_ID,
|
||||
bundleMcp: true,
|
||||
config: {
|
||||
command: "claude",
|
||||
args: ["-p", "--output-format", "json", "--permission-mode", "bypassPermissions"],
|
||||
args: [
|
||||
"-p",
|
||||
"--output-format",
|
||||
"stream-json",
|
||||
"--verbose",
|
||||
"--permission-mode",
|
||||
"bypassPermissions",
|
||||
],
|
||||
resumeArgs: [
|
||||
"-p",
|
||||
"--output-format",
|
||||
"json",
|
||||
"stream-json",
|
||||
"--verbose",
|
||||
"--permission-mode",
|
||||
"bypassPermissions",
|
||||
"--resume",
|
||||
"{sessionId}",
|
||||
],
|
||||
output: "json",
|
||||
output: "jsonl",
|
||||
input: "arg",
|
||||
modelArg: "--model",
|
||||
modelAliases: CLAUDE_MODEL_ALIASES,
|
||||
modelAliases: CLAUDE_CLI_MODEL_ALIASES,
|
||||
sessionArg: "--session-id",
|
||||
sessionMode: "always",
|
||||
sessionIdFields: ["session_id", "sessionId", "conversation_id", "conversationId"],
|
||||
sessionIdFields: [...CLAUDE_CLI_SESSION_ID_FIELDS],
|
||||
systemPromptArg: "--append-system-prompt",
|
||||
systemPromptMode: "append",
|
||||
systemPromptWhen: "first",
|
||||
clearEnv: ["ANTHROPIC_API_KEY", "ANTHROPIC_API_KEY_OLD"],
|
||||
clearEnv: [...CLAUDE_CLI_CLEAR_ENV],
|
||||
reliability: {
|
||||
watchdog: {
|
||||
fresh: { ...CLI_FRESH_WATCHDOG_DEFAULTS },
|
||||
|
||||
84
extensions/anthropic/cli-shared.ts
Normal file
84
extensions/anthropic/cli-shared.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import type { CliBackendConfig } from "openclaw/plugin-sdk/cli-backend";
|
||||
|
||||
export const CLAUDE_CLI_BACKEND_ID = "claude-cli";
|
||||
|
||||
export const CLAUDE_CLI_MODEL_ALIASES: Record<string, string> = {
|
||||
opus: "opus",
|
||||
"opus-4.6": "opus",
|
||||
"opus-4.5": "opus",
|
||||
"opus-4": "opus",
|
||||
"claude-opus-4-6": "opus",
|
||||
"claude-opus-4-5": "opus",
|
||||
"claude-opus-4": "opus",
|
||||
sonnet: "sonnet",
|
||||
"sonnet-4.6": "sonnet",
|
||||
"sonnet-4.5": "sonnet",
|
||||
"sonnet-4.1": "sonnet",
|
||||
"sonnet-4.0": "sonnet",
|
||||
"claude-sonnet-4-6": "sonnet",
|
||||
"claude-sonnet-4-5": "sonnet",
|
||||
"claude-sonnet-4-1": "sonnet",
|
||||
"claude-sonnet-4-0": "sonnet",
|
||||
haiku: "haiku",
|
||||
"haiku-3.5": "haiku",
|
||||
"claude-haiku-3-5": "haiku",
|
||||
};
|
||||
|
||||
export const CLAUDE_CLI_SESSION_ID_FIELDS = [
|
||||
"session_id",
|
||||
"sessionId",
|
||||
"conversation_id",
|
||||
"conversationId",
|
||||
] as const;
|
||||
|
||||
export const CLAUDE_CLI_CLEAR_ENV = ["ANTHROPIC_API_KEY", "ANTHROPIC_API_KEY_OLD"] as const;
|
||||
|
||||
const CLAUDE_LEGACY_SKIP_PERMISSIONS_ARG = "--dangerously-skip-permissions";
|
||||
const CLAUDE_PERMISSION_MODE_ARG = "--permission-mode";
|
||||
const CLAUDE_BYPASS_PERMISSIONS_MODE = "bypassPermissions";
|
||||
|
||||
export function isClaudeCliProvider(providerId: string): boolean {
|
||||
return providerId.trim().toLowerCase() === CLAUDE_CLI_BACKEND_ID;
|
||||
}
|
||||
|
||||
export function normalizeClaudePermissionArgs(args?: string[]): string[] | undefined {
|
||||
if (!args) {
|
||||
return args;
|
||||
}
|
||||
const normalized: string[] = [];
|
||||
let sawLegacySkip = false;
|
||||
let hasPermissionMode = false;
|
||||
for (let i = 0; i < args.length; i += 1) {
|
||||
const arg = args[i];
|
||||
if (arg === CLAUDE_LEGACY_SKIP_PERMISSIONS_ARG) {
|
||||
sawLegacySkip = true;
|
||||
continue;
|
||||
}
|
||||
if (arg === CLAUDE_PERMISSION_MODE_ARG) {
|
||||
hasPermissionMode = true;
|
||||
normalized.push(arg);
|
||||
const maybeValue = args[i + 1];
|
||||
if (typeof maybeValue === "string") {
|
||||
normalized.push(maybeValue);
|
||||
i += 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (arg.startsWith(`${CLAUDE_PERMISSION_MODE_ARG}=`)) {
|
||||
hasPermissionMode = true;
|
||||
}
|
||||
normalized.push(arg);
|
||||
}
|
||||
if (sawLegacySkip && !hasPermissionMode) {
|
||||
normalized.push(CLAUDE_PERMISSION_MODE_ARG, CLAUDE_BYPASS_PERMISSIONS_MODE);
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
export function normalizeClaudeBackendConfig(config: CliBackendConfig): CliBackendConfig {
|
||||
return {
|
||||
...config,
|
||||
args: normalizeClaudePermissionArgs(config.args),
|
||||
resumeArgs: normalizeClaudePermissionArgs(config.resumeArgs),
|
||||
};
|
||||
}
|
||||
@@ -378,6 +378,12 @@ export default definePluginEntry({
|
||||
docsPath: "/providers/models",
|
||||
envVars: ["ANTHROPIC_OAUTH_TOKEN", "ANTHROPIC_API_KEY"],
|
||||
deprecatedProfileIds: [CLAUDE_CLI_PROFILE_ID],
|
||||
oauthProfileIdRepairs: [
|
||||
{
|
||||
legacyProfileId: "anthropic:default",
|
||||
promptLabel: "Anthropic",
|
||||
},
|
||||
],
|
||||
auth: [
|
||||
{
|
||||
id: "cli",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "anthropic",
|
||||
"enabledByDefault": true,
|
||||
"providers": ["anthropic"],
|
||||
"mediaUnderstandingProviders": ["anthropic"],
|
||||
"cliBackends": ["claude-cli"],
|
||||
"providerAuthEnvVars": {
|
||||
"anthropic": ["ANTHROPIC_OAUTH_TOKEN", "ANTHROPIC_API_KEY"]
|
||||
@@ -11,6 +11,7 @@
|
||||
"provider": "anthropic",
|
||||
"method": "cli",
|
||||
"choiceId": "anthropic-cli",
|
||||
"deprecatedChoiceIds": ["claude-cli"],
|
||||
"choiceLabel": "Anthropic Claude CLI",
|
||||
"choiceHint": "Reuse a local Claude CLI login on this host",
|
||||
"groupId": "anthropic",
|
||||
@@ -41,6 +42,9 @@
|
||||
"cliDescription": "Anthropic API key"
|
||||
}
|
||||
],
|
||||
"contracts": {
|
||||
"mediaUnderstandingProviders": ["anthropic"]
|
||||
},
|
||||
"configSchema": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/anthropic-provider",
|
||||
"version": "2026.3.22",
|
||||
"version": "2026.3.26",
|
||||
"private": true,
|
||||
"description": "OpenClaw Anthropic provider plugin",
|
||||
"type": "module",
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { describePluginRegistrationContract } from "../../test/helpers/extensions/plugin-registration-contract.js";
|
||||
|
||||
describePluginRegistrationContract({
|
||||
pluginId: "anthropic",
|
||||
providerIds: ["anthropic"],
|
||||
mediaUnderstandingProviderIds: ["anthropic"],
|
||||
cliBackendIds: ["claude-cli"],
|
||||
requireDescribeImages: true,
|
||||
});
|
||||
3
extensions/anthropic/provider-runtime.contract.test.ts
Normal file
3
extensions/anthropic/provider-runtime.contract.test.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { describeAnthropicProviderRuntimeContract } from "../../test/helpers/extensions/provider-runtime-contract.js";
|
||||
|
||||
describeAnthropicProviderRuntimeContract();
|
||||
3
extensions/anthropic/provider.contract.test.ts
Normal file
3
extensions/anthropic/provider.contract.test.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { describeProviderContracts } from "../../test/helpers/extensions/provider-contract.js";
|
||||
|
||||
describeProviderContracts("anthropic");
|
||||
1
extensions/anthropic/test-api.ts
Normal file
1
extensions/anthropic/test-api.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { buildAnthropicCliBackend } from "./cli-backend.js";
|
||||
@@ -1 +1,6 @@
|
||||
export { bluebubblesPlugin } from "./src/channel.js";
|
||||
export {
|
||||
resolveBlueBubblesGroupRequireMention,
|
||||
resolveBlueBubblesGroupToolPolicy,
|
||||
} from "./src/group-policy.js";
|
||||
export { isAllowedBlueBubblesSender } from "./src/targets.js";
|
||||
|
||||
1
extensions/bluebubbles/channel-config-api.ts
Normal file
1
extensions/bluebubbles/channel-config-api.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { BlueBubblesChannelConfigSchema } from "./src/config-schema.js";
|
||||
6
extensions/bluebubbles/package-manifest.contract.test.ts
Normal file
6
extensions/bluebubbles/package-manifest.contract.test.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { describePackageManifestContract } from "../../test/helpers/extensions/package-manifest-contract.js";
|
||||
|
||||
describePackageManifestContract({
|
||||
pluginId: "bluebubbles",
|
||||
minHostVersionBaseline: "2026.3.22",
|
||||
});
|
||||
@@ -1,16 +1,13 @@
|
||||
{
|
||||
"name": "@openclaw/bluebubbles",
|
||||
"version": "2026.3.22",
|
||||
"version": "2026.3.26",
|
||||
"description": "OpenClaw BlueBubbles channel plugin",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"zod": "^4.3.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"openclaw": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"openclaw": ">=2026.3.22"
|
||||
"openclaw": ">=2026.3.26"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"openclaw": {
|
||||
@@ -43,7 +40,7 @@
|
||||
"npmSpec": "@openclaw/bluebubbles",
|
||||
"localPath": "extensions/bluebubbles",
|
||||
"defaultChoice": "npm",
|
||||
"minHostVersion": ">=2026.3.22"
|
||||
"minHostVersion": ">=2026.3.26"
|
||||
},
|
||||
"release": {
|
||||
"publishToNpm": true
|
||||
|
||||
@@ -135,7 +135,8 @@ describe("bluebubblesMessageActions", () => {
|
||||
},
|
||||
};
|
||||
const actions = describeMessageTool({ cfg })?.actions ?? [];
|
||||
expect(actions).toContain("sendAttachment");
|
||||
expect(actions).toContain("upload-file");
|
||||
expect(actions).not.toContain("sendAttachment");
|
||||
expect(actions).not.toContain("react");
|
||||
expect(actions).not.toContain("reply");
|
||||
expect(actions).not.toContain("sendWithEffect");
|
||||
@@ -165,6 +166,7 @@ describe("bluebubblesMessageActions", () => {
|
||||
expect(supportsAction({ action: "removeParticipant" })).toBe(true);
|
||||
expect(supportsAction({ action: "leaveGroup" })).toBe(true);
|
||||
expect(supportsAction({ action: "sendAttachment" })).toBe(true);
|
||||
expect(supportsAction({ action: "upload-file" })).toBe(true);
|
||||
});
|
||||
|
||||
it("returns false for unsupported actions", () => {
|
||||
@@ -204,6 +206,36 @@ describe("bluebubblesMessageActions", () => {
|
||||
});
|
||||
|
||||
describe("handleAction", () => {
|
||||
it("maps upload-file to the attachment runtime using canonical naming", async () => {
|
||||
const result = await callHandleAction({
|
||||
action: "upload-file",
|
||||
params: {
|
||||
to: "+15551234567",
|
||||
filename: "photo.png",
|
||||
buffer: Buffer.from("img").toString("base64"),
|
||||
message: "caption",
|
||||
contentType: "image/png",
|
||||
},
|
||||
cfg: blueBubblesConfig(),
|
||||
accountId: null,
|
||||
});
|
||||
|
||||
expect(sendBlueBubblesAttachment).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
to: "+15551234567",
|
||||
filename: "photo.png",
|
||||
caption: "caption",
|
||||
contentType: "image/png",
|
||||
}),
|
||||
);
|
||||
expect(result).toMatchObject({
|
||||
details: {
|
||||
ok: true,
|
||||
messageId: "att-msg-123",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("throws for unsupported actions", async () => {
|
||||
const cfg: OpenClawConfig = {
|
||||
channels: {
|
||||
|
||||
@@ -52,7 +52,10 @@ function readMessageText(params: Record<string, unknown>): string | undefined {
|
||||
}
|
||||
|
||||
/** Supported action names for BlueBubbles */
|
||||
const SUPPORTED_ACTIONS = new Set<ChannelMessageActionName>(BLUEBUBBLES_ACTION_NAMES);
|
||||
const SUPPORTED_ACTIONS = new Set<ChannelMessageActionName>([
|
||||
...BLUEBUBBLES_ACTION_NAMES,
|
||||
"upload-file",
|
||||
]);
|
||||
const PRIVATE_API_ACTIONS = new Set<ChannelMessageActionName>([
|
||||
"react",
|
||||
"edit",
|
||||
@@ -107,6 +110,9 @@ export const bluebubblesMessageActions: ChannelMessageActionAdapter = {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (actions.delete("sendAttachment")) {
|
||||
actions.add("upload-file");
|
||||
}
|
||||
return { actions: Array.from(actions) };
|
||||
},
|
||||
supportsAction: ({ action }) => SUPPORTED_ACTIONS.has(action),
|
||||
@@ -428,11 +434,11 @@ export const bluebubblesMessageActions: ChannelMessageActionAdapter = {
|
||||
return jsonResult({ ok: true, left: resolvedChatGuid });
|
||||
}
|
||||
|
||||
// Handle sendAttachment action
|
||||
if (action === "sendAttachment") {
|
||||
// Handle sendAttachment action (legacy) and upload-file (canonical)
|
||||
if (action === "sendAttachment" || action === "upload-file") {
|
||||
const to = readStringParam(params, "to", { required: true });
|
||||
const filename = readStringParam(params, "filename", { required: true });
|
||||
const caption = readStringParam(params, "caption");
|
||||
const caption = readStringParam(params, "caption") ?? readStringParam(params, "message");
|
||||
const contentType =
|
||||
readStringParam(params, "contentType") ?? readStringParam(params, "mimeType");
|
||||
const asVoice = readBooleanParam(params, "asVoice");
|
||||
@@ -448,10 +454,10 @@ export const bluebubblesMessageActions: ChannelMessageActionAdapter = {
|
||||
} else if (filePath) {
|
||||
// Read file from path (will be handled by caller providing buffer)
|
||||
throw new Error(
|
||||
"BlueBubbles sendAttachment: filePath not supported in action, provide buffer as base64.",
|
||||
`BlueBubbles ${action}: filePath not supported in action, provide buffer as base64.`,
|
||||
);
|
||||
} else {
|
||||
throw new Error("BlueBubbles sendAttachment requires buffer (base64) parameter.");
|
||||
throw new Error(`BlueBubbles ${action} requires buffer (base64) parameter.`);
|
||||
}
|
||||
|
||||
const result = await runtime.sendBlueBubblesAttachment({
|
||||
|
||||
@@ -4,14 +4,13 @@ import {
|
||||
adaptScopedAccountAccessor,
|
||||
createScopedChannelConfigAdapter,
|
||||
} from "openclaw/plugin-sdk/channel-config-helpers";
|
||||
import { buildChannelConfigSchema } from "openclaw/plugin-sdk/channel-config-schema";
|
||||
import {
|
||||
listBlueBubblesAccountIds,
|
||||
type ResolvedBlueBubblesAccount,
|
||||
resolveBlueBubblesAccount,
|
||||
resolveDefaultBlueBubblesAccountId,
|
||||
} from "./accounts.js";
|
||||
import { BlueBubblesConfigSchema } from "./config-schema.js";
|
||||
import { BlueBubblesChannelConfigSchema } from "./config-schema.js";
|
||||
import type { ChannelPlugin } from "./runtime-api.js";
|
||||
import { normalizeBlueBubblesHandle } from "./targets.js";
|
||||
|
||||
@@ -41,7 +40,7 @@ export const bluebubblesCapabilities: ChannelPlugin<ResolvedBlueBubblesAccount>[
|
||||
};
|
||||
|
||||
export const bluebubblesReload = { configPrefixes: ["channels.bluebubbles"] };
|
||||
export const bluebubblesConfigSchema = buildChannelConfigSchema(BlueBubblesConfigSchema);
|
||||
export const bluebubblesConfigSchema = BlueBubblesChannelConfigSchema;
|
||||
|
||||
export const bluebubblesConfigAdapter =
|
||||
createScopedChannelConfigAdapter<ResolvedBlueBubblesAccount>({
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import {
|
||||
AllowFromListSchema,
|
||||
buildChannelConfigSchema,
|
||||
buildCatchallMultiAccountChannelSchema,
|
||||
DmPolicySchema,
|
||||
GroupPolicySchema,
|
||||
MarkdownConfigSchema,
|
||||
ToolPolicySchema,
|
||||
} from "openclaw/plugin-sdk/channel-config-schema";
|
||||
import { z } from "zod";
|
||||
import { z } from "openclaw/plugin-sdk/zod";
|
||||
import { bluebubblesChannelConfigUiHints } from "./config-ui-hints.js";
|
||||
import { buildSecretInputSchema, hasConfiguredSecretInput } from "./secret-input.js";
|
||||
|
||||
const bluebubblesActionSchema = z
|
||||
@@ -71,3 +73,7 @@ export const BlueBubblesConfigSchema = buildCatchallMultiAccountChannelSchema(
|
||||
).extend({
|
||||
actions: bluebubblesActionSchema,
|
||||
});
|
||||
|
||||
export const BlueBubblesChannelConfigSchema = buildChannelConfigSchema(BlueBubblesConfigSchema, {
|
||||
uiHints: bluebubblesChannelConfigUiHints,
|
||||
});
|
||||
|
||||
12
extensions/bluebubbles/src/config-ui-hints.ts
Normal file
12
extensions/bluebubbles/src/config-ui-hints.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import type { ChannelConfigUiHint } from "openclaw/plugin-sdk/core";
|
||||
|
||||
export const bluebubblesChannelConfigUiHints = {
|
||||
"": {
|
||||
label: "BlueBubbles",
|
||||
help: "BlueBubbles channel provider configuration used for Apple messaging bridge integrations. Keep DM policy aligned with your trusted sender model in shared deployments.",
|
||||
},
|
||||
dmPolicy: {
|
||||
label: "BlueBubbles DM Policy",
|
||||
help: 'Direct message access control ("pairing" recommended). "open" requires channels.bluebubbles.allowFrom=["*"].',
|
||||
},
|
||||
} satisfies Record<string, ChannelConfigUiHint>;
|
||||
6
extensions/bluebubbles/src/dm-policy.contract.test.ts
Normal file
6
extensions/bluebubbles/src/dm-policy.contract.test.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { describe } from "vitest";
|
||||
import { installDmPolicyContractSuite } from "../../../test/helpers/channels/dm-policy-contract.js";
|
||||
|
||||
describe("bluebubbles dm policy contract", () => {
|
||||
installDmPolicyContractSuite("bluebubbles");
|
||||
});
|
||||
@@ -0,0 +1,3 @@
|
||||
import { describeChannelRegistryBackedContracts } from "../../../test/helpers/channels/registry-backed-contract.js";
|
||||
|
||||
describeChannelRegistryBackedContracts("bluebubbles");
|
||||
@@ -1 +1,53 @@
|
||||
export * from "openclaw/plugin-sdk/bluebubbles";
|
||||
export { resolveAckReaction } from "openclaw/plugin-sdk/bluebubbles";
|
||||
export {
|
||||
createActionGate,
|
||||
jsonResult,
|
||||
readNumberParam,
|
||||
readReactionParams,
|
||||
readStringParam,
|
||||
} from "openclaw/plugin-sdk/bluebubbles";
|
||||
export type { HistoryEntry } from "openclaw/plugin-sdk/bluebubbles";
|
||||
export {
|
||||
evictOldHistoryKeys,
|
||||
recordPendingHistoryEntryIfEnabled,
|
||||
} from "openclaw/plugin-sdk/bluebubbles";
|
||||
export { resolveControlCommandGate } from "openclaw/plugin-sdk/bluebubbles";
|
||||
export { logAckFailure, logInboundDrop, logTypingFailure } from "openclaw/plugin-sdk/bluebubbles";
|
||||
export { BLUEBUBBLES_ACTION_NAMES, BLUEBUBBLES_ACTIONS } from "openclaw/plugin-sdk/bluebubbles";
|
||||
export { resolveChannelMediaMaxBytes } from "openclaw/plugin-sdk/bluebubbles";
|
||||
export { PAIRING_APPROVED_MESSAGE } from "openclaw/plugin-sdk/bluebubbles";
|
||||
export { collectBlueBubblesStatusIssues } from "openclaw/plugin-sdk/bluebubbles";
|
||||
export type {
|
||||
BaseProbeResult,
|
||||
ChannelAccountSnapshot,
|
||||
ChannelMessageActionAdapter,
|
||||
ChannelMessageActionName,
|
||||
} from "openclaw/plugin-sdk/bluebubbles";
|
||||
export type { ChannelPlugin } from "openclaw/plugin-sdk/bluebubbles";
|
||||
export type { OpenClawConfig } from "openclaw/plugin-sdk/bluebubbles";
|
||||
export { parseFiniteNumber } from "openclaw/plugin-sdk/bluebubbles";
|
||||
export type { PluginRuntime } from "openclaw/plugin-sdk/bluebubbles";
|
||||
export { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/bluebubbles";
|
||||
export {
|
||||
DM_GROUP_ACCESS_REASON,
|
||||
readStoreAllowFromForDmPolicy,
|
||||
resolveDmGroupAccessWithLists,
|
||||
} from "openclaw/plugin-sdk/bluebubbles";
|
||||
export { readBooleanParam } from "openclaw/plugin-sdk/bluebubbles";
|
||||
export { mapAllowFromEntries } from "openclaw/plugin-sdk/bluebubbles";
|
||||
export { createChannelPairingController } from "openclaw/plugin-sdk/bluebubbles";
|
||||
export { createChannelReplyPipeline } from "openclaw/plugin-sdk/bluebubbles";
|
||||
export { resolveRequestUrl } from "openclaw/plugin-sdk/bluebubbles";
|
||||
export { buildProbeChannelStatusSummary } from "openclaw/plugin-sdk/bluebubbles";
|
||||
export { stripMarkdown } from "openclaw/plugin-sdk/bluebubbles";
|
||||
export { extractToolSend } from "openclaw/plugin-sdk/bluebubbles";
|
||||
export {
|
||||
WEBHOOK_RATE_LIMIT_DEFAULTS,
|
||||
createFixedWindowRateLimiter,
|
||||
createWebhookInFlightLimiter,
|
||||
readWebhookBodyOrReject,
|
||||
registerWebhookTargetWithPluginRoute,
|
||||
resolveRequestClientIp,
|
||||
resolveWebhookTargetWithAuthOrRejectSync,
|
||||
withResolvedWebhookRequestPipeline,
|
||||
} from "openclaw/plugin-sdk/bluebubbles";
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import {
|
||||
createSetupInputPresenceValidator,
|
||||
createTopLevelChannelDmPolicySetter,
|
||||
normalizeAccountId,
|
||||
patchScopedAccountConfig,
|
||||
@@ -42,18 +43,20 @@ export const blueBubblesSetupAdapter: ChannelSetupAdapter = {
|
||||
accountId,
|
||||
name,
|
||||
}),
|
||||
validateInput: ({ input }) => {
|
||||
if (!input.httpUrl && !input.password) {
|
||||
return "BlueBubbles requires --http-url and --password.";
|
||||
}
|
||||
if (!input.httpUrl) {
|
||||
return "BlueBubbles requires --http-url.";
|
||||
}
|
||||
if (!input.password) {
|
||||
return "BlueBubbles requires --password.";
|
||||
}
|
||||
return null;
|
||||
},
|
||||
validateInput: createSetupInputPresenceValidator({
|
||||
validate: ({ input }) => {
|
||||
if (!input.httpUrl && !input.password) {
|
||||
return "BlueBubbles requires --http-url and --password.";
|
||||
}
|
||||
if (!input.httpUrl) {
|
||||
return "BlueBubbles requires --http-url.";
|
||||
}
|
||||
if (!input.password) {
|
||||
return "BlueBubbles requires --password.";
|
||||
}
|
||||
return null;
|
||||
},
|
||||
}),
|
||||
applyAccountConfig: ({ cfg, accountId, input }) => {
|
||||
const next = prepareScopedSetupConfig({
|
||||
cfg,
|
||||
|
||||
3
extensions/brave/bundled-web-search.contract.test.ts
Normal file
3
extensions/brave/bundled-web-search.contract.test.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { describeBundledWebSearchFastPathContract } from "../../test/helpers/extensions/bundled-web-search-fast-path-contract.js";
|
||||
|
||||
describeBundledWebSearchFastPathContract("brave");
|
||||
@@ -15,6 +15,9 @@
|
||||
"help": "Brave Search mode: web or llm-context."
|
||||
}
|
||||
},
|
||||
"contracts": {
|
||||
"webSearchProviders": ["brave"]
|
||||
},
|
||||
"configSchema": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/brave-plugin",
|
||||
"version": "2026.3.22",
|
||||
"version": "2026.3.26",
|
||||
"private": true,
|
||||
"description": "OpenClaw Brave plugin",
|
||||
"type": "module",
|
||||
|
||||
6
extensions/brave/plugin-registration.contract.test.ts
Normal file
6
extensions/brave/plugin-registration.contract.test.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { describePluginRegistrationContract } from "../../test/helpers/extensions/plugin-registration-contract.js";
|
||||
|
||||
describePluginRegistrationContract({
|
||||
pluginId: "brave",
|
||||
webSearchProviderIds: ["brave"],
|
||||
});
|
||||
@@ -16,6 +16,18 @@ describe("brave web search provider", () => {
|
||||
search_lang: "jp",
|
||||
ui_lang: "en-US",
|
||||
});
|
||||
expect(__testing.normalizeBraveLanguageParams({ search_lang: "tr-TR", ui_lang: "tr" })).toEqual(
|
||||
{
|
||||
search_lang: "tr",
|
||||
ui_lang: "tr-TR",
|
||||
},
|
||||
);
|
||||
expect(__testing.normalizeBraveLanguageParams({ search_lang: "EN", ui_lang: "en-us" })).toEqual(
|
||||
{
|
||||
search_lang: "en",
|
||||
ui_lang: "en-US",
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("flags invalid brave language fields", () => {
|
||||
@@ -24,6 +36,12 @@ describe("brave web search provider", () => {
|
||||
search_lang: "xx",
|
||||
}),
|
||||
).toEqual({ invalidField: "search_lang" });
|
||||
expect(__testing.normalizeBraveLanguageParams({ search_lang: "en-US" })).toEqual({
|
||||
invalidField: "search_lang",
|
||||
});
|
||||
expect(__testing.normalizeBraveLanguageParams({ ui_lang: "en" })).toEqual({
|
||||
invalidField: "ui_lang",
|
||||
});
|
||||
});
|
||||
|
||||
it("defaults brave mode to web unless llm-context is explicitly selected", () => {
|
||||
|
||||
@@ -580,6 +580,7 @@ export function createBraveWebSearchProvider(): WebSearchProviderPlugin {
|
||||
id: "brave",
|
||||
label: "Brave Search",
|
||||
hint: "Structured results · country/language/time filters",
|
||||
onboardingScopes: ["text-inference"],
|
||||
credentialLabel: "Brave Search API key",
|
||||
envVars: ["BRAVE_API_KEY"],
|
||||
placeholder: "BSA...",
|
||||
|
||||
1
extensions/brave/test-api.ts
Normal file
1
extensions/brave/test-api.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { __testing } from "./src/brave-web-search-provider.js";
|
||||
3
extensions/brave/web-search-provider.contract.test.ts
Normal file
3
extensions/brave/web-search-provider.contract.test.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { describeWebSearchProviderContracts } from "../../test/helpers/extensions/web-search-provider-contract.js";
|
||||
|
||||
describeWebSearchProviderContracts("brave");
|
||||
1
extensions/brave/web-search-provider.ts
Normal file
1
extensions/brave/web-search-provider.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { __testing, createBraveWebSearchProvider } from "./src/brave-web-search-provider.js";
|
||||
1
extensions/browser/browser-runtime-api.ts
Normal file
1
extensions/browser/browser-runtime-api.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from "./src/browser-runtime.js";
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@openclaw/browser-plugin",
|
||||
"version": "2026.3.25",
|
||||
"version": "2026.3.26",
|
||||
"private": true,
|
||||
"description": "OpenClaw browser tool plugin",
|
||||
"type": "module",
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
export { createBrowserTool } from "./src/browser-tool.js";
|
||||
export * from "./src/browser-runtime.js";
|
||||
export { registerBrowserCli } from "./src/cli/browser-cli.js";
|
||||
export { createBrowserPluginService } from "./src/plugin-service.js";
|
||||
export { handleBrowserGatewayRequest } from "./src/gateway/browser-request.js";
|
||||
export { browserHandlers } from "./src/gateway/browser-request.js";
|
||||
export {
|
||||
definePluginEntry,
|
||||
type OpenClawPluginApi,
|
||||
|
||||
@@ -27,7 +27,7 @@ const browserClientMocks = vi.hoisted(() => ({
|
||||
browserStop: vi.fn(async (..._args: unknown[]) => ({})),
|
||||
browserTabs: vi.fn(async (..._args: unknown[]): Promise<Array<Record<string, unknown>>> => []),
|
||||
}));
|
||||
vi.mock("../../../extensions/browser/src/browser/client.js", () => browserClientMocks);
|
||||
vi.mock("./browser/client.js", () => browserClientMocks);
|
||||
|
||||
const browserActionsMocks = vi.hoisted(() => ({
|
||||
browserAct: vi.fn(async () => ({ ok: true })),
|
||||
@@ -48,7 +48,7 @@ const browserActionsMocks = vi.hoisted(() => ({
|
||||
browserPdfSave: vi.fn(async () => ({ ok: true, path: "/tmp/test.pdf" })),
|
||||
browserScreenshotAction: vi.fn(async () => ({ ok: true, path: "/tmp/test.png" })),
|
||||
}));
|
||||
vi.mock("../../../extensions/browser/src/browser/client-actions.js", () => browserActionsMocks);
|
||||
vi.mock("./browser/client-actions.js", () => browserActionsMocks);
|
||||
|
||||
const browserConfigMocks = vi.hoisted(() => ({
|
||||
resolveBrowserConfig: vi.fn(() => ({
|
||||
@@ -89,13 +89,15 @@ const browserConfigMocks = vi.hoisted(() => ({
|
||||
};
|
||||
}),
|
||||
}));
|
||||
vi.mock("../../../extensions/browser/src/browser/config.js", () => browserConfigMocks);
|
||||
vi.mock("./browser/config.js", () => browserConfigMocks);
|
||||
|
||||
const nodesUtilsMocks = vi.hoisted(() => ({
|
||||
listNodes: vi.fn(async (..._args: unknown[]): Promise<Array<Record<string, unknown>>> => []),
|
||||
}));
|
||||
vi.mock("./nodes-utils.js", async () => {
|
||||
const actual = await vi.importActual<typeof import("./nodes-utils.js")>("./nodes-utils.js");
|
||||
vi.mock("../../../src/agents/tools/nodes-utils.js", async () => {
|
||||
const actual = await vi.importActual<typeof import("../../../src/agents/tools/nodes-utils.js")>(
|
||||
"../../../src/agents/tools/nodes-utils.js",
|
||||
);
|
||||
return {
|
||||
...actual,
|
||||
listNodes: nodesUtilsMocks.listNodes,
|
||||
@@ -108,39 +110,35 @@ const gatewayMocks = vi.hoisted(() => ({
|
||||
payload: { result: { ok: true, running: true } },
|
||||
})),
|
||||
}));
|
||||
vi.mock("./gateway.js", () => gatewayMocks);
|
||||
vi.mock("../../../src/agents/tools/gateway.js", () => gatewayMocks);
|
||||
|
||||
const configMocks = vi.hoisted(() => ({
|
||||
loadConfig: vi.fn(() => ({ browser: {} })),
|
||||
}));
|
||||
vi.mock("../../config/config.js", () => configMocks);
|
||||
vi.mock("../../../src/config/config.js", () => configMocks);
|
||||
|
||||
const sessionTabRegistryMocks = vi.hoisted(() => ({
|
||||
trackSessionBrowserTab: vi.fn(),
|
||||
untrackSessionBrowserTab: vi.fn(),
|
||||
}));
|
||||
vi.mock(
|
||||
"../../../extensions/browser/src/browser/session-tab-registry.js",
|
||||
() => sessionTabRegistryMocks,
|
||||
);
|
||||
vi.mock("./browser/session-tab-registry.js", () => sessionTabRegistryMocks);
|
||||
|
||||
const toolCommonMocks = vi.hoisted(() => ({
|
||||
imageResultFromFile: vi.fn(),
|
||||
}));
|
||||
vi.mock("./common.js", async () => {
|
||||
const actual = await vi.importActual<typeof import("./common.js")>("./common.js");
|
||||
vi.mock("../../../src/agents/tools/common.js", async () => {
|
||||
const actual = await vi.importActual<typeof import("../../../src/agents/tools/common.js")>(
|
||||
"../../../src/agents/tools/common.js",
|
||||
);
|
||||
return {
|
||||
...actual,
|
||||
imageResultFromFile: toolCommonMocks.imageResultFromFile,
|
||||
};
|
||||
});
|
||||
|
||||
import { __testing as browserToolActionsTesting } from "../../../extensions/browser/src/browser-tool.actions.js";
|
||||
import {
|
||||
__testing as browserToolTesting,
|
||||
createBrowserTool,
|
||||
} from "../../../extensions/browser/src/browser-tool.js";
|
||||
import { DEFAULT_AI_SNAPSHOT_MAX_CHARS } from "../../../extensions/browser/src/browser/constants.js";
|
||||
import { __testing as browserToolActionsTesting } from "./browser-tool.actions.js";
|
||||
import { __testing as browserToolTesting, createBrowserTool } from "./browser-tool.js";
|
||||
import { DEFAULT_AI_SNAPSHOT_MAX_CHARS } from "./browser/constants.js";
|
||||
|
||||
function mockSingleBrowserProxyNode() {
|
||||
nodesUtilsMocks.listNodes.mockResolvedValue([
|
||||
@@ -1,13 +1,10 @@
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import {
|
||||
startBrowserBridgeServer,
|
||||
stopBrowserBridgeServer,
|
||||
} from "../../extensions/browser/src/browser/bridge-server.js";
|
||||
import type { ResolvedBrowserConfig } from "../../extensions/browser/src/browser/config.js";
|
||||
import { startBrowserBridgeServer, stopBrowserBridgeServer } from "./bridge-server.js";
|
||||
import type { ResolvedBrowserConfig } from "./config.js";
|
||||
import {
|
||||
DEFAULT_OPENCLAW_BROWSER_COLOR,
|
||||
DEFAULT_OPENCLAW_BROWSER_PROFILE_NAME,
|
||||
} from "../../extensions/browser/src/browser/constants.js";
|
||||
} from "./constants.js";
|
||||
|
||||
function buildResolvedConfig(): ResolvedBrowserConfig {
|
||||
return {
|
||||
@@ -3,17 +3,14 @@ import {
|
||||
appendCdpPath,
|
||||
getHeadersWithAuth,
|
||||
normalizeCdpHttpBaseForJsonEndpoints,
|
||||
} from "../../extensions/browser/src/browser/cdp.helpers.js";
|
||||
import { __test } from "../../extensions/browser/src/browser/client-fetch.js";
|
||||
import {
|
||||
resolveBrowserConfig,
|
||||
resolveProfile,
|
||||
} from "../../extensions/browser/src/browser/config.js";
|
||||
import { shouldRejectBrowserMutation } from "../../extensions/browser/src/browser/csrf.js";
|
||||
import { toBoolean } from "../../extensions/browser/src/browser/routes/utils.js";
|
||||
import type { BrowserServerState } from "../../extensions/browser/src/browser/server-context.js";
|
||||
import { listKnownProfileNames } from "../../extensions/browser/src/browser/server-context.js";
|
||||
import { resolveTargetIdFromTabs } from "../../extensions/browser/src/browser/target-id.js";
|
||||
} from "./cdp.helpers.js";
|
||||
import { __test } from "./client-fetch.js";
|
||||
import { resolveBrowserConfig, resolveProfile } from "./config.js";
|
||||
import { shouldRejectBrowserMutation } from "./csrf.js";
|
||||
import { toBoolean } from "./routes/utils.js";
|
||||
import type { BrowserServerState } from "./server-context.js";
|
||||
import { listKnownProfileNames } from "./server-context.js";
|
||||
import { resolveTargetIdFromTabs } from "./target-id.js";
|
||||
|
||||
describe("toBoolean", () => {
|
||||
it("parses yes/no and 1/0", () => {
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
hasProxyEnv,
|
||||
withNoProxyForCdpUrl,
|
||||
withNoProxyForLocalhost,
|
||||
} from "../../extensions/browser/src/browser/cdp-proxy-bypass.js";
|
||||
} from "./cdp-proxy-bypass.js";
|
||||
|
||||
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||
|
||||
@@ -202,8 +202,7 @@ describe("cdp-proxy-bypass", () => {
|
||||
describe("withNoProxyForLocalhost concurrency", () => {
|
||||
it("does not leak NO_PROXY when called concurrently", async () => {
|
||||
await withIsolatedNoProxyEnv(async () => {
|
||||
const { withNoProxyForLocalhost } =
|
||||
await import("../../extensions/browser/src/browser/cdp-proxy-bypass.js");
|
||||
const { withNoProxyForLocalhost } = await import("./cdp-proxy-bypass.js");
|
||||
|
||||
// Simulate concurrent calls
|
||||
const callA = withNoProxyForLocalhost(async () => {
|
||||
@@ -230,8 +229,7 @@ describe("withNoProxyForLocalhost concurrency", () => {
|
||||
describe("withNoProxyForLocalhost reverse exit order", () => {
|
||||
it("restores NO_PROXY when first caller exits before second", async () => {
|
||||
await withIsolatedNoProxyEnv(async () => {
|
||||
const { withNoProxyForLocalhost } =
|
||||
await import("../../extensions/browser/src/browser/cdp-proxy-bypass.js");
|
||||
const { withNoProxyForLocalhost } = await import("./cdp-proxy-bypass.js");
|
||||
|
||||
// Call A enters first, exits first (short task)
|
||||
// Call B enters second, exits last (long task)
|
||||
@@ -261,8 +259,7 @@ describe("withNoProxyForLocalhost preserves user-configured NO_PROXY", () => {
|
||||
process.env.HTTP_PROXY = "http://proxy:8080";
|
||||
|
||||
try {
|
||||
const { withNoProxyForLocalhost } =
|
||||
await import("../../extensions/browser/src/browser/cdp-proxy-bypass.js");
|
||||
const { withNoProxyForLocalhost } = await import("./cdp-proxy-bypass.js");
|
||||
|
||||
await withNoProxyForLocalhost(async () => {
|
||||
// Should not modify since loopback is already covered
|
||||
@@ -4,7 +4,7 @@ import {
|
||||
PROFILE_WS_REACHABILITY_MAX_TIMEOUT_MS,
|
||||
PROFILE_WS_REACHABILITY_MIN_TIMEOUT_MS,
|
||||
resolveCdpReachabilityTimeouts,
|
||||
} from "../../extensions/browser/src/browser/cdp-timeouts.js";
|
||||
} from "./cdp-timeouts.js";
|
||||
|
||||
describe("resolveCdpReachabilityTimeouts", () => {
|
||||
it("uses loopback defaults when timeout is omitted", () => {
|
||||
@@ -1,17 +1,12 @@
|
||||
import { createServer } from "node:http";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { type WebSocket, WebSocketServer } from "ws";
|
||||
import { isWebSocketUrl } from "../../extensions/browser/src/browser/cdp.helpers.js";
|
||||
import {
|
||||
createTargetViaCdp,
|
||||
evaluateJavaScript,
|
||||
normalizeCdpWsUrl,
|
||||
snapshotAria,
|
||||
} from "../../extensions/browser/src/browser/cdp.js";
|
||||
import { parseHttpUrl } from "../../extensions/browser/src/browser/config.js";
|
||||
import { InvalidBrowserNavigationUrlError } from "../../extensions/browser/src/browser/navigation-guard.js";
|
||||
import { SsrFBlockedError } from "../infra/net/ssrf.js";
|
||||
import { rawDataToString } from "../infra/ws.js";
|
||||
import { isWebSocketUrl } from "./cdp.helpers.js";
|
||||
import { createTargetViaCdp, evaluateJavaScript, normalizeCdpWsUrl, snapshotAria } from "./cdp.js";
|
||||
import { parseHttpUrl } from "./config.js";
|
||||
import { InvalidBrowserNavigationUrlError } from "./navigation-guard.js";
|
||||
|
||||
describe("cdp", () => {
|
||||
let httpServer: ReturnType<typeof createServer> | null = null;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user