Compare commits
411 Commits
65dedef65b
...
b2990a003e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b2990a003e | ||
|
|
4027b3583e | ||
|
|
a3ec2d0734 | ||
|
|
9f03791aa9 | ||
|
|
f52ca0a712 | ||
|
|
ddccfd3ec1 | ||
|
|
f60eae83fa | ||
|
|
5935c4d23d | ||
|
|
1c4db91593 | ||
|
|
9d2066bd53 | ||
|
|
f57e70912c | ||
|
|
a7f4a53ce8 | ||
|
|
8f3bfbd1c4 | ||
|
|
f8dfd034f5 | ||
|
|
fc40ba8e7e | ||
|
|
1f2f79a7a7 | ||
|
|
6e09c1142e | ||
|
|
27677dd8bd | ||
|
|
be4f7ef361 | ||
|
|
6b83d82e82 | ||
|
|
6fb2d3d7d7 | ||
|
|
425003417d | ||
|
|
a8893094ea | ||
|
|
a03d852d65 | ||
|
|
e3b85b9829 | ||
|
|
3014a91b07 | ||
|
|
981de05181 | ||
|
|
9950440cf6 | ||
|
|
80d8fe7786 | ||
|
|
b37626ce6b | ||
|
|
1ee57cf727 | ||
|
|
4322ca6f4a | ||
|
|
afbb1af6c5 | ||
|
|
600c46b5a4 | ||
|
|
7d5ca1176d | ||
|
|
5915d479dc | ||
|
|
30098b04d7 | ||
|
|
f72214725d | ||
|
|
9bef525944 | ||
|
|
edd6289f26 | ||
|
|
d0b98c75e5 | ||
|
|
e332a717a8 | ||
|
|
23cfcd60df | ||
|
|
465536e811 | ||
|
|
3d1c3b78ec | ||
|
|
1861e76360 | ||
|
|
c248da0317 | ||
|
|
b7f4755020 | ||
|
|
3e82cbd55b | ||
|
|
11a968f5c3 | ||
|
|
5d8c665baf | ||
|
|
9df78b3379 | ||
|
|
20578da204 | ||
|
|
564fe6f089 | ||
|
|
dd8373a424 | ||
|
|
9be3c27bb7 | ||
|
|
e12184661e | ||
|
|
3a57106c1e | ||
|
|
2c30ba400b | ||
|
|
5d3af3bc62 | ||
|
|
e9f182def7 | ||
|
|
1b31e2f345 | ||
|
|
58d5b39c9a | ||
|
|
157d6d2db7 | ||
|
|
d5593d647c | ||
|
|
83715eca49 | ||
|
|
7dfa99a6f7 | ||
|
|
ac2b71f240 | ||
|
|
578bde1e0d | ||
|
|
e2c03845c7 | ||
|
|
1523ef2494 | ||
|
|
cdec53b22b | ||
|
|
a6afcb4c1d | ||
|
|
2a68bcbeb3 | ||
|
|
c8af8e9555 | ||
|
|
e77988f747 | ||
|
|
96ad19a627 | ||
|
|
fe81b1d712 | ||
|
|
d1ecb46076 | ||
|
|
fff59da962 | ||
|
|
9e3ea2687c | ||
|
|
cfd6b21d0e | ||
|
|
118507953b | ||
|
|
befa421a57 | ||
|
|
e6fdac7bfb | ||
|
|
67f90dae54 | ||
|
|
31face5740 | ||
|
|
0da6de6624 | ||
|
|
0eae9f456c | ||
|
|
561a10c491 | ||
|
|
c6b4de520a | ||
|
|
f49297e2c1 | ||
|
|
966228a6a9 | ||
|
|
5fb8f779ca | ||
|
|
88e29c728c | ||
|
|
a63ec41a7b | ||
|
|
64849e81f5 | ||
|
|
d3bb32273e | ||
|
|
7113dc21a9 | ||
|
|
4ab814fd50 | ||
|
|
c83bdb73a4 | ||
|
|
ea9eed14f8 | ||
|
|
91e445c260 | ||
|
|
6cd3bc3a46 | ||
|
|
37eaca719a | ||
|
|
ff6114599e | ||
|
|
532b9653be | ||
|
|
b7aac92ac4 | ||
|
|
1a48bce294 | ||
|
|
17b18971f1 | ||
|
|
9f101d3a9a | ||
|
|
a884955cd6 | ||
|
|
f72ac60b01 | ||
|
|
761188cd1d | ||
|
|
d9cadf9737 | ||
|
|
a4382607d7 | ||
|
|
84e115834f | ||
|
|
78f7e5147b | ||
|
|
7b0a0f3dac | ||
|
|
3711143549 | ||
|
|
777756e1c2 | ||
|
|
1b34446bf5 | ||
|
|
13db0489c8 | ||
|
|
2af977f947 | ||
|
|
7cee8c2345 | ||
|
|
e0aa8457c2 | ||
|
|
822388fe92 | ||
|
|
e18f43ddad | ||
|
|
991ed3ab58 | ||
|
|
5676a6b38d | ||
|
|
2b1f68c928 | ||
|
|
673583a38b | ||
|
|
b4cce3ac7a | ||
|
|
4023b76ed3 | ||
|
|
e9d117d221 | ||
|
|
149dc7c4e7 | ||
|
|
e70984745b | ||
|
|
da9f28d270 | ||
|
|
99b4f2a24e | ||
|
|
f9fae2c439 | ||
|
|
9bd64c8a1f | ||
|
|
c429ccb64f | ||
|
|
57d008a33d | ||
|
|
6b0d6e2540 | ||
|
|
dfef943f0a | ||
|
|
39c682219e | ||
|
|
66307695eb | ||
|
|
d134a8c7f3 | ||
|
|
bb3d7343f4 | ||
|
|
1a05ee941e | ||
|
|
1b3f987c4d | ||
|
|
81c68f582d | ||
|
|
d842b28a15 | ||
|
|
ed4529e246 | ||
|
|
bf08b485bd | ||
|
|
845d97b6a5 | ||
|
|
8b64705e05 | ||
|
|
bcb0ed0866 | ||
|
|
9ae1b732ef | ||
|
|
385e66cbd5 | ||
|
|
2d317ce423 | ||
|
|
284d24209b | ||
|
|
b8174decf3 | ||
|
|
41cc5bcd4f | ||
|
|
f6d98a908a | ||
|
|
d03eca8450 | ||
|
|
be9a2fb134 | ||
|
|
8d2f98fb01 | ||
|
|
d5f6caba3f | ||
|
|
4682c2e3e2 | ||
|
|
34dd7324d9 | ||
|
|
9ef24fd400 | ||
|
|
85cd55e22b | ||
|
|
4e4ed2ea17 | ||
|
|
521b121815 | ||
|
|
e74235fdce | ||
|
|
f37b79cf4f | ||
|
|
889480cef9 | ||
|
|
01449a2f41 | ||
|
|
d46b489e21 | ||
|
|
935a0e5708 | ||
|
|
baa1e95b9d | ||
|
|
87a61c3b88 | ||
|
|
e9a32b83c2 | ||
|
|
5ba4586e58 | ||
|
|
e25f8ed56c | ||
|
|
0bc8a592a6 | ||
|
|
1d7dd5f261 | ||
|
|
19b8416a81 | ||
|
|
5020bfa2a9 | ||
|
|
b9910ab037 | ||
|
|
902f968056 | ||
|
|
bd259eeb23 | ||
|
|
476f367cf1 | ||
|
|
dda8a2b238 | ||
|
|
7ee99af9f8 | ||
|
|
4347d2468c | ||
|
|
cf1d3f7a7c | ||
|
|
0fa55ed2b4 | ||
|
|
63c9fac9fc | ||
|
|
8c7901c984 | ||
|
|
aa2eb48b9c | ||
|
|
7aeabbabd4 | ||
|
|
e58291e070 | ||
|
|
411d5fda58 | ||
|
|
19775abdda | ||
|
|
a87a07ec8a | ||
|
|
0a5821a811 | ||
|
|
b796f6ec01 | ||
|
|
92112a61db | ||
|
|
a2b00495cd | ||
|
|
f8575c401c | ||
|
|
3367b2aa27 | ||
|
|
bcbb447357 | ||
|
|
8eb11bd304 | ||
|
|
6c6f1e9660 | ||
|
|
e550e252a7 | ||
|
|
e4d572192d | ||
|
|
2601f413c3 | ||
|
|
1bdd9e313f | ||
|
|
9d2784cdb9 | ||
|
|
bcde2fca5a | ||
|
|
9b6fffd00a | ||
|
|
99346314f5 | ||
|
|
1968a4b7d2 | ||
|
|
a68e32d95b | ||
|
|
6360809310 | ||
|
|
238200f652 | ||
|
|
d54605bd82 | ||
|
|
92803facf6 | ||
|
|
443ee26af3 | ||
|
|
395810a60b | ||
|
|
6c03fe1a4d | ||
|
|
a863ac9862 | ||
|
|
8f366babe4 | ||
|
|
083ec9325e | ||
|
|
74039fc0f1 | ||
|
|
17287bc8d0 | ||
|
|
964b14d59c | ||
|
|
d3e53eaf27 | ||
|
|
20a603de01 | ||
|
|
6453f5c395 | ||
|
|
3cf35b0710 | ||
|
|
bc5b0c82ac | ||
|
|
63b13c7e2f | ||
|
|
8ff75eaf12 | ||
|
|
76211500e8 | ||
|
|
28a05f9940 | ||
|
|
701d43892f | ||
|
|
3ae049b501 | ||
|
|
e9f70e8585 | ||
|
|
8582ed4d4f | ||
|
|
7fabe03a8b | ||
|
|
0992c5a809 | ||
|
|
5d3c898a94 | ||
|
|
0e0e395b9e | ||
|
|
7a8a39a141 | ||
|
|
b897389b87 | ||
|
|
a1e89afcc1 | ||
|
|
e4f7155369 | ||
|
|
9d9378436b | ||
|
|
141dc1af4b | ||
|
|
c83c19d9cd | ||
|
|
35dc417b18 | ||
|
|
1f3afa38e8 | ||
|
|
633f848481 | ||
|
|
24fbafa9a7 | ||
|
|
ca92597e1f | ||
|
|
c621c80afc | ||
|
|
ba4a55f6d9 | ||
|
|
511b2c91e3 | ||
|
|
b48d72a2b8 | ||
|
|
b4e2e746b3 | ||
|
|
3d5c03ec29 | ||
|
|
dc8a63cb8b | ||
|
|
5c8880ed3f | ||
|
|
01d76e4799 | ||
|
|
a393ae79d2 | ||
|
|
abcca0f9bd | ||
|
|
73c405f74a | ||
|
|
1f6a446f6c | ||
|
|
b2aff036ad | ||
|
|
58f4185925 | ||
|
|
96c9ffdedc | ||
|
|
1aeaf811b0 | ||
|
|
8e2b17e0c5 | ||
|
|
66e33abd7b | ||
|
|
29de43d307 | ||
|
|
a10603f9f0 | ||
|
|
7387bc574f | ||
|
|
bce8c0eb12 | ||
|
|
7a2c4d3cf1 | ||
|
|
9297ea48e5 | ||
|
|
147eba11fd | ||
|
|
f06dd8df06 | ||
|
|
367372f526 | ||
|
|
ad943bd8cf | ||
|
|
baf9505bfd | ||
|
|
e6c38e078a | ||
|
|
abcaa8c7a9 | ||
|
|
1295b67057 | ||
|
|
34e2425b4d | ||
|
|
f1de88c198 | ||
|
|
57ea4e8897 | ||
|
|
b5c2b1880d | ||
|
|
a64d8d2d66 | ||
|
|
37721ebd7c | ||
|
|
35988d77ec | ||
|
|
6750696874 | ||
|
|
9c29853014 | ||
|
|
8a5b139a9f | ||
|
|
b6c8c1e89d | ||
|
|
a6c68e8690 | ||
|
|
76391bba3f | ||
|
|
08886eaaa3 | ||
|
|
bbf2205640 | ||
|
|
582a4e261a | ||
|
|
83e64c1ac9 | ||
|
|
8978d16659 | ||
|
|
7a6c40872d | ||
|
|
75093ebe1c | ||
|
|
fcf08299fa | ||
|
|
1f2fb823a3 | ||
|
|
36b0070b71 | ||
|
|
230ca789e2 | ||
|
|
4f2166c503 | ||
|
|
7d89855c55 | ||
|
|
aa91f6e700 | ||
|
|
59cfff02f6 | ||
|
|
1838ab019b | ||
|
|
0ffc251704 | ||
|
|
76b5208b11 | ||
|
|
a767c584c7 | ||
|
|
8cab78abbc | ||
|
|
dcc2de15a6 | ||
|
|
1287328b6f | ||
|
|
b9b94715fa | ||
|
|
efb93d18cf | ||
|
|
c3a8a5374f | ||
|
|
247fab47ca | ||
|
|
dae00fe184 | ||
|
|
76361ae3ab | ||
|
|
e25fedf932 | ||
|
|
ddc5683c67 | ||
|
|
68ba1afb34 | ||
|
|
4b7406719c | ||
|
|
821ed35be1 | ||
|
|
ed65131c1c | ||
|
|
1766cd4123 | ||
|
|
d4ed79ffd0 | ||
|
|
86d38c2d82 | ||
|
|
88fe4de151 | ||
|
|
ee26b68fe1 | ||
|
|
a42e1c82d9 | ||
|
|
c4feb7a457 | ||
|
|
9e908ad6be | ||
|
|
3282d22dd9 | ||
|
|
952b0f8c48 | ||
|
|
e5eb9610dc | ||
|
|
2957d4306d | ||
|
|
b56e7e66cc | ||
|
|
0fc4d7f52a | ||
|
|
5ceff756e1 | ||
|
|
009b16fab8 | ||
|
|
b7e401b6b6 | ||
|
|
9c4cbaab7b | ||
|
|
15792b153f | ||
|
|
481f696a87 | ||
|
|
7a9ddcd590 | ||
|
|
f99e3ddd6d | ||
|
|
cbc405c9e3 | ||
|
|
51e72d41c2 | ||
|
|
762652279b | ||
|
|
72ea3eedc9 | ||
|
|
a00e0bc189 | ||
|
|
67945e8d62 | ||
|
|
d2a852b982 | ||
|
|
ded95d5c70 | ||
|
|
a441059761 | ||
|
|
84ac889e22 | ||
|
|
e9f0be06eb | ||
|
|
08ed62852a | ||
|
|
85dd070dea | ||
|
|
0b95efff27 | ||
|
|
3c8fa0f913 | ||
|
|
b1d25ed0dd | ||
|
|
48aaf6ce4e | ||
|
|
beafaef92f | ||
|
|
14c77f8295 | ||
|
|
bf15d0a3f5 | ||
|
|
ca47b0d79c | ||
|
|
9b1a6b30d9 | ||
|
|
310eed825e | ||
|
|
a642ca4ea8 | ||
|
|
e849df64dc | ||
|
|
e913de0720 | ||
|
|
c0a6e675a3 | ||
|
|
cc366f4baa | ||
|
|
4b1956ab49 | ||
|
|
9cb5e22861 | ||
|
|
aa6b9e3a20 | ||
|
|
c67df653b6 | ||
|
|
b17e6fdd07 | ||
|
|
b603d9c6ba | ||
|
|
5ca1b96988 | ||
|
|
a007ba7b1b | ||
|
|
ff713a41e0 | ||
|
|
9d28763753 | ||
|
|
3696532f04 | ||
|
|
cbe388ece3 | ||
|
|
034d4513d9 |
@@ -29,10 +29,12 @@ git log --oneline --left-right main...upstream/main | head -20
|
|||||||
```
|
```
|
||||||
|
|
||||||
This shows:
|
This shows:
|
||||||
|
|
||||||
- `<` = your local commits (ahead)
|
- `<` = your local commits (ahead)
|
||||||
- `>` = upstream commits you're missing (behind)
|
- `>` = upstream commits you're missing (behind)
|
||||||
|
|
||||||
**Decision point:**
|
**Decision point:**
|
||||||
|
|
||||||
- Few local commits, many upstream → **Rebase** (cleaner history)
|
- Few local commits, many upstream → **Rebase** (cleaner history)
|
||||||
- Many local commits or shared branch → **Merge** (preserves history)
|
- Many local commits or shared branch → **Merge** (preserves history)
|
||||||
|
|
||||||
@@ -70,12 +72,12 @@ git rebase --abort
|
|||||||
|
|
||||||
### Common Conflict Patterns
|
### Common Conflict Patterns
|
||||||
|
|
||||||
| File | Resolution |
|
| File | Resolution |
|
||||||
|------|------------|
|
| ---------------- | ------------------------------------------------ |
|
||||||
| `package.json` | Take upstream deps, keep local scripts if needed |
|
| `package.json` | Take upstream deps, keep local scripts if needed |
|
||||||
| `pnpm-lock.yaml` | Accept upstream, regenerate with `pnpm install` |
|
| `pnpm-lock.yaml` | Accept upstream, regenerate with `pnpm install` |
|
||||||
| `*.patch` files | Usually take upstream version |
|
| `*.patch` files | Usually take upstream version |
|
||||||
| Source files | Merge logic carefully, prefer upstream structure |
|
| Source files | Merge logic carefully, prefer upstream structure |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -88,6 +90,7 @@ git merge upstream/main --no-edit
|
|||||||
```
|
```
|
||||||
|
|
||||||
Resolve conflicts same as rebase, then:
|
Resolve conflicts same as rebase, then:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git add <resolved-files>
|
git add <resolved-files>
|
||||||
git commit
|
git commit
|
||||||
@@ -170,6 +173,7 @@ pnpm clawdbot agent --message "Verification: macOS app rebuild successful - agen
|
|||||||
Upstream updates may introduce Swift 6.2 / macOS 26 SDK incompatibilities. Use analyze-mode for systematic debugging:
|
Upstream updates may introduce Swift 6.2 / macOS 26 SDK incompatibilities. Use analyze-mode for systematic debugging:
|
||||||
|
|
||||||
### Analyze-Mode Investigation
|
### Analyze-Mode Investigation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Gather context with parallel agents
|
# Gather context with parallel agents
|
||||||
morph-mcp_warpgrep_codebase_search search_string="Find deprecated FileManager.default and Thread.isMainThread usages in Swift files" repo_path="/Volumes/Main SSD/Developer/clawdis"
|
morph-mcp_warpgrep_codebase_search search_string="Find deprecated FileManager.default and Thread.isMainThread usages in Swift files" repo_path="/Volumes/Main SSD/Developer/clawdis"
|
||||||
@@ -179,6 +183,7 @@ morph-mcp_warpgrep_codebase_search search_string="Locate Peekaboo submodule and
|
|||||||
### Common Swift 6.2 Fixes
|
### Common Swift 6.2 Fixes
|
||||||
|
|
||||||
**FileManager.default Deprecation:**
|
**FileManager.default Deprecation:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Search for deprecated usage
|
# Search for deprecated usage
|
||||||
grep -r "FileManager\.default" src/ apps/ --include="*.swift"
|
grep -r "FileManager\.default" src/ apps/ --include="*.swift"
|
||||||
@@ -189,6 +194,7 @@ grep -r "FileManager\.default" src/ apps/ --include="*.swift"
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Thread.isMainThread Deprecation:**
|
**Thread.isMainThread Deprecation:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Search for deprecated usage
|
# Search for deprecated usage
|
||||||
grep -r "Thread\.isMainThread" src/ apps/ --include="*.swift"
|
grep -r "Thread\.isMainThread" src/ apps/ --include="*.swift"
|
||||||
@@ -199,6 +205,7 @@ grep -r "Thread\.isMainThread" src/ apps/ --include="*.swift"
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Peekaboo Submodule Fixes
|
### Peekaboo Submodule Fixes
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Check Peekaboo for concurrency issues
|
# Check Peekaboo for concurrency issues
|
||||||
cd src/canvas-host/a2ui
|
cd src/canvas-host/a2ui
|
||||||
@@ -210,6 +217,7 @@ pnpm canvas:a2ui:bundle
|
|||||||
```
|
```
|
||||||
|
|
||||||
### macOS App Concurrency Fixes
|
### macOS App Concurrency Fixes
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Check macOS app for issues
|
# Check macOS app for issues
|
||||||
grep -r "Thread\.isMainThread\|FileManager\.default" apps/macos/ --include="*.swift"
|
grep -r "Thread\.isMainThread\|FileManager\.default" apps/macos/ --include="*.swift"
|
||||||
@@ -220,7 +228,9 @@ cd apps/macos && rm -rf .build .swiftpm
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Model Configuration Updates
|
### Model Configuration Updates
|
||||||
|
|
||||||
If upstream introduced new model configurations:
|
If upstream introduced new model configurations:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Check for OpenRouter API key requirements
|
# Check for OpenRouter API key requirements
|
||||||
grep -r "openrouter\|OPENROUTER" src/ --include="*.ts" --include="*.js"
|
grep -r "openrouter\|OPENROUTER" src/ --include="*.ts" --include="*.js"
|
||||||
@@ -265,6 +275,7 @@ Common issue: `fetch.preconnect` type mismatch. Fix by using `FetchLike` type in
|
|||||||
### macOS App Crashes on Launch
|
### macOS App Crashes on Launch
|
||||||
|
|
||||||
Usually resource bundle mismatch. Full rebuild required:
|
Usually resource bundle mismatch. Full rebuild required:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd apps/macos && rm -rf .build .swiftpm
|
cd apps/macos && rm -rf .build .swiftpm
|
||||||
./scripts/restart-mac.sh
|
./scripts/restart-mac.sh
|
||||||
@@ -285,12 +296,14 @@ pnpm install 2>&1 | grep -i patch
|
|||||||
**Symptoms:** Build fails with deprecation warnings about `FileManager.default` or `Thread.isMainThread`
|
**Symptoms:** Build fails with deprecation warnings about `FileManager.default` or `Thread.isMainThread`
|
||||||
|
|
||||||
**Search-Mode Investigation:**
|
**Search-Mode Investigation:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Exhaustive search for deprecated APIs
|
# Exhaustive search for deprecated APIs
|
||||||
morph-mcp_warpgrep_codebase_search search_string="Find all Swift files using deprecated FileManager.default or Thread.isMainThread" repo_path="/Volumes/Main SSD/Developer/clawdis"
|
morph-mcp_warpgrep_codebase_search search_string="Find all Swift files using deprecated FileManager.default or Thread.isMainThread" repo_path="/Volumes/Main SSD/Developer/clawdis"
|
||||||
```
|
```
|
||||||
|
|
||||||
**Quick Fix Commands:**
|
**Quick Fix Commands:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Find all affected files
|
# Find all affected files
|
||||||
find . -name "*.swift" -exec grep -l "FileManager\.default\|Thread\.isMainThread" {} \;
|
find . -name "*.swift" -exec grep -l "FileManager\.default\|Thread\.isMainThread" {} \;
|
||||||
@@ -303,6 +316,7 @@ grep -rn "Thread\.isMainThread" --include="*.swift" .
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Rebuild After Fixes:**
|
**Rebuild After Fixes:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Clean all build artifacts
|
# Clean all build artifacts
|
||||||
rm -rf apps/macos/.build apps/macos/.swiftpm
|
rm -rf apps/macos/.build apps/macos/.swiftpm
|
||||||
|
|||||||
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -1 +1 @@
|
|||||||
custom: ['https://github.com/sponsors/steipete']
|
custom: ["https://github.com/sponsors/steipete"]
|
||||||
|
|||||||
6
.github/ISSUE_TEMPLATE/bug_report.md
vendored
6
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -6,23 +6,29 @@ labels: bug
|
|||||||
---
|
---
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
What went wrong?
|
What went wrong?
|
||||||
|
|
||||||
## Steps to reproduce
|
## Steps to reproduce
|
||||||
|
|
||||||
1.
|
1.
|
||||||
2.
|
2.
|
||||||
3.
|
3.
|
||||||
|
|
||||||
## Expected behavior
|
## Expected behavior
|
||||||
|
|
||||||
What did you expect to happen?
|
What did you expect to happen?
|
||||||
|
|
||||||
## Actual behavior
|
## Actual behavior
|
||||||
|
|
||||||
What actually happened?
|
What actually happened?
|
||||||
|
|
||||||
## Environment
|
## Environment
|
||||||
|
|
||||||
- Clawdbot version:
|
- Clawdbot version:
|
||||||
- OS:
|
- OS:
|
||||||
- Install method (pnpm/npx/docker/etc):
|
- Install method (pnpm/npx/docker/etc):
|
||||||
|
|
||||||
## Logs or screenshots
|
## Logs or screenshots
|
||||||
|
|
||||||
Paste relevant logs or add screenshots (redact secrets).
|
Paste relevant logs or add screenshots (redact secrets).
|
||||||
|
|||||||
4
.github/ISSUE_TEMPLATE/feature_request.md
vendored
4
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -6,13 +6,17 @@ labels: enhancement
|
|||||||
---
|
---
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
Describe the problem you are trying to solve or the opportunity you see.
|
Describe the problem you are trying to solve or the opportunity you see.
|
||||||
|
|
||||||
## Proposed solution
|
## Proposed solution
|
||||||
|
|
||||||
What would you like Clawdbot to do?
|
What would you like Clawdbot to do?
|
||||||
|
|
||||||
## Alternatives considered
|
## Alternatives considered
|
||||||
|
|
||||||
Any other approaches you have considered?
|
Any other approaches you have considered?
|
||||||
|
|
||||||
## Additional context
|
## Additional context
|
||||||
|
|
||||||
Links, screenshots, or related issues.
|
Links, screenshots, or related issues.
|
||||||
|
|||||||
2
.github/actionlint.yaml
vendored
2
.github/actionlint.yaml
vendored
@@ -12,6 +12,6 @@ paths:
|
|||||||
.github/workflows/**/*.yml:
|
.github/workflows/**/*.yml:
|
||||||
ignore:
|
ignore:
|
||||||
# Ignore shellcheck warnings (we run shellcheck separately)
|
# Ignore shellcheck warnings (we run shellcheck separately)
|
||||||
- 'shellcheck reported issue.+'
|
- "shellcheck reported issue.+"
|
||||||
# Ignore intentional if: false for disabled jobs
|
# Ignore intentional if: false for disabled jobs
|
||||||
- 'constant expression "false" in condition'
|
- 'constant expression "false" in condition'
|
||||||
|
|||||||
55
.github/workflows/auto-response.yml
vendored
55
.github/workflows/auto-response.yml
vendored
@@ -2,25 +2,26 @@ name: Auto response
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
issues:
|
issues:
|
||||||
types: [labeled]
|
types: [opened, edited, labeled]
|
||||||
pull_request_target:
|
pull_request_target:
|
||||||
types: [labeled]
|
types: [labeled]
|
||||||
|
|
||||||
permissions:
|
permissions: {}
|
||||||
issues: write
|
|
||||||
pull-requests: write
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
auto-response:
|
auto-response:
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
pull-requests: write
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/create-github-app-token@v1
|
- uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1
|
||||||
id: app-token
|
id: app-token
|
||||||
with:
|
with:
|
||||||
app-id: "2729701"
|
app-id: "2729701"
|
||||||
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||||
- name: Handle labeled items
|
- name: Handle labeled items
|
||||||
uses: actions/github-script@v7
|
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
|
||||||
with:
|
with:
|
||||||
github-token: ${{ steps.app-token.outputs.token }}
|
github-token: ${{ steps.app-token.outputs.token }}
|
||||||
script: |
|
script: |
|
||||||
@@ -30,22 +31,49 @@ jobs:
|
|||||||
label: "r: skill",
|
label: "r: skill",
|
||||||
close: true,
|
close: true,
|
||||||
message:
|
message:
|
||||||
"Thanks for the contribution! New skills should be published to Clawdhub for everyone to use. We’re keeping the core lean on skills, so I’m closing this out.",
|
"Thanks for the contribution! New skills should be published to [Clawhub](https://clawhub.ai) for everyone to use. We’re keeping the core lean on skills, so I’m closing this out.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "r: support",
|
label: "r: support",
|
||||||
close: true,
|
close: true,
|
||||||
message:
|
message:
|
||||||
"Please use our support server https://molt.bot/discord and ask in #help or #users-helping-users to resolve this, or follow the stuck FAQ at https://docs.molt.bot/help/faq#im-stuck-whats-the-fastest-way-to-get-unstuck.",
|
"Please use [our support server](https://discord.gg/clawd) and ask in #help or #users-helping-users to resolve this, or follow the stuck FAQ at https://docs.openclaw.ai/help/faq#im-stuck-whats-the-fastest-way-to-get-unstuck.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "r: third-party-extension",
|
label: "r: third-party-extension",
|
||||||
close: true,
|
close: true,
|
||||||
message:
|
message:
|
||||||
"This would be better made as a third-party extension with our SDK that you maintain yourself. Docs: https://docs.molt.bot/plugin.",
|
"This would be better made as a third-party extension with our SDK that you maintain yourself. Docs: https://docs.openclaw.ai/plugin.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "r: moltbook",
|
||||||
|
close: true,
|
||||||
|
lock: true,
|
||||||
|
lockReason: "off-topic",
|
||||||
|
message:
|
||||||
|
"OpenClaw is not affiliated with Moltbook, and issues related to Moltbook should not be submitted here.",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const issue = context.payload.issue;
|
||||||
|
if (issue) {
|
||||||
|
const title = issue.title ?? "";
|
||||||
|
const body = issue.body ?? "";
|
||||||
|
const haystack = `${title}\n${body}`.toLowerCase();
|
||||||
|
const hasLabel = (issue.labels ?? []).some((label) =>
|
||||||
|
typeof label === "string" ? label === "r: moltbook" : label?.name === "r: moltbook",
|
||||||
|
);
|
||||||
|
if (haystack.includes("moltbook") && !hasLabel) {
|
||||||
|
await github.rest.issues.addLabels({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: issue.number,
|
||||||
|
labels: ["r: moltbook"],
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const labelName = context.payload.label?.name;
|
const labelName = context.payload.label?.name;
|
||||||
if (!labelName) {
|
if (!labelName) {
|
||||||
return;
|
return;
|
||||||
@@ -76,3 +104,12 @@ jobs:
|
|||||||
state: "closed",
|
state: "closed",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (rule.lock) {
|
||||||
|
await github.rest.issues.lock({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: issueNumber,
|
||||||
|
lock_reason: rule.lockReason ?? "resolved",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
18
.github/workflows/ci.yml
vendored
18
.github/workflows/ci.yml
vendored
@@ -71,15 +71,15 @@ jobs:
|
|||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
|
- runtime: node
|
||||||
|
task: tsgo
|
||||||
|
command: pnpm tsgo
|
||||||
- runtime: node
|
- runtime: node
|
||||||
task: lint
|
task: lint
|
||||||
command: pnpm lint
|
command: pnpm build && pnpm lint
|
||||||
- runtime: node
|
- runtime: node
|
||||||
task: test
|
task: test
|
||||||
command: pnpm canvas:a2ui:bundle && pnpm test
|
command: pnpm canvas:a2ui:bundle && pnpm test
|
||||||
- runtime: node
|
|
||||||
task: build
|
|
||||||
command: pnpm build
|
|
||||||
- runtime: node
|
- runtime: node
|
||||||
task: protocol
|
task: protocol
|
||||||
command: pnpm protocol:check
|
command: pnpm protocol:check
|
||||||
@@ -89,9 +89,6 @@ jobs:
|
|||||||
- runtime: bun
|
- runtime: bun
|
||||||
task: test
|
task: test
|
||||||
command: pnpm canvas:a2ui:bundle && bunx vitest run
|
command: pnpm canvas:a2ui:bundle && bunx vitest run
|
||||||
- runtime: bun
|
|
||||||
task: build
|
|
||||||
command: bunx tsc -p tsconfig.json
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -197,14 +194,11 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- runtime: node
|
- runtime: node
|
||||||
task: lint
|
task: build & lint
|
||||||
command: pnpm lint
|
command: pnpm build && pnpm lint
|
||||||
- runtime: node
|
- runtime: node
|
||||||
task: test
|
task: test
|
||||||
command: pnpm canvas:a2ui:bundle && pnpm test
|
command: pnpm canvas:a2ui:bundle && pnpm test
|
||||||
- runtime: node
|
|
||||||
task: build
|
|
||||||
command: pnpm build
|
|
||||||
- runtime: node
|
- runtime: node
|
||||||
task: protocol
|
task: protocol
|
||||||
command: pnpm protocol:check
|
command: pnpm protocol:check
|
||||||
|
|||||||
134
.github/workflows/formal-conformance.yml
vendored
Normal file
134
.github/workflows/formal-conformance.yml
vendored
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
name: Formal models (informational conformance)
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
formal_conformance:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 20
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout openclaw (PR)
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
path: openclaw
|
||||||
|
|
||||||
|
- name: Checkout formal models
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
repository: vignesh07/clawdbot-formal-models
|
||||||
|
ref: main
|
||||||
|
path: clawdbot-formal-models
|
||||||
|
|
||||||
|
- name: Setup Node
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: "22"
|
||||||
|
|
||||||
|
- name: Regenerate extracted constants from openclaw
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
cd clawdbot-formal-models
|
||||||
|
export OPENCLAW_REPO_DIR="${GITHUB_WORKSPACE}/openclaw"
|
||||||
|
node scripts/extract-tool-groups.mjs
|
||||||
|
node scripts/check-tool-group-alias.mjs
|
||||||
|
|
||||||
|
# Drift is about extracted artifacts only; compute it before model checking
|
||||||
|
# to avoid any incidental file touches affecting the result.
|
||||||
|
- name: Compute drift (generated/*)
|
||||||
|
id: drift
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
cd clawdbot-formal-models
|
||||||
|
|
||||||
|
if git diff --quiet -- generated; then
|
||||||
|
echo "drift=false" >> "$GITHUB_OUTPUT"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "drift=true" >> "$GITHUB_OUTPUT"
|
||||||
|
git diff -- generated > "${GITHUB_WORKSPACE}/formal-models-drift.diff"
|
||||||
|
|
||||||
|
- name: Model check (green suite)
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
cd clawdbot-formal-models
|
||||||
|
make \
|
||||||
|
precedence groups elevated nodes-policy \
|
||||||
|
attacker approvals approvals-token nodes-pipeline \
|
||||||
|
gateway-exposure gateway-exposure-v2 gateway-exposure-v2-protected \
|
||||||
|
gateway-auth-conformance gateway-auth-tailscale gateway-auth-proxy \
|
||||||
|
pairing pairing-cap pairing-idempotency pairing-refresh pairing-refresh-race \
|
||||||
|
ingress-gating ingress-idempotency ingress-dedupe-fallback ingress-trace ingress-trace2 \
|
||||||
|
routing-isolation routing-precedence routing-identitylinks routing-identity-transitive routing-identity-symmetry routing-identity-channel-override \
|
||||||
|
routing-thread-parent discord-pluralkit \
|
||||||
|
ingress-retry session-key-stability session-explosion-bound config-normalization \
|
||||||
|
queue-drain delivery-route-stability delivery-pipeline retry-termination retry-eventual-success \
|
||||||
|
no-cross-stream multi-event-eventual-emission \
|
||||||
|
dedupe-collision-fallback crash-restart-dedupe two-worker-dedupe openclaw-session-key-conformance \
|
||||||
|
routing-thread-parent-channel-override routing-trirule gateway-auth-proxy-header-spoof \
|
||||||
|
group-alias-check
|
||||||
|
|
||||||
|
- name: Model check (negative suite, expected violations)
|
||||||
|
continue-on-error: true
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
cd clawdbot-formal-models
|
||||||
|
make -k \
|
||||||
|
precedence-negative groups-negative elevated-negative nodes-policy-negative \
|
||||||
|
attacker-negative attacker-nodes-negative attacker-nodes-allowlist-negative attacker-nodes-allowlist-negative \
|
||||||
|
approvals-negative approvals-token-negative nodes-pipeline-negative \
|
||||||
|
gateway-exposure-negative gateway-exposure-v2-negative gateway-exposure-v2-protected-negative \
|
||||||
|
gateway-exposure-v2-unsafe-custom gateway-exposure-v2-unsafe-tailnet gateway-exposure-v2-unsafe-auto \
|
||||||
|
gateway-auth-conformance-negative gateway-auth-tailscale-negative gateway-auth-proxy-negative \
|
||||||
|
pairing-negative pairing-cap-negative pairing-idempotency-negative pairing-refresh-negative pairing-refresh-race-negative \
|
||||||
|
ingress-gating-negative ingress-idempotency-negative ingress-dedupe-fallback-negative ingress-trace-negative ingress-trace2-negative \
|
||||||
|
routing-isolation-negative routing-precedence-negative routing-identitylinks-negative routing-identity-transitive-negative routing-identity-symmetry-negative routing-identity-channel-override-negative \
|
||||||
|
routing-thread-parent-negative discord-pluralkit-negative \
|
||||||
|
ingress-retry-negative session-key-stability-negative config-normalization-negative \
|
||||||
|
queue-drain delivery-route-stability-negative delivery-pipeline-negative retry-termination-negative retry-eventual-success-negative \
|
||||||
|
no-cross-stream-negative multi-event-eventual-emission-negative \
|
||||||
|
dedupe-collision-fallback-negative crash-restart-dedupe-negative two-worker-dedupe-negative openclaw-session-key-conformance-negative \
|
||||||
|
routing-thread-parent-channel-override-negative routing-trirule-negative gateway-auth-proxy-header-spoof-negative
|
||||||
|
|
||||||
|
- name: Upload drift diff artifact
|
||||||
|
if: steps.drift.outputs.drift == 'true'
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: formal-models-conformance-drift
|
||||||
|
path: formal-models-drift.diff
|
||||||
|
|
||||||
|
- name: Comment on PR (informational)
|
||||||
|
if: steps.drift.outputs.drift == 'true'
|
||||||
|
uses: actions/github-script@v7
|
||||||
|
with:
|
||||||
|
script: |
|
||||||
|
const body = [
|
||||||
|
'⚠️ **Formal models conformance drift detected**',
|
||||||
|
'',
|
||||||
|
'The formal models extracted constants (`generated/*`) do not match this openclaw PR.',
|
||||||
|
'',
|
||||||
|
'This check is **informational** (not blocking merges yet).',
|
||||||
|
'See the `formal-models-conformance-drift` artifact for the diff.',
|
||||||
|
'',
|
||||||
|
'If this change is intentional, follow up by updating the formal models repo or regenerating the extracted artifacts there.',
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
await github.rest.issues.createComment({
|
||||||
|
owner: context.repo.owner,
|
||||||
|
repo: context.repo.repo,
|
||||||
|
issue_number: context.payload.pull_request.number,
|
||||||
|
body,
|
||||||
|
});
|
||||||
|
|
||||||
|
- name: Summary
|
||||||
|
run: |
|
||||||
|
if [ "${{ steps.drift.outputs.drift }}" = "true" ]; then
|
||||||
|
echo "Formal conformance drift detected (informational)."
|
||||||
|
else
|
||||||
|
echo "Formal conformance: no drift."
|
||||||
|
fi
|
||||||
66
.github/workflows/labeler.yml
vendored
66
.github/workflows/labeler.yml
vendored
@@ -3,22 +3,78 @@ name: Labeler
|
|||||||
on:
|
on:
|
||||||
pull_request_target:
|
pull_request_target:
|
||||||
types: [opened, synchronize, reopened]
|
types: [opened, synchronize, reopened]
|
||||||
|
issues:
|
||||||
|
types: [opened]
|
||||||
|
|
||||||
permissions:
|
permissions: {}
|
||||||
contents: read
|
|
||||||
pull-requests: write
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
label:
|
label:
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
pull-requests: write
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/create-github-app-token@v1
|
- uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1
|
||||||
id: app-token
|
id: app-token
|
||||||
with:
|
with:
|
||||||
app-id: "2729701"
|
app-id: "2729701"
|
||||||
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||||
- uses: actions/labeler@v5
|
- uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5
|
||||||
with:
|
with:
|
||||||
configuration-path: .github/labeler.yml
|
configuration-path: .github/labeler.yml
|
||||||
repo-token: ${{ steps.app-token.outputs.token }}
|
repo-token: ${{ steps.app-token.outputs.token }}
|
||||||
sync-labels: true
|
sync-labels: true
|
||||||
|
- name: Apply maintainer label for org members
|
||||||
|
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
|
||||||
|
with:
|
||||||
|
github-token: ${{ steps.app-token.outputs.token }}
|
||||||
|
script: |
|
||||||
|
const association = context.payload.pull_request?.author_association;
|
||||||
|
if (!association) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (![
|
||||||
|
"MEMBER",
|
||||||
|
"OWNER",
|
||||||
|
].includes(association)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await github.rest.issues.addLabels({
|
||||||
|
...context.repo,
|
||||||
|
issue_number: context.payload.pull_request.number,
|
||||||
|
labels: ["maintainer"],
|
||||||
|
});
|
||||||
|
|
||||||
|
label-issues:
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1
|
||||||
|
id: app-token
|
||||||
|
with:
|
||||||
|
app-id: "2729701"
|
||||||
|
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
|
||||||
|
- name: Apply maintainer label for org members
|
||||||
|
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
|
||||||
|
with:
|
||||||
|
github-token: ${{ steps.app-token.outputs.token }}
|
||||||
|
script: |
|
||||||
|
const association = context.payload.issue?.author_association;
|
||||||
|
if (!association) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (![
|
||||||
|
"MEMBER",
|
||||||
|
"OWNER",
|
||||||
|
].includes(association)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await github.rest.issues.addLabels({
|
||||||
|
...context.repo,
|
||||||
|
issue_number: context.payload.issue.number,
|
||||||
|
labels: ["maintainer"],
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,5 +1,20 @@
|
|||||||
{
|
{
|
||||||
"$schema": "./node_modules/oxfmt/configuration_schema.json",
|
"$schema": "./node_modules/oxfmt/configuration_schema.json",
|
||||||
"indentWidth": 2,
|
"experimentalSortImports": {
|
||||||
"printWidth": 100
|
"newlinesBetween": false,
|
||||||
|
},
|
||||||
|
"experimentalSortPackageJson": {
|
||||||
|
"sortScripts": true,
|
||||||
|
},
|
||||||
|
"ignorePatterns": [
|
||||||
|
"apps/",
|
||||||
|
"assets/",
|
||||||
|
"dist/",
|
||||||
|
"docs/_layouts/",
|
||||||
|
"node_modules/",
|
||||||
|
"patches/",
|
||||||
|
"pnpm-lock.yaml/",
|
||||||
|
"Swabble/",
|
||||||
|
"vendor/",
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,35 @@
|
|||||||
{
|
{
|
||||||
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
||||||
"plugins": [
|
"plugins": ["unicorn", "typescript", "oxc"],
|
||||||
"unicorn",
|
|
||||||
"typescript",
|
|
||||||
"oxc"
|
|
||||||
],
|
|
||||||
"categories": {
|
"categories": {
|
||||||
"correctness": "error"
|
"correctness": "error",
|
||||||
|
"perf": "error",
|
||||||
|
"suspicious": "error"
|
||||||
},
|
},
|
||||||
"ignorePatterns": ["src/canvas-host/a2ui/a2ui.bundle.js"]
|
"rules": {
|
||||||
|
"curly": "error",
|
||||||
|
"eslint-plugin-unicorn/prefer-array-find": "off",
|
||||||
|
"eslint/no-await-in-loop": "off",
|
||||||
|
"eslint/no-new": "off",
|
||||||
|
"oxc/no-accumulating-spread": "off",
|
||||||
|
"oxc/no-async-endpoint-handlers": "off",
|
||||||
|
"oxc/no-map-spread": "off",
|
||||||
|
"typescript/no-explicit-any": "error",
|
||||||
|
"typescript/no-extraneous-class": "off",
|
||||||
|
"typescript/no-unsafe-type-assertion": "off",
|
||||||
|
"unicorn/consistent-function-scoping": "off",
|
||||||
|
"unicorn/require-post-message-target-origin": "off"
|
||||||
|
},
|
||||||
|
"ignorePatterns": [
|
||||||
|
"assets/",
|
||||||
|
"dist/",
|
||||||
|
"docs/_layouts/",
|
||||||
|
"node_modules/",
|
||||||
|
"patches/",
|
||||||
|
"pnpm-lock.yaml/",
|
||||||
|
"skills/",
|
||||||
|
"src/canvas-host/a2ui/a2ui.bundle.js",
|
||||||
|
"Swabble/",
|
||||||
|
"vendor/"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
195
.pi/extensions/diff.ts
Normal file
195
.pi/extensions/diff.ts
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
/**
|
||||||
|
* Diff Extension
|
||||||
|
*
|
||||||
|
* /diff command shows modified/deleted/new files from git status and opens
|
||||||
|
* the selected file in VS Code's diff view.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
||||||
|
import { DynamicBorder } from "@mariozechner/pi-coding-agent";
|
||||||
|
import {
|
||||||
|
Container,
|
||||||
|
Key,
|
||||||
|
matchesKey,
|
||||||
|
type SelectItem,
|
||||||
|
SelectList,
|
||||||
|
Text,
|
||||||
|
} from "@mariozechner/pi-tui";
|
||||||
|
|
||||||
|
interface FileInfo {
|
||||||
|
status: string;
|
||||||
|
statusLabel: string;
|
||||||
|
file: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function (pi: ExtensionAPI) {
|
||||||
|
pi.registerCommand("diff", {
|
||||||
|
description: "Show git changes and open in VS Code diff view",
|
||||||
|
handler: async (_args, ctx) => {
|
||||||
|
if (!ctx.hasUI) {
|
||||||
|
ctx.ui.notify("No UI available", "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get changed files from git status
|
||||||
|
const result = await pi.exec("git", ["status", "--porcelain"], { cwd: ctx.cwd });
|
||||||
|
|
||||||
|
if (result.code !== 0) {
|
||||||
|
ctx.ui.notify(`git status failed: ${result.stderr}`, "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result.stdout || !result.stdout.trim()) {
|
||||||
|
ctx.ui.notify("No changes in working tree", "info");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse git status output
|
||||||
|
// Format: XY filename (where XY is two-letter status, then space, then filename)
|
||||||
|
const lines = result.stdout.split("\n");
|
||||||
|
const files: FileInfo[] = [];
|
||||||
|
|
||||||
|
for (const line of lines) {
|
||||||
|
if (line.length < 4) {
|
||||||
|
continue;
|
||||||
|
} // Need at least "XY f"
|
||||||
|
|
||||||
|
const status = line.slice(0, 2);
|
||||||
|
const file = line.slice(2).trimStart();
|
||||||
|
|
||||||
|
// Translate status codes to short labels
|
||||||
|
let statusLabel: string;
|
||||||
|
if (status.includes("M")) {
|
||||||
|
statusLabel = "M";
|
||||||
|
} else if (status.includes("A")) {
|
||||||
|
statusLabel = "A";
|
||||||
|
} else if (status.includes("D")) {
|
||||||
|
statusLabel = "D";
|
||||||
|
} else if (status.includes("?")) {
|
||||||
|
statusLabel = "?";
|
||||||
|
} else if (status.includes("R")) {
|
||||||
|
statusLabel = "R";
|
||||||
|
} else if (status.includes("C")) {
|
||||||
|
statusLabel = "C";
|
||||||
|
} else {
|
||||||
|
statusLabel = status.trim() || "~";
|
||||||
|
}
|
||||||
|
|
||||||
|
files.push({ status: statusLabel, statusLabel, file });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (files.length === 0) {
|
||||||
|
ctx.ui.notify("No changes found", "info");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const openSelected = async (fileInfo: FileInfo): Promise<void> => {
|
||||||
|
try {
|
||||||
|
// Open in VS Code diff view.
|
||||||
|
// For untracked files, git difftool won't work, so fall back to just opening the file.
|
||||||
|
if (fileInfo.status === "?") {
|
||||||
|
await pi.exec("code", ["-g", fileInfo.file], { cwd: ctx.cwd });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const diffResult = await pi.exec(
|
||||||
|
"git",
|
||||||
|
["difftool", "-y", "--tool=vscode", fileInfo.file],
|
||||||
|
{
|
||||||
|
cwd: ctx.cwd,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (diffResult.code !== 0) {
|
||||||
|
await pi.exec("code", ["-g", fileInfo.file], { cwd: ctx.cwd });
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
|
ctx.ui.notify(`Failed to open ${fileInfo.file}: ${message}`, "error");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Show file picker with SelectList
|
||||||
|
await ctx.ui.custom<void>((tui, theme, _kb, done) => {
|
||||||
|
const container = new Container();
|
||||||
|
|
||||||
|
// Top border
|
||||||
|
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
||||||
|
|
||||||
|
// Title
|
||||||
|
container.addChild(new Text(theme.fg("accent", theme.bold(" Select file to diff")), 0, 0));
|
||||||
|
|
||||||
|
// Build select items with colored status
|
||||||
|
const items: SelectItem[] = files.map((f) => {
|
||||||
|
let statusColor: string;
|
||||||
|
switch (f.status) {
|
||||||
|
case "M":
|
||||||
|
statusColor = theme.fg("warning", f.status);
|
||||||
|
break;
|
||||||
|
case "A":
|
||||||
|
statusColor = theme.fg("success", f.status);
|
||||||
|
break;
|
||||||
|
case "D":
|
||||||
|
statusColor = theme.fg("error", f.status);
|
||||||
|
break;
|
||||||
|
case "?":
|
||||||
|
statusColor = theme.fg("muted", f.status);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
statusColor = theme.fg("dim", f.status);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
value: f,
|
||||||
|
label: `${statusColor} ${f.file}`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const visibleRows = Math.min(files.length, 15);
|
||||||
|
let currentIndex = 0;
|
||||||
|
|
||||||
|
const selectList = new SelectList(items, visibleRows, {
|
||||||
|
selectedPrefix: (t) => theme.fg("accent", t),
|
||||||
|
selectedText: (t) => t, // Keep existing colors
|
||||||
|
description: (t) => theme.fg("muted", t),
|
||||||
|
scrollInfo: (t) => theme.fg("dim", t),
|
||||||
|
noMatch: (t) => theme.fg("warning", t),
|
||||||
|
});
|
||||||
|
selectList.onSelect = (item) => {
|
||||||
|
void openSelected(item.value as FileInfo);
|
||||||
|
};
|
||||||
|
selectList.onCancel = () => done();
|
||||||
|
selectList.onSelectionChange = (item) => {
|
||||||
|
currentIndex = items.indexOf(item);
|
||||||
|
};
|
||||||
|
container.addChild(selectList);
|
||||||
|
|
||||||
|
// Help text
|
||||||
|
container.addChild(
|
||||||
|
new Text(theme.fg("dim", " ↑↓ navigate • ←→ page • enter open • esc close"), 0, 0),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Bottom border
|
||||||
|
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
||||||
|
|
||||||
|
return {
|
||||||
|
render: (w) => container.render(w),
|
||||||
|
invalidate: () => container.invalidate(),
|
||||||
|
handleInput: (data) => {
|
||||||
|
// Add paging with left/right
|
||||||
|
if (matchesKey(data, Key.left)) {
|
||||||
|
// Page up - clamp to 0
|
||||||
|
currentIndex = Math.max(0, currentIndex - visibleRows);
|
||||||
|
selectList.setSelectedIndex(currentIndex);
|
||||||
|
} else if (matchesKey(data, Key.right)) {
|
||||||
|
// Page down - clamp to last
|
||||||
|
currentIndex = Math.min(items.length - 1, currentIndex + visibleRows);
|
||||||
|
selectList.setSelectedIndex(currentIndex);
|
||||||
|
} else {
|
||||||
|
selectList.handleInput(data);
|
||||||
|
}
|
||||||
|
tui.requestRender();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
194
.pi/extensions/files.ts
Normal file
194
.pi/extensions/files.ts
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
/**
|
||||||
|
* Files Extension
|
||||||
|
*
|
||||||
|
* /files command lists all files the model has read/written/edited in the active session branch,
|
||||||
|
* coalesced by path and sorted newest first. Selecting a file opens it in VS Code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
||||||
|
import { DynamicBorder } from "@mariozechner/pi-coding-agent";
|
||||||
|
import {
|
||||||
|
Container,
|
||||||
|
Key,
|
||||||
|
matchesKey,
|
||||||
|
type SelectItem,
|
||||||
|
SelectList,
|
||||||
|
Text,
|
||||||
|
} from "@mariozechner/pi-tui";
|
||||||
|
|
||||||
|
interface FileEntry {
|
||||||
|
path: string;
|
||||||
|
operations: Set<"read" | "write" | "edit">;
|
||||||
|
lastTimestamp: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
type FileToolName = "read" | "write" | "edit";
|
||||||
|
|
||||||
|
export default function (pi: ExtensionAPI) {
|
||||||
|
pi.registerCommand("files", {
|
||||||
|
description: "Show files read/written/edited in this session",
|
||||||
|
handler: async (_args, ctx) => {
|
||||||
|
if (!ctx.hasUI) {
|
||||||
|
ctx.ui.notify("No UI available", "error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the current branch (path from leaf to root)
|
||||||
|
const branch = ctx.sessionManager.getBranch();
|
||||||
|
|
||||||
|
// First pass: collect tool calls (id -> {path, name}) from assistant messages
|
||||||
|
const toolCalls = new Map<string, { path: string; name: FileToolName; timestamp: number }>();
|
||||||
|
|
||||||
|
for (const entry of branch) {
|
||||||
|
if (entry.type !== "message") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const msg = entry.message;
|
||||||
|
|
||||||
|
if (msg.role === "assistant" && Array.isArray(msg.content)) {
|
||||||
|
for (const block of msg.content) {
|
||||||
|
if (block.type === "toolCall") {
|
||||||
|
const name = block.name;
|
||||||
|
if (name === "read" || name === "write" || name === "edit") {
|
||||||
|
const path = block.arguments?.path;
|
||||||
|
if (path && typeof path === "string") {
|
||||||
|
toolCalls.set(block.id, { path, name, timestamp: msg.timestamp });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second pass: match tool results to get the actual execution timestamp
|
||||||
|
const fileMap = new Map<string, FileEntry>();
|
||||||
|
|
||||||
|
for (const entry of branch) {
|
||||||
|
if (entry.type !== "message") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const msg = entry.message;
|
||||||
|
|
||||||
|
if (msg.role === "toolResult") {
|
||||||
|
const toolCall = toolCalls.get(msg.toolCallId);
|
||||||
|
if (!toolCall) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { path, name } = toolCall;
|
||||||
|
const timestamp = msg.timestamp;
|
||||||
|
|
||||||
|
const existing = fileMap.get(path);
|
||||||
|
if (existing) {
|
||||||
|
existing.operations.add(name);
|
||||||
|
if (timestamp > existing.lastTimestamp) {
|
||||||
|
existing.lastTimestamp = timestamp;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fileMap.set(path, {
|
||||||
|
path,
|
||||||
|
operations: new Set([name]),
|
||||||
|
lastTimestamp: timestamp,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileMap.size === 0) {
|
||||||
|
ctx.ui.notify("No files read/written/edited in this session", "info");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort by most recent first
|
||||||
|
const files = Array.from(fileMap.values()).toSorted(
|
||||||
|
(a, b) => b.lastTimestamp - a.lastTimestamp,
|
||||||
|
);
|
||||||
|
|
||||||
|
const openSelected = async (file: FileEntry): Promise<void> => {
|
||||||
|
try {
|
||||||
|
await pi.exec("code", ["-g", file.path], { cwd: ctx.cwd });
|
||||||
|
} catch (error) {
|
||||||
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
|
ctx.ui.notify(`Failed to open ${file.path}: ${message}`, "error");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Show file picker with SelectList
|
||||||
|
await ctx.ui.custom<void>((tui, theme, _kb, done) => {
|
||||||
|
const container = new Container();
|
||||||
|
|
||||||
|
// Top border
|
||||||
|
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
||||||
|
|
||||||
|
// Title
|
||||||
|
container.addChild(new Text(theme.fg("accent", theme.bold(" Select file to open")), 0, 0));
|
||||||
|
|
||||||
|
// Build select items with colored operations
|
||||||
|
const items: SelectItem[] = files.map((f) => {
|
||||||
|
const ops: string[] = [];
|
||||||
|
if (f.operations.has("read")) {
|
||||||
|
ops.push(theme.fg("muted", "R"));
|
||||||
|
}
|
||||||
|
if (f.operations.has("write")) {
|
||||||
|
ops.push(theme.fg("success", "W"));
|
||||||
|
}
|
||||||
|
if (f.operations.has("edit")) {
|
||||||
|
ops.push(theme.fg("warning", "E"));
|
||||||
|
}
|
||||||
|
const opsLabel = ops.join("");
|
||||||
|
return {
|
||||||
|
value: f,
|
||||||
|
label: `${opsLabel} ${f.path}`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const visibleRows = Math.min(files.length, 15);
|
||||||
|
let currentIndex = 0;
|
||||||
|
|
||||||
|
const selectList = new SelectList(items, visibleRows, {
|
||||||
|
selectedPrefix: (t) => theme.fg("accent", t),
|
||||||
|
selectedText: (t) => t, // Keep existing colors
|
||||||
|
description: (t) => theme.fg("muted", t),
|
||||||
|
scrollInfo: (t) => theme.fg("dim", t),
|
||||||
|
noMatch: (t) => theme.fg("warning", t),
|
||||||
|
});
|
||||||
|
selectList.onSelect = (item) => {
|
||||||
|
void openSelected(item.value as FileEntry);
|
||||||
|
};
|
||||||
|
selectList.onCancel = () => done();
|
||||||
|
selectList.onSelectionChange = (item) => {
|
||||||
|
currentIndex = items.indexOf(item);
|
||||||
|
};
|
||||||
|
container.addChild(selectList);
|
||||||
|
|
||||||
|
// Help text
|
||||||
|
container.addChild(
|
||||||
|
new Text(theme.fg("dim", " ↑↓ navigate • ←→ page • enter open • esc close"), 0, 0),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Bottom border
|
||||||
|
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
||||||
|
|
||||||
|
return {
|
||||||
|
render: (w) => container.render(w),
|
||||||
|
invalidate: () => container.invalidate(),
|
||||||
|
handleInput: (data) => {
|
||||||
|
// Add paging with left/right
|
||||||
|
if (matchesKey(data, Key.left)) {
|
||||||
|
// Page up - clamp to 0
|
||||||
|
currentIndex = Math.max(0, currentIndex - visibleRows);
|
||||||
|
selectList.setSelectedIndex(currentIndex);
|
||||||
|
} else if (matchesKey(data, Key.right)) {
|
||||||
|
// Page down - clamp to last
|
||||||
|
currentIndex = Math.min(items.length - 1, currentIndex + visibleRows);
|
||||||
|
selectList.setSelectedIndex(currentIndex);
|
||||||
|
} else {
|
||||||
|
selectList.handleInput(data);
|
||||||
|
}
|
||||||
|
tui.requestRender();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
193
.pi/extensions/prompt-url-widget.ts
Normal file
193
.pi/extensions/prompt-url-widget.ts
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
import {
|
||||||
|
DynamicBorder,
|
||||||
|
type ExtensionAPI,
|
||||||
|
type ExtensionContext,
|
||||||
|
} from "@mariozechner/pi-coding-agent";
|
||||||
|
import { Container, Text } from "@mariozechner/pi-tui";
|
||||||
|
|
||||||
|
const PR_PROMPT_PATTERN = /^\s*You are given one or more GitHub PR URLs:\s*(\S+)/im;
|
||||||
|
const ISSUE_PROMPT_PATTERN = /^\s*Analyze GitHub issue\(s\):\s*(\S+)/im;
|
||||||
|
|
||||||
|
type PromptMatch = {
|
||||||
|
kind: "pr" | "issue";
|
||||||
|
url: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type GhMetadata = {
|
||||||
|
title?: string;
|
||||||
|
author?: {
|
||||||
|
login?: string;
|
||||||
|
name?: string | null;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
function extractPromptMatch(prompt: string): PromptMatch | undefined {
|
||||||
|
const prMatch = prompt.match(PR_PROMPT_PATTERN);
|
||||||
|
if (prMatch?.[1]) {
|
||||||
|
return { kind: "pr", url: prMatch[1].trim() };
|
||||||
|
}
|
||||||
|
|
||||||
|
const issueMatch = prompt.match(ISSUE_PROMPT_PATTERN);
|
||||||
|
if (issueMatch?.[1]) {
|
||||||
|
return { kind: "issue", url: issueMatch[1].trim() };
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchGhMetadata(
|
||||||
|
pi: ExtensionAPI,
|
||||||
|
kind: PromptMatch["kind"],
|
||||||
|
url: string,
|
||||||
|
): Promise<GhMetadata | undefined> {
|
||||||
|
const args =
|
||||||
|
kind === "pr"
|
||||||
|
? ["pr", "view", url, "--json", "title,author"]
|
||||||
|
: ["issue", "view", url, "--json", "title,author"];
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await pi.exec("gh", args);
|
||||||
|
if (result.code !== 0 || !result.stdout) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return JSON.parse(result.stdout) as GhMetadata;
|
||||||
|
} catch {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatAuthor(author?: GhMetadata["author"]): string | undefined {
|
||||||
|
if (!author) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const name = author.name?.trim();
|
||||||
|
const login = author.login?.trim();
|
||||||
|
if (name && login) {
|
||||||
|
return `${name} (@${login})`;
|
||||||
|
}
|
||||||
|
if (login) {
|
||||||
|
return `@${login}`;
|
||||||
|
}
|
||||||
|
if (name) {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function promptUrlWidgetExtension(pi: ExtensionAPI) {
|
||||||
|
const setWidget = (
|
||||||
|
ctx: ExtensionContext,
|
||||||
|
match: PromptMatch,
|
||||||
|
title?: string,
|
||||||
|
authorText?: string,
|
||||||
|
) => {
|
||||||
|
ctx.ui.setWidget("prompt-url", (_tui, thm) => {
|
||||||
|
const titleText = title ? thm.fg("accent", title) : thm.fg("accent", match.url);
|
||||||
|
const authorLine = authorText ? thm.fg("muted", authorText) : undefined;
|
||||||
|
const urlLine = thm.fg("dim", match.url);
|
||||||
|
|
||||||
|
const lines = [titleText];
|
||||||
|
if (authorLine) {
|
||||||
|
lines.push(authorLine);
|
||||||
|
}
|
||||||
|
lines.push(urlLine);
|
||||||
|
|
||||||
|
const container = new Container();
|
||||||
|
container.addChild(new DynamicBorder((s: string) => thm.fg("muted", s)));
|
||||||
|
container.addChild(new Text(lines.join("\n"), 1, 0));
|
||||||
|
return container;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const applySessionName = (ctx: ExtensionContext, match: PromptMatch, title?: string) => {
|
||||||
|
const label = match.kind === "pr" ? "PR" : "Issue";
|
||||||
|
const trimmedTitle = title?.trim();
|
||||||
|
const fallbackName = `${label}: ${match.url}`;
|
||||||
|
const desiredName = trimmedTitle ? `${label}: ${trimmedTitle} (${match.url})` : fallbackName;
|
||||||
|
const currentName = pi.getSessionName()?.trim();
|
||||||
|
if (!currentName) {
|
||||||
|
pi.setSessionName(desiredName);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (currentName === match.url || currentName === fallbackName) {
|
||||||
|
pi.setSessionName(desiredName);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
pi.on("before_agent_start", async (event, ctx) => {
|
||||||
|
if (!ctx.hasUI) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const match = extractPromptMatch(event.prompt);
|
||||||
|
if (!match) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setWidget(ctx, match);
|
||||||
|
applySessionName(ctx, match);
|
||||||
|
void fetchGhMetadata(pi, match.kind, match.url).then((meta) => {
|
||||||
|
const title = meta?.title?.trim();
|
||||||
|
const authorText = formatAuthor(meta?.author);
|
||||||
|
setWidget(ctx, match, title, authorText);
|
||||||
|
applySessionName(ctx, match, title);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
pi.on("session_switch", async (_event, ctx) => {
|
||||||
|
rebuildFromSession(ctx);
|
||||||
|
});
|
||||||
|
|
||||||
|
const getUserText = (content: string | { type: string; text?: string }[] | undefined): string => {
|
||||||
|
if (!content) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
if (typeof content === "string") {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
content
|
||||||
|
.filter((block): block is { type: "text"; text: string } => block.type === "text")
|
||||||
|
.map((block) => block.text)
|
||||||
|
.join("\n") ?? ""
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const rebuildFromSession = (ctx: ExtensionContext) => {
|
||||||
|
if (!ctx.hasUI) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const entries = ctx.sessionManager.getEntries();
|
||||||
|
const lastMatch = [...entries].toReversed().find((entry) => {
|
||||||
|
if (entry.type !== "message" || entry.message.role !== "user") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const text = getUserText(entry.message.content);
|
||||||
|
return !!extractPromptMatch(text);
|
||||||
|
});
|
||||||
|
|
||||||
|
const content =
|
||||||
|
lastMatch?.type === "message" && lastMatch.message.role === "user"
|
||||||
|
? lastMatch.message.content
|
||||||
|
: undefined;
|
||||||
|
const text = getUserText(content);
|
||||||
|
const match = text ? extractPromptMatch(text) : undefined;
|
||||||
|
if (!match) {
|
||||||
|
ctx.ui.setWidget("prompt-url", undefined);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setWidget(ctx, match);
|
||||||
|
applySessionName(ctx, match);
|
||||||
|
void fetchGhMetadata(pi, match.kind, match.url).then((meta) => {
|
||||||
|
const title = meta?.title?.trim();
|
||||||
|
const authorText = formatAuthor(meta?.author);
|
||||||
|
setWidget(ctx, match, title, authorText);
|
||||||
|
applySessionName(ctx, match, title);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
pi.on("session_start", async (_event, ctx) => {
|
||||||
|
rebuildFromSession(ctx);
|
||||||
|
});
|
||||||
|
}
|
||||||
26
.pi/extensions/redraws.ts
Normal file
26
.pi/extensions/redraws.ts
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* Redraws Extension
|
||||||
|
*
|
||||||
|
* Exposes /tui to show TUI redraw stats.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
||||||
|
import { Text } from "@mariozechner/pi-tui";
|
||||||
|
|
||||||
|
export default function (pi: ExtensionAPI) {
|
||||||
|
pi.registerCommand("tui", {
|
||||||
|
description: "Show TUI stats",
|
||||||
|
handler: async (_args, ctx) => {
|
||||||
|
if (!ctx.hasUI) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let redraws = 0;
|
||||||
|
await ctx.ui.custom<void>((tui, _theme, _keybindings, done) => {
|
||||||
|
redraws = tui.fullRedraws;
|
||||||
|
done(undefined);
|
||||||
|
return new Text("", 0, 0);
|
||||||
|
});
|
||||||
|
ctx.ui.notify(`TUI full redraws: ${redraws}`, "info");
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
2
.pi/git/.gitignore
vendored
Normal file
2
.pi/git/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
||||||
58
.pi/prompts/cl.md
Normal file
58
.pi/prompts/cl.md
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
---
|
||||||
|
description: Audit changelog entries before release
|
||||||
|
---
|
||||||
|
|
||||||
|
Audit changelog entries for all commits since the last release.
|
||||||
|
|
||||||
|
## Process
|
||||||
|
|
||||||
|
1. **Find the last release tag:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git tag --sort=-version:refname | head -1
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **List all commits since that tag:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git log <tag>..HEAD --oneline
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Read each package's [Unreleased] section:**
|
||||||
|
- packages/ai/CHANGELOG.md
|
||||||
|
- packages/tui/CHANGELOG.md
|
||||||
|
- packages/coding-agent/CHANGELOG.md
|
||||||
|
|
||||||
|
4. **For each commit, check:**
|
||||||
|
- Skip: changelog updates, doc-only changes, release housekeeping
|
||||||
|
- Determine which package(s) the commit affects (use `git show <hash> --stat`)
|
||||||
|
- Verify a changelog entry exists in the affected package(s)
|
||||||
|
- For external contributions (PRs), verify format: `Description ([#N](url) by [@user](url))`
|
||||||
|
|
||||||
|
5. **Cross-package duplication rule:**
|
||||||
|
Changes in `ai`, `agent` or `tui` that affect end users should be duplicated to `coding-agent` changelog, since coding-agent is the user-facing package that depends on them.
|
||||||
|
|
||||||
|
6. **Add New Features section after changelog fixes:**
|
||||||
|
- Insert a `### New Features` section at the start of `## [Unreleased]` in `packages/coding-agent/CHANGELOG.md`.
|
||||||
|
- Propose the top new features to the user for confirmation before writing them.
|
||||||
|
- Link to relevant docs and sections whenever possible.
|
||||||
|
|
||||||
|
7. **Report:**
|
||||||
|
- List commits with missing entries
|
||||||
|
- List entries that need cross-package duplication
|
||||||
|
- Add any missing entries directly
|
||||||
|
|
||||||
|
## Changelog Format Reference
|
||||||
|
|
||||||
|
Sections (in order):
|
||||||
|
|
||||||
|
- `### Breaking Changes` - API changes requiring migration
|
||||||
|
- `### Added` - New features
|
||||||
|
- `### Changed` - Changes to existing functionality
|
||||||
|
- `### Fixed` - Bug fixes
|
||||||
|
- `### Removed` - Removed features
|
||||||
|
|
||||||
|
Attribution:
|
||||||
|
|
||||||
|
- Internal: `Fixed foo ([#123](https://github.com/badlogic/pi-mono/issues/123))`
|
||||||
|
- External: `Added bar ([#456](https://github.com/badlogic/pi-mono/pull/456) by [@user](https://github.com/user))`
|
||||||
22
.pi/prompts/is.md
Normal file
22
.pi/prompts/is.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
description: Analyze GitHub issues (bugs or feature requests)
|
||||||
|
---
|
||||||
|
|
||||||
|
Analyze GitHub issue(s): $ARGUMENTS
|
||||||
|
|
||||||
|
For each issue:
|
||||||
|
|
||||||
|
1. Read the issue in full, including all comments and linked issues/PRs.
|
||||||
|
|
||||||
|
2. **For bugs**:
|
||||||
|
- Ignore any root cause analysis in the issue (likely wrong)
|
||||||
|
- Read all related code files in full (no truncation)
|
||||||
|
- Trace the code path and identify the actual root cause
|
||||||
|
- Propose a fix
|
||||||
|
|
||||||
|
3. **For feature requests**:
|
||||||
|
- Read all related code files in full (no truncation)
|
||||||
|
- Propose the most concise implementation approach
|
||||||
|
- List affected files and changes needed
|
||||||
|
|
||||||
|
Do NOT implement unless explicitly asked. Analyze and propose only.
|
||||||
105
.pi/prompts/landpr.md
Normal file
105
.pi/prompts/landpr.md
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
---
|
||||||
|
description: Land a PR (merge with proper workflow)
|
||||||
|
---
|
||||||
|
|
||||||
|
Input
|
||||||
|
|
||||||
|
- PR: $1 <number|url>
|
||||||
|
- If missing: use the most recent PR mentioned in the conversation.
|
||||||
|
- If ambiguous: ask.
|
||||||
|
|
||||||
|
Do (review-only)
|
||||||
|
Goal: produce a thorough review and a clear recommendation (READY for /landpr vs NEEDS WORK). Do NOT merge, do NOT push, do NOT make changes in the repo as part of this command.
|
||||||
|
|
||||||
|
1. Identify PR meta + context
|
||||||
|
|
||||||
|
```sh
|
||||||
|
gh pr view <PR> --json number,title,state,isDraft,author,baseRefName,headRefName,headRepository,url,body,labels,assignees,reviewRequests,files,additions,deletions --jq '{number,title,url,state,isDraft,author:.author.login,base:.baseRefName,head:.headRefName,headRepo:.headRepository.nameWithOwner,additions,deletions,files:.files|length}'
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Read the PR description carefully
|
||||||
|
- Summarize the stated goal, scope, and any “why now?” rationale.
|
||||||
|
- Call out any missing context: motivation, alternatives considered, rollout/compat notes, risk.
|
||||||
|
|
||||||
|
3. Read the diff thoroughly (prefer full diff)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
gh pr diff <PR>
|
||||||
|
# If you need more surrounding context for files:
|
||||||
|
gh pr checkout <PR> # optional; still review-only
|
||||||
|
git show --stat
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Validate the change is needed / valuable
|
||||||
|
- What user/customer/dev pain does this solve?
|
||||||
|
- Is this change the smallest reasonable fix?
|
||||||
|
- Are we introducing complexity for marginal benefit?
|
||||||
|
- Are we changing behavior/contract in a way that needs docs or a release note?
|
||||||
|
|
||||||
|
5. Evaluate implementation quality + optimality
|
||||||
|
- Correctness: edge cases, error handling, null/undefined, concurrency, ordering.
|
||||||
|
- Design: is the abstraction/architecture appropriate or over/under-engineered?
|
||||||
|
- Performance: hot paths, allocations, queries, network, N+1s, caching.
|
||||||
|
- Security/privacy: authz/authn, input validation, secrets, logging PII.
|
||||||
|
- Backwards compatibility: public APIs, config, migrations.
|
||||||
|
- Style consistency: formatting, naming, patterns used elsewhere.
|
||||||
|
|
||||||
|
6. Tests & verification
|
||||||
|
- Identify what’s covered by tests (unit/integration/e2e).
|
||||||
|
- Are there regression tests for the bug fixed / scenario added?
|
||||||
|
- Missing tests? Call out exact cases that should be added.
|
||||||
|
- If tests are present, do they actually assert the important behavior (not just snapshots / happy path)?
|
||||||
|
|
||||||
|
7. Follow-up refactors / cleanup suggestions
|
||||||
|
- Any code that should be simplified before merge?
|
||||||
|
- Any TODOs that should be tickets vs addressed now?
|
||||||
|
- Any deprecations, docs, types, or lint rules we should adjust?
|
||||||
|
|
||||||
|
8. Key questions to answer explicitly
|
||||||
|
- Can we fix everything ourselves in a follow-up, or does the contributor need to update this PR?
|
||||||
|
- Any blocking concerns (must-fix before merge)?
|
||||||
|
- Is this PR ready to land, or does it need work?
|
||||||
|
|
||||||
|
9. Output (structured)
|
||||||
|
Produce a review with these sections:
|
||||||
|
|
||||||
|
A) TL;DR recommendation
|
||||||
|
|
||||||
|
- One of: READY FOR /landpr | NEEDS WORK | NEEDS DISCUSSION
|
||||||
|
- 1–3 sentence rationale.
|
||||||
|
|
||||||
|
B) What changed
|
||||||
|
|
||||||
|
- Brief bullet summary of the diff/behavioral changes.
|
||||||
|
|
||||||
|
C) What’s good
|
||||||
|
|
||||||
|
- Bullets: correctness, simplicity, tests, docs, ergonomics, etc.
|
||||||
|
|
||||||
|
D) Concerns / questions (actionable)
|
||||||
|
|
||||||
|
- Numbered list.
|
||||||
|
- Mark each item as:
|
||||||
|
- BLOCKER (must fix before merge)
|
||||||
|
- IMPORTANT (should fix before merge)
|
||||||
|
- NIT (optional)
|
||||||
|
- For each: point to the file/area and propose a concrete fix or alternative.
|
||||||
|
|
||||||
|
E) Tests
|
||||||
|
|
||||||
|
- What exists.
|
||||||
|
- What’s missing (specific scenarios).
|
||||||
|
|
||||||
|
F) Follow-ups (optional)
|
||||||
|
|
||||||
|
- Non-blocking refactors/tickets to open later.
|
||||||
|
|
||||||
|
G) Suggested PR comment (optional)
|
||||||
|
|
||||||
|
- Offer: “Want me to draft a PR comment to the author?”
|
||||||
|
- If yes, provide a ready-to-paste comment summarizing the above, with clear asks.
|
||||||
|
|
||||||
|
Rules / Guardrails
|
||||||
|
|
||||||
|
- Review only: do not merge (`gh pr merge`), do not push branches, do not edit code.
|
||||||
|
- If you need clarification, ask questions rather than guessing.
|
||||||
105
.pi/prompts/reviewpr.md
Normal file
105
.pi/prompts/reviewpr.md
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
---
|
||||||
|
description: Review a PR thoroughly without merging
|
||||||
|
---
|
||||||
|
|
||||||
|
Input
|
||||||
|
|
||||||
|
- PR: $1 <number|url>
|
||||||
|
- If missing: use the most recent PR mentioned in the conversation.
|
||||||
|
- If ambiguous: ask.
|
||||||
|
|
||||||
|
Do (review-only)
|
||||||
|
Goal: produce a thorough review and a clear recommendation (READY for /landpr vs NEEDS WORK). Do NOT merge, do NOT push, do NOT make changes in the repo as part of this command.
|
||||||
|
|
||||||
|
1. Identify PR meta + context
|
||||||
|
|
||||||
|
```sh
|
||||||
|
gh pr view <PR> --json number,title,state,isDraft,author,baseRefName,headRefName,headRepository,url,body,labels,assignees,reviewRequests,files,additions,deletions --jq '{number,title,url,state,isDraft,author:.author.login,base:.baseRefName,head:.headRefName,headRepo:.headRepository.nameWithOwner,additions,deletions,files:.files|length}'
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Read the PR description carefully
|
||||||
|
- Summarize the stated goal, scope, and any "why now?" rationale.
|
||||||
|
- Call out any missing context: motivation, alternatives considered, rollout/compat notes, risk.
|
||||||
|
|
||||||
|
3. Read the diff thoroughly (prefer full diff)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
gh pr diff <PR>
|
||||||
|
# If you need more surrounding context for files:
|
||||||
|
gh pr checkout <PR> # optional; still review-only
|
||||||
|
git show --stat
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Validate the change is needed / valuable
|
||||||
|
- What user/customer/dev pain does this solve?
|
||||||
|
- Is this change the smallest reasonable fix?
|
||||||
|
- Are we introducing complexity for marginal benefit?
|
||||||
|
- Are we changing behavior/contract in a way that needs docs or a release note?
|
||||||
|
|
||||||
|
5. Evaluate implementation quality + optimality
|
||||||
|
- Correctness: edge cases, error handling, null/undefined, concurrency, ordering.
|
||||||
|
- Design: is the abstraction/architecture appropriate or over/under-engineered?
|
||||||
|
- Performance: hot paths, allocations, queries, network, N+1s, caching.
|
||||||
|
- Security/privacy: authz/authn, input validation, secrets, logging PII.
|
||||||
|
- Backwards compatibility: public APIs, config, migrations.
|
||||||
|
- Style consistency: formatting, naming, patterns used elsewhere.
|
||||||
|
|
||||||
|
6. Tests & verification
|
||||||
|
- Identify what's covered by tests (unit/integration/e2e).
|
||||||
|
- Are there regression tests for the bug fixed / scenario added?
|
||||||
|
- Missing tests? Call out exact cases that should be added.
|
||||||
|
- If tests are present, do they actually assert the important behavior (not just snapshots / happy path)?
|
||||||
|
|
||||||
|
7. Follow-up refactors / cleanup suggestions
|
||||||
|
- Any code that should be simplified before merge?
|
||||||
|
- Any TODOs that should be tickets vs addressed now?
|
||||||
|
- Any deprecations, docs, types, or lint rules we should adjust?
|
||||||
|
|
||||||
|
8. Key questions to answer explicitly
|
||||||
|
- Can we fix everything ourselves in a follow-up, or does the contributor need to update this PR?
|
||||||
|
- Any blocking concerns (must-fix before merge)?
|
||||||
|
- Is this PR ready to land, or does it need work?
|
||||||
|
|
||||||
|
9. Output (structured)
|
||||||
|
Produce a review with these sections:
|
||||||
|
|
||||||
|
A) TL;DR recommendation
|
||||||
|
|
||||||
|
- One of: READY FOR /landpr | NEEDS WORK | NEEDS DISCUSSION
|
||||||
|
- 1–3 sentence rationale.
|
||||||
|
|
||||||
|
B) What changed
|
||||||
|
|
||||||
|
- Brief bullet summary of the diff/behavioral changes.
|
||||||
|
|
||||||
|
C) What's good
|
||||||
|
|
||||||
|
- Bullets: correctness, simplicity, tests, docs, ergonomics, etc.
|
||||||
|
|
||||||
|
D) Concerns / questions (actionable)
|
||||||
|
|
||||||
|
- Numbered list.
|
||||||
|
- Mark each item as:
|
||||||
|
- BLOCKER (must fix before merge)
|
||||||
|
- IMPORTANT (should fix before merge)
|
||||||
|
- NIT (optional)
|
||||||
|
- For each: point to the file/area and propose a concrete fix or alternative.
|
||||||
|
|
||||||
|
E) Tests
|
||||||
|
|
||||||
|
- What exists.
|
||||||
|
- What's missing (specific scenarios).
|
||||||
|
|
||||||
|
F) Follow-ups (optional)
|
||||||
|
|
||||||
|
- Non-blocking refactors/tickets to open later.
|
||||||
|
|
||||||
|
G) Suggested PR comment (optional)
|
||||||
|
|
||||||
|
- Offer: "Want me to draft a PR comment to the author?"
|
||||||
|
- If yes, provide a ready-to-paste comment summarizing the above, with clear asks.
|
||||||
|
|
||||||
|
Rules / Guardrails
|
||||||
|
|
||||||
|
- Review only: do not merge (`gh pr merge`), do not push branches, do not edit code.
|
||||||
|
- If you need clarification, ask questions rather than guessing.
|
||||||
@@ -51,9 +51,9 @@ repos:
|
|||||||
rev: v0.11.0
|
rev: v0.11.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: shellcheck
|
- id: shellcheck
|
||||||
args: [--severity=error] # Only fail on errors, not warnings/info
|
args: [--severity=error] # Only fail on errors, not warnings/info
|
||||||
# Exclude vendor and scripts with embedded code or known issues
|
# Exclude vendor and scripts with embedded code or known issues
|
||||||
exclude: '^(vendor/|scripts/e2e/)'
|
exclude: "^(vendor/|scripts/e2e/)"
|
||||||
|
|
||||||
# GitHub Actions linting
|
# GitHub Actions linting
|
||||||
- repo: https://github.com/rhysd/actionlint
|
- repo: https://github.com/rhysd/actionlint
|
||||||
@@ -67,7 +67,7 @@ repos:
|
|||||||
hooks:
|
hooks:
|
||||||
- id: zizmor
|
- id: zizmor
|
||||||
args: [--persona=regular, --min-severity=medium, --min-confidence=medium]
|
args: [--persona=regular, --min-severity=medium, --min-confidence=medium]
|
||||||
exclude: '^(vendor/|Swabble/)'
|
exclude: "^(vendor/|Swabble/)"
|
||||||
|
|
||||||
# Project checks (same commands as CI)
|
# Project checks (same commands as CI)
|
||||||
- repo: local
|
- repo: local
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
src/canvas-host/a2ui/a2ui.bundle.js
|
|
||||||
25
AGENTS.md
25
AGENTS.md
@@ -1,8 +1,10 @@
|
|||||||
# Repository Guidelines
|
# Repository Guidelines
|
||||||
|
|
||||||
- Repo: https://github.com/openclaw/openclaw
|
- Repo: https://github.com/openclaw/openclaw
|
||||||
- GitHub issues/comments/PR comments: use literal multiline strings or `-F - <<'EOF'` (or $'...') for real newlines; never embed "\\n".
|
- GitHub issues/comments/PR comments: use literal multiline strings or `-F - <<'EOF'` (or $'...') for real newlines; never embed "\\n".
|
||||||
|
|
||||||
## Project Structure & Module Organization
|
## Project Structure & Module Organization
|
||||||
|
|
||||||
- Source code: `src/` (CLI wiring in `src/cli`, commands in `src/commands`, web provider in `src/provider-web.ts`, infra in `src/infra`, media pipeline in `src/media`).
|
- Source code: `src/` (CLI wiring in `src/cli`, commands in `src/commands`, web provider in `src/provider-web.ts`, infra in `src/infra`, media pipeline in `src/media`).
|
||||||
- Tests: colocated `*.test.ts`.
|
- Tests: colocated `*.test.ts`.
|
||||||
- Docs: `docs/` (images, queue, Pi config). Built output lives in `dist/`.
|
- Docs: `docs/` (images, queue, Pi config). Built output lives in `dist/`.
|
||||||
@@ -16,6 +18,7 @@
|
|||||||
- When adding channels/extensions/apps/docs, review `.github/labeler.yml` for label coverage.
|
- When adding channels/extensions/apps/docs, review `.github/labeler.yml` for label coverage.
|
||||||
|
|
||||||
## Docs Linking (Mintlify)
|
## Docs Linking (Mintlify)
|
||||||
|
|
||||||
- Docs are hosted on Mintlify (docs.openclaw.ai).
|
- Docs are hosted on Mintlify (docs.openclaw.ai).
|
||||||
- Internal doc links in `docs/**/*.md`: root-relative, no `.md`/`.mdx` (example: `[Config](/configuration)`).
|
- Internal doc links in `docs/**/*.md`: root-relative, no `.md`/`.mdx` (example: `[Config](/configuration)`).
|
||||||
- Section cross-references: use anchors on root-relative paths (example: `[Hooks](/configuration#hooks)`).
|
- Section cross-references: use anchors on root-relative paths (example: `[Hooks](/configuration#hooks)`).
|
||||||
@@ -26,6 +29,7 @@
|
|||||||
- Docs content must be generic: no personal device names/hostnames/paths; use placeholders like `user@gateway-host` and “gateway host”.
|
- Docs content must be generic: no personal device names/hostnames/paths; use placeholders like `user@gateway-host` and “gateway host”.
|
||||||
|
|
||||||
## exe.dev VM ops (general)
|
## exe.dev VM ops (general)
|
||||||
|
|
||||||
- Access: stable path is `ssh exe.dev` then `ssh vm-name` (assume SSH key already set).
|
- Access: stable path is `ssh exe.dev` then `ssh vm-name` (assume SSH key already set).
|
||||||
- SSH flaky: use exe.dev web terminal or Shelley (web agent); keep a tmux session for long ops.
|
- SSH flaky: use exe.dev web terminal or Shelley (web agent); keep a tmux session for long ops.
|
||||||
- Update: `sudo npm i -g openclaw@latest` (global install needs root on `/usr/lib/node_modules`).
|
- Update: `sudo npm i -g openclaw@latest` (global install needs root on `/usr/lib/node_modules`).
|
||||||
@@ -36,6 +40,7 @@
|
|||||||
- Verify: `openclaw channels status --probe`, `ss -ltnp | rg 18789`, `tail -n 120 /tmp/openclaw-gateway.log`.
|
- Verify: `openclaw channels status --probe`, `ss -ltnp | rg 18789`, `tail -n 120 /tmp/openclaw-gateway.log`.
|
||||||
|
|
||||||
## Build, Test, and Development Commands
|
## Build, Test, and Development Commands
|
||||||
|
|
||||||
- Runtime baseline: Node **22+** (keep Node + Bun paths working).
|
- Runtime baseline: Node **22+** (keep Node + Bun paths working).
|
||||||
- Install deps: `pnpm install`
|
- Install deps: `pnpm install`
|
||||||
- Pre-commit hooks: `prek install` (runs same checks as CI)
|
- Pre-commit hooks: `prek install` (runs same checks as CI)
|
||||||
@@ -44,24 +49,27 @@
|
|||||||
- Run CLI in dev: `pnpm openclaw ...` (bun) or `pnpm dev`.
|
- Run CLI in dev: `pnpm openclaw ...` (bun) or `pnpm dev`.
|
||||||
- Node remains supported for running built output (`dist/*`) and production installs.
|
- Node remains supported for running built output (`dist/*`) and production installs.
|
||||||
- Mac packaging (dev): `scripts/package-mac-app.sh` defaults to current arch. Release checklist: `docs/platforms/mac/release.md`.
|
- Mac packaging (dev): `scripts/package-mac-app.sh` defaults to current arch. Release checklist: `docs/platforms/mac/release.md`.
|
||||||
- Type-check/build: `pnpm build` (tsc)
|
- Type-check/build: `pnpm build`
|
||||||
- Lint/format: `pnpm lint` (oxlint), `pnpm format` (oxfmt)
|
- Lint/format: `pnpm check`
|
||||||
- Tests: `pnpm test` (vitest); coverage: `pnpm test:coverage`
|
- Tests: `pnpm test` (vitest); coverage: `pnpm test:coverage`
|
||||||
|
|
||||||
## Coding Style & Naming Conventions
|
## Coding Style & Naming Conventions
|
||||||
|
|
||||||
- Language: TypeScript (ESM). Prefer strict typing; avoid `any`.
|
- Language: TypeScript (ESM). Prefer strict typing; avoid `any`.
|
||||||
- Formatting/linting via Oxlint and Oxfmt; run `pnpm lint` before commits.
|
- Formatting/linting via Oxlint and Oxfmt; run `pnpm check` before commits.
|
||||||
- Add brief code comments for tricky or non-obvious logic.
|
- Add brief code comments for tricky or non-obvious logic.
|
||||||
- Keep files concise; extract helpers instead of “V2” copies. Use existing patterns for CLI options and dependency injection via `createDefaultDeps`.
|
- Keep files concise; extract helpers instead of “V2” copies. Use existing patterns for CLI options and dependency injection via `createDefaultDeps`.
|
||||||
- Aim to keep files under ~700 LOC; guideline only (not a hard guardrail). Split/refactor when it improves clarity or testability.
|
- Aim to keep files under ~700 LOC; guideline only (not a hard guardrail). Split/refactor when it improves clarity or testability.
|
||||||
- Naming: use **OpenClaw** for product/app/docs headings; use `openclaw` for CLI command, package/binary, paths, and config keys.
|
- Naming: use **OpenClaw** for product/app/docs headings; use `openclaw` for CLI command, package/binary, paths, and config keys.
|
||||||
|
|
||||||
## Release Channels (Naming)
|
## Release Channels (Naming)
|
||||||
|
|
||||||
- stable: tagged releases only (e.g. `vYYYY.M.D`), npm dist-tag `latest`.
|
- stable: tagged releases only (e.g. `vYYYY.M.D`), npm dist-tag `latest`.
|
||||||
- beta: prerelease tags `vYYYY.M.D-beta.N`, npm dist-tag `beta` (may ship without macOS app).
|
- beta: prerelease tags `vYYYY.M.D-beta.N`, npm dist-tag `beta` (may ship without macOS app).
|
||||||
- dev: moving head on `main` (no tag; git checkout main).
|
- dev: moving head on `main` (no tag; git checkout main).
|
||||||
|
|
||||||
## Testing Guidelines
|
## Testing Guidelines
|
||||||
|
|
||||||
- Framework: Vitest with V8 coverage thresholds (70% lines/branches/functions/statements).
|
- Framework: Vitest with V8 coverage thresholds (70% lines/branches/functions/statements).
|
||||||
- Naming: match source names with `*.test.ts`; e2e in `*.e2e.test.ts`.
|
- Naming: match source names with `*.test.ts`; e2e in `*.e2e.test.ts`.
|
||||||
- Run `pnpm test` (or `pnpm test:coverage`) before pushing when you touch logic.
|
- Run `pnpm test` (or `pnpm test:coverage`) before pushing when you touch logic.
|
||||||
@@ -72,6 +80,7 @@
|
|||||||
- Mobile: before using a simulator, check for connected real devices (iOS + Android) and prefer them when available.
|
- Mobile: before using a simulator, check for connected real devices (iOS + Android) and prefer them when available.
|
||||||
|
|
||||||
## Commit & Pull Request Guidelines
|
## Commit & Pull Request Guidelines
|
||||||
|
|
||||||
- Create commits with `scripts/committer "<msg>" <file...>`; avoid manual `git add`/`git commit` so staging stays scoped.
|
- Create commits with `scripts/committer "<msg>" <file...>`; avoid manual `git add`/`git commit` so staging stays scoped.
|
||||||
- Follow concise, action-oriented commit messages (e.g., `CLI: add verbose flag to send`).
|
- Follow concise, action-oriented commit messages (e.g., `CLI: add verbose flag to send`).
|
||||||
- Group related changes; avoid bundling unrelated refactors.
|
- Group related changes; avoid bundling unrelated refactors.
|
||||||
@@ -90,23 +99,28 @@
|
|||||||
- After merging a PR: run `bun scripts/update-clawtributors.ts` if the contributor is missing, then commit the regenerated README.
|
- After merging a PR: run `bun scripts/update-clawtributors.ts` if the contributor is missing, then commit the regenerated README.
|
||||||
|
|
||||||
## Shorthand Commands
|
## Shorthand Commands
|
||||||
|
|
||||||
- `sync`: if working tree is dirty, commit all changes (pick a sensible Conventional Commit message), then `git pull --rebase`; if rebase conflicts and cannot resolve, stop; otherwise `git push`.
|
- `sync`: if working tree is dirty, commit all changes (pick a sensible Conventional Commit message), then `git pull --rebase`; if rebase conflicts and cannot resolve, stop; otherwise `git push`.
|
||||||
|
|
||||||
### PR Workflow (Review vs Land)
|
### PR Workflow (Review vs Land)
|
||||||
|
|
||||||
- **Review mode (PR link only):** read `gh pr view/diff`; **do not** switch branches; **do not** change code.
|
- **Review mode (PR link only):** read `gh pr view/diff`; **do not** switch branches; **do not** change code.
|
||||||
- **Landing mode:** create an integration branch from `main`, bring in PR commits (**prefer rebase** for linear history; **merge allowed** when complexity/conflicts make it safer), apply fixes, add changelog (+ thanks + PR #), run full gate **locally before committing** (`pnpm lint && pnpm build && pnpm test`), commit, merge back to `main`, then `git switch main` (never stay on a topic branch after landing). Important: contributor needs to be in git graph after this!
|
- **Landing mode:** create an integration branch from `main`, bring in PR commits (**prefer rebase** for linear history; **merge allowed** when complexity/conflicts make it safer), apply fixes, add changelog (+ thanks + PR #), run full gate **locally before committing** (`pnpm build && pnpm check && pnpm test`), commit, merge back to `main`, then `git switch main` (never stay on a topic branch after landing). Important: contributor needs to be in git graph after this!
|
||||||
|
|
||||||
## Security & Configuration Tips
|
## Security & Configuration Tips
|
||||||
|
|
||||||
- Web provider stores creds at `~/.openclaw/credentials/`; rerun `openclaw login` if logged out.
|
- Web provider stores creds at `~/.openclaw/credentials/`; rerun `openclaw login` if logged out.
|
||||||
- Pi sessions live under `~/.openclaw/sessions/` by default; the base directory is not configurable.
|
- Pi sessions live under `~/.openclaw/sessions/` by default; the base directory is not configurable.
|
||||||
- Environment variables: see `~/.profile`.
|
- Environment variables: see `~/.profile`.
|
||||||
- Never commit or publish real phone numbers, videos, or live configuration values. Use obviously fake placeholders in docs, tests, and examples.
|
- Never commit or publish real phone numbers, videos, or live configuration values. Use obviously fake placeholders in docs, tests, and examples.
|
||||||
- Release flow: always read `docs/reference/RELEASING.md` and `docs/platforms/mac/release.md` before any release work; do not ask routine questions once those docs answer them.
|
- Release flow: always read `docs/reference/RELEASING.md` and `docs/platforms/mac/release.md` before any release work; do not ask routine questions once those docs answer them.
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
- Rebrand/migration issues or legacy config/service warnings: run `openclaw doctor` (see `docs/gateway/doctor.md`).
|
- Rebrand/migration issues or legacy config/service warnings: run `openclaw doctor` (see `docs/gateway/doctor.md`).
|
||||||
|
|
||||||
## Agent-Specific Notes
|
## Agent-Specific Notes
|
||||||
|
|
||||||
- Vocabulary: "makeup" = "mac app".
|
- Vocabulary: "makeup" = "mac app".
|
||||||
- Never edit `node_modules` (global/Homebrew/npm/git installs too). Updates overwrite. Skill notes go in `tools.md` or `AGENTS.md`.
|
- Never edit `node_modules` (global/Homebrew/npm/git installs too). Updates overwrite. Skill notes go in `tools.md` or `AGENTS.md`.
|
||||||
- Signal: "update fly" => `fly ssh console -a flawd-bot -C "bash -lc 'cd /data/clawd/openclaw && git pull --rebase origin main'"` then `fly machines restart e825232f34d058 -a flawd-bot`.
|
- Signal: "update fly" => `fly ssh console -a flawd-bot -C "bash -lc 'cd /data/clawd/openclaw && git pull --rebase origin main'"` then `fly machines restart e825232f34d058 -a flawd-bot`.
|
||||||
@@ -155,6 +169,7 @@
|
|||||||
- Release guardrails: do not change version numbers without operator’s explicit consent; always ask permission before running any npm publish/release step.
|
- Release guardrails: do not change version numbers without operator’s explicit consent; always ask permission before running any npm publish/release step.
|
||||||
|
|
||||||
## NPM + 1Password (publish/verify)
|
## NPM + 1Password (publish/verify)
|
||||||
|
|
||||||
- Use the 1password skill; all `op` commands must run inside a fresh tmux session.
|
- Use the 1password skill; all `op` commands must run inside a fresh tmux session.
|
||||||
- Sign in: `eval "$(op signin --account my.1password.com)"` (app unlocked + integration on).
|
- Sign in: `eval "$(op signin --account my.1password.com)"` (app unlocked + integration on).
|
||||||
- OTP: `op read 'op://Private/Npmjs/one-time password?attribute=otp'`.
|
- OTP: `op read 'op://Private/Npmjs/one-time password?attribute=otp'`.
|
||||||
|
|||||||
277
CHANGELOG.md
277
CHANGELOG.md
@@ -2,10 +2,197 @@
|
|||||||
|
|
||||||
Docs: https://docs.openclaw.ai
|
Docs: https://docs.openclaw.ai
|
||||||
|
|
||||||
## 2026.1.29
|
## 2026.2.2
|
||||||
Status: stable.
|
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
|
|
||||||
|
- Web UI: add Agents dashboard for managing agent files, tools, skills, models, channels, and cron jobs.
|
||||||
|
- Memory: implement the opt-in QMD backend for workspace memory. (#3160) Thanks @vignesh07.
|
||||||
|
- Config: allow setting a default subagent thinking level via `agents.defaults.subagents.thinking` (and per-agent `agents.list[].subagents.thinking`). (#7372) Thanks @tyler6204.
|
||||||
|
- Security: add healthcheck skill and bootstrap audit guidance. (#7641) Thanks @Takhoffman.
|
||||||
|
- Docs: zh-CN translation polish + pipeline guidance. (#8202, #6995) Thanks @AaronWander, @taiyi747, @Explorer1092, @rendaoyuan.
|
||||||
|
- Docs: zh-CN translations seed + nav polish + landing notice + typo fix. (#6619, #7242, #7303, #7415) Thanks @joshp123, @lailoo.
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- Security: Matrix allowlists now require full MXIDs; ambiguous name resolution no longer grants access. Thanks @MegaManSec.
|
||||||
|
- Security: enforce access-group gating for Slack slash commands when channel type lookup fails.
|
||||||
|
- Security: require validated shared-secret auth before skipping device identity on gateway connect.
|
||||||
|
- Security: guard skill installer downloads with SSRF checks (block private/localhost URLs).
|
||||||
|
- Security: harden Windows exec allowlist; block cmd.exe bypass via single &. Thanks @simecek.
|
||||||
|
- Media understanding: apply SSRF guardrails to provider fetches; allow private baseUrl overrides explicitly.
|
||||||
|
- fix(voice-call): harden inbound allowlist; reject anonymous callers; require Telnyx publicKey for allowlist; token-gate Twilio media streams; cap webhook body size (thanks @simecek)
|
||||||
|
- fix(webchat): respect user scroll position during streaming and refresh (#7226) (thanks @marcomarandiz)
|
||||||
|
- Telegram: recover from grammY long-poll timed out errors. (#7466) Thanks @macmimi23.
|
||||||
|
- Agents: repair malformed tool calls and session transcripts. (#7473) Thanks @justinhuangcode.
|
||||||
|
- fix(agents): validate AbortSignal instances before calling AbortSignal.any() (#7277) (thanks @Elarwei001)
|
||||||
|
- Media understanding: skip binary media from file text extraction. (#7475) Thanks @AlexZhangji.
|
||||||
|
- Onboarding: keep TUI flow exclusive (skip completion prompt + background Web UI seed); completion prompt now handled by install/update.
|
||||||
|
- TUI: block onboarding output while TUI is active and restore terminal state on exit.
|
||||||
|
- CLI/Zsh completion: cache scripts in state dir and escape option descriptions to avoid invalid option errors.
|
||||||
|
- fix(ui): resolve Control UI asset path correctly.
|
||||||
|
- fix(ui): refresh agent files after external edits.
|
||||||
|
- Docs: finish renaming the QMD memory docs to reference the OpenClaw state dir.
|
||||||
|
- Tests: stub SSRF DNS pinning in web auto-reply + Gemini video coverage. (#6619) Thanks @joshp123.
|
||||||
|
|
||||||
|
## 2026.2.1
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
- Docs: onboarding/install/i18n/exec-approvals/Control UI/exe.dev/cacheRetention updates + misc nav/typos. (#3050, #3461, #4064, #4675, #4729, #4763, #5003, #5402, #5446, #5474, #5663, #5689, #5694, #5967, #6270, #6300, #6311, #6416, #6487, #6550, #6789)
|
||||||
|
- Telegram: use shared pairing store. (#6127) Thanks @obviyus.
|
||||||
|
- Agents: add OpenRouter app attribution headers. Thanks @alexanderatallah.
|
||||||
|
- Agents: add system prompt safety guardrails. (#5445) Thanks @joshp123.
|
||||||
|
- Agents: update pi-ai to 0.50.9 and rename cacheControlTtl -> cacheRetention (with back-compat mapping).
|
||||||
|
- Agents: extend CreateAgentSessionOptions with systemPrompt/skills/contextFiles.
|
||||||
|
- Agents: add tool policy conformance snapshot (no runtime behavior change). (#6011)
|
||||||
|
- Auth: update MiniMax OAuth hint + portal auth note copy.
|
||||||
|
- Discord: inherit thread parent bindings for routing. (#3892) Thanks @aerolalit.
|
||||||
|
- Gateway: inject timestamps into agent and chat.send messages. (#3705) Thanks @conroywhitney, @CashWilliams.
|
||||||
|
- Gateway: require TLS 1.3 minimum for TLS listeners. (#5970) Thanks @loganaden.
|
||||||
|
- Web UI: refine chat layout + extend session active duration.
|
||||||
|
- CI: add formal conformance + alias consistency checks. (#5723, #5807)
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- Security: guard remote media fetches with SSRF protections (block private/localhost, DNS pinning).
|
||||||
|
- Updates: clean stale global install rename dirs and extend gateway update timeouts to avoid npm ENOTEMPTY failures.
|
||||||
|
- Plugins: validate plugin/hook install paths and reject traversal-like names.
|
||||||
|
- Telegram: add download timeouts for file fetches. (#6914) Thanks @hclsys.
|
||||||
|
- Telegram: enforce thread specs for DM vs forum sends. (#6833) Thanks @obviyus.
|
||||||
|
- Streaming: flush block streaming on paragraph boundaries for newline chunking. (#7014)
|
||||||
|
- Streaming: stabilize partial streaming filters.
|
||||||
|
- Auto-reply: avoid referencing workspace files in /new greeting prompt. (#5706) Thanks @bravostation.
|
||||||
|
- Tools: align tool execute adapters/signatures (legacy + parameter order + arg normalization).
|
||||||
|
- Tools: treat "\*" tool allowlist entries as valid to avoid spurious unknown-entry warnings.
|
||||||
|
- Skills: update session-logs paths from .clawdbot to .openclaw. (#4502)
|
||||||
|
- Slack: harden media fetch limits and Slack file URL validation. (#6639) Thanks @davidiach.
|
||||||
|
- Lint: satisfy curly rule after import sorting. (#6310)
|
||||||
|
- Process: resolve Windows `spawn()` failures for npm-family CLIs by appending `.cmd` when needed. (#5815) Thanks @thejhinvirtuoso.
|
||||||
|
- Discord: resolve PluralKit proxied senders for allowlists and labels. (#5838) Thanks @thewilloftheshadow.
|
||||||
|
- Tlon: add timeout to SSE client fetch calls (CWE-400). (#5926)
|
||||||
|
- Memory search: L2-normalize local embedding vectors to fix semantic search. (#5332)
|
||||||
|
- Agents: align embedded runner + typings with pi-coding-agent API updates (pi 0.51.0).
|
||||||
|
- Agents: ensure OpenRouter attribution headers apply in the embedded runner.
|
||||||
|
- Agents: cap context window resolution for compaction safeguard. (#6187) Thanks @iamEvanYT.
|
||||||
|
- System prompt: resolve overrides and hint using session_status for current date/time. (#1897, #1928, #2108, #3677)
|
||||||
|
- Agents: fix Pi prompt template argument syntax. (#6543)
|
||||||
|
- Subagents: fix announce failover race (always emit lifecycle end; timeout=0 means no-timeout). (#6621)
|
||||||
|
- Teams: gate media auth retries.
|
||||||
|
- Telegram: restore draft streaming partials. (#5543) Thanks @obviyus.
|
||||||
|
- Onboarding: friendlier Windows onboarding message. (#6242) Thanks @shanselman.
|
||||||
|
- TUI: prevent crash when searching with digits in the model selector.
|
||||||
|
- Agents: wire before_tool_call plugin hook into tool execution. (#6570, #6660) Thanks @ryancnelson.
|
||||||
|
- Browser: secure Chrome extension relay CDP sessions.
|
||||||
|
- Docker: use container port for gateway command instead of host port. (#5110) Thanks @mise42.
|
||||||
|
- Docker: start gateway CMD by default for container deployments. (#6635) Thanks @kaizen403.
|
||||||
|
- fix(lobster): block arbitrary exec via lobsterPath/cwd injection (GHSA-4mhr-g7xj-cg8j). (#5335) Thanks @vignesh07.
|
||||||
|
- Security: sanitize WhatsApp accountId to prevent path traversal. (#4610)
|
||||||
|
- Security: restrict MEDIA path extraction to prevent LFI. (#4930)
|
||||||
|
- Security: validate message-tool filePath/path against sandbox root. (#6398)
|
||||||
|
- Security: block LD*/DYLD* env overrides for host exec. (#4896) Thanks @HassanFleyah.
|
||||||
|
- Security: harden web tool content wrapping + file parsing safeguards. (#4058) Thanks @VACInc.
|
||||||
|
- Security: enforce Twitch `allowFrom` allowlist gating (deny non-allowlisted senders). Thanks @MegaManSec.
|
||||||
|
|
||||||
|
## 2026.1.31
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
- Docs: onboarding/install/i18n/exec-approvals/Control UI/exe.dev/cacheRetention updates + misc nav/typos. (#3050, #3461, #4064, #4675, #4729, #4763, #5003, #5402, #5446, #5474, #5663, #5689, #5694, #5967, #6270, #6300, #6311, #6416, #6487, #6550, #6789)
|
||||||
|
- Telegram: use shared pairing store. (#6127) Thanks @obviyus.
|
||||||
|
- Agents: add OpenRouter app attribution headers. Thanks @alexanderatallah.
|
||||||
|
- Agents: add system prompt safety guardrails. (#5445) Thanks @joshp123.
|
||||||
|
- Agents: update pi-ai to 0.50.9 and rename cacheControlTtl -> cacheRetention (with back-compat mapping).
|
||||||
|
- Agents: extend CreateAgentSessionOptions with systemPrompt/skills/contextFiles.
|
||||||
|
- Agents: add tool policy conformance snapshot (no runtime behavior change). (#6011)
|
||||||
|
- Auth: update MiniMax OAuth hint + portal auth note copy.
|
||||||
|
- Discord: inherit thread parent bindings for routing. (#3892) Thanks @aerolalit.
|
||||||
|
- Gateway: inject timestamps into agent and chat.send messages. (#3705) Thanks @conroywhitney, @CashWilliams.
|
||||||
|
- Gateway: require TLS 1.3 minimum for TLS listeners. (#5970) Thanks @loganaden.
|
||||||
|
- Web UI: refine chat layout + extend session active duration.
|
||||||
|
- CI: add formal conformance + alias consistency checks. (#5723, #5807)
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- Security: guard remote media fetches with SSRF protections (block private/localhost, DNS pinning).
|
||||||
|
- Updates: clean stale global install rename dirs and extend gateway update timeouts to avoid npm ENOTEMPTY failures.
|
||||||
|
- Plugins: validate plugin/hook install paths and reject traversal-like names.
|
||||||
|
- Telegram: add download timeouts for file fetches. (#6914) Thanks @hclsys.
|
||||||
|
- Telegram: enforce thread specs for DM vs forum sends. (#6833) Thanks @obviyus.
|
||||||
|
- Streaming: flush block streaming on paragraph boundaries for newline chunking. (#7014)
|
||||||
|
- Streaming: stabilize partial streaming filters.
|
||||||
|
- Auto-reply: avoid referencing workspace files in /new greeting prompt. (#5706) Thanks @bravostation.
|
||||||
|
- Tools: align tool execute adapters/signatures (legacy + parameter order + arg normalization).
|
||||||
|
- Tools: treat `"*"` tool allowlist entries as valid to avoid spurious unknown-entry warnings.
|
||||||
|
- Skills: update session-logs paths from .clawdbot to .openclaw. (#4502)
|
||||||
|
- Slack: harden media fetch limits and Slack file URL validation. (#6639) Thanks @davidiach.
|
||||||
|
- Lint: satisfy curly rule after import sorting. (#6310)
|
||||||
|
- Process: resolve Windows `spawn()` failures for npm-family CLIs by appending `.cmd` when needed. (#5815) Thanks @thejhinvirtuoso.
|
||||||
|
- Discord: resolve PluralKit proxied senders for allowlists and labels. (#5838) Thanks @thewilloftheshadow.
|
||||||
|
- Tlon: add timeout to SSE client fetch calls (CWE-400). (#5926)
|
||||||
|
- Memory search: L2-normalize local embedding vectors to fix semantic search. (#5332)
|
||||||
|
- Agents: align embedded runner + typings with pi-coding-agent API updates (pi 0.51.0).
|
||||||
|
- Agents: ensure OpenRouter attribution headers apply in the embedded runner.
|
||||||
|
- Agents: cap context window resolution for compaction safeguard. (#6187) Thanks @iamEvanYT.
|
||||||
|
- System prompt: resolve overrides and hint using session_status for current date/time. (#1897, #1928, #2108, #3677)
|
||||||
|
- Agents: fix Pi prompt template argument syntax. (#6543)
|
||||||
|
- Subagents: fix announce failover race (always emit lifecycle end; timeout=0 means no-timeout). (#6621)
|
||||||
|
- Teams: gate media auth retries.
|
||||||
|
- Telegram: restore draft streaming partials. (#5543) Thanks @obviyus.
|
||||||
|
- Onboarding: friendlier Windows onboarding message. (#6242) Thanks @shanselman.
|
||||||
|
- TUI: prevent crash when searching with digits in the model selector.
|
||||||
|
- Agents: wire before_tool_call plugin hook into tool execution. (#6570, #6660) Thanks @ryancnelson.
|
||||||
|
- Browser: secure Chrome extension relay CDP sessions.
|
||||||
|
- Docker: use container port for gateway command instead of host port. (#5110) Thanks @mise42.
|
||||||
|
- Docker: start gateway CMD by default for container deployments. (#6635) Thanks @kaizen403.
|
||||||
|
- fix(lobster): block arbitrary exec via lobsterPath/cwd injection (GHSA-4mhr-g7xj-cg8j). (#5335) Thanks @vignesh07.
|
||||||
|
- Security: sanitize WhatsApp accountId to prevent path traversal. (#4610)
|
||||||
|
- Security: restrict MEDIA path extraction to prevent LFI. (#4930)
|
||||||
|
- Security: validate message-tool filePath/path against sandbox root. (#6398)
|
||||||
|
- Security: block LD*/DYLD* env overrides for host exec. (#4896) Thanks @HassanFleyah.
|
||||||
|
- Security: harden web tool content wrapping + file parsing safeguards. (#4058) Thanks @VACInc.
|
||||||
|
- Security: enforce Twitch `allowFrom` allowlist gating (deny non-allowlisted senders). Thanks @MegaManSec.
|
||||||
|
|
||||||
|
## 2026.1.30
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
- CLI: add `completion` command (Zsh/Bash/PowerShell/Fish) and auto-setup during postinstall/onboarding.
|
||||||
|
- CLI: add per-agent `models status` (`--agent` filter). (#4780) Thanks @jlowin.
|
||||||
|
- Agents: add Kimi K2.5 to the synthetic model catalog. (#4407) Thanks @manikv12.
|
||||||
|
- Auth: switch Kimi Coding to built-in provider; normalize OAuth profile email.
|
||||||
|
- Auth: add MiniMax OAuth plugin + onboarding option. (#4521) Thanks @Maosghoul.
|
||||||
|
- Agents: update pi SDK/API usage and dependencies.
|
||||||
|
- Web UI: refresh sessions after chat commands and improve session display names.
|
||||||
|
- Build: move TypeScript builds to `tsdown` + `tsgo` (faster builds, CI typechecks), update tsconfig target, and clean up lint rules.
|
||||||
|
- Build: align npm tar override and bin metadata so the `openclaw` CLI entrypoint is preserved in npm publishes.
|
||||||
|
- Docs: add pi/pi-dev docs and update OpenClaw branding + install links.
|
||||||
|
- Docker E2E: stabilize gateway readiness, plugin installs/manifests, and cleanup/doctor switch entrypoint checks.
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- Security: restrict local path extraction in media parser to prevent LFI. (#4880)
|
||||||
|
- Gateway: prevent token defaults from becoming the literal "undefined". (#4873) Thanks @Hisleren.
|
||||||
|
- Control UI: fix assets resolution for npm global installs. (#4909) Thanks @YuriNachos.
|
||||||
|
- macOS: avoid stderr pipe backpressure in gateway discovery. (#3304) Thanks @abhijeet117.
|
||||||
|
- Telegram: normalize account token lookup for non-normalized IDs. (#5055) Thanks @jasonsschin.
|
||||||
|
- Telegram: preserve delivery thread fallback and fix threadId handling in delivery context.
|
||||||
|
- Telegram: fix HTML nesting for overlapping styles/links. (#4578) Thanks @ThanhNguyxn.
|
||||||
|
- Telegram: accept numeric messageId/chatId in react actions. (#4533) Thanks @Ayush10.
|
||||||
|
- Telegram: honor per-account proxy dispatcher via undici fetch. (#4456) Thanks @spiceoogway.
|
||||||
|
- Telegram: scope skill commands to bound agent per bot. (#4360) Thanks @robhparker.
|
||||||
|
- BlueBubbles: debounce by messageId to preserve attachments in text+image messages. (#4984)
|
||||||
|
- Routing: prefer requesterOrigin over stale session entries for sub-agent announce delivery. (#4957)
|
||||||
|
- Extensions: restore embedded extension discovery typings.
|
||||||
|
- CLI: fix `tui:dev` port resolution.
|
||||||
|
- LINE: fix status command TypeError. (#4651)
|
||||||
|
- OAuth: skip expired-token warnings when refresh tokens are still valid. (#4593)
|
||||||
|
- Build: skip redundant UI install step in Dockerfile. (#4584) Thanks @obviyus.
|
||||||
|
|
||||||
|
## 2026.1.29
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
- Rebrand: rename the npm package/CLI to `openclaw`, add a `openclaw` compatibility shim, and move extensions to the `@openclaw/*` scope.
|
- Rebrand: rename the npm package/CLI to `openclaw`, add a `openclaw` compatibility shim, and move extensions to the `@openclaw/*` scope.
|
||||||
- Onboarding: strengthen security warning copy for beta + access control expectations.
|
- Onboarding: strengthen security warning copy for beta + access control expectations.
|
||||||
- Onboarding: add Venice API key to non-interactive flow. (#1893) Thanks @jonisjongithub.
|
- Onboarding: add Venice API key to non-interactive flow. (#1893) Thanks @jonisjongithub.
|
||||||
@@ -41,9 +228,7 @@ Status: stable.
|
|||||||
- Routing: add per-account DM session scope and document multi-account isolation. (#3095) Thanks @jarvis-sam.
|
- Routing: add per-account DM session scope and document multi-account isolation. (#3095) Thanks @jarvis-sam.
|
||||||
- Routing: precompile session key regexes. (#1697) Thanks @Ray0907.
|
- Routing: precompile session key regexes. (#1697) Thanks @Ray0907.
|
||||||
- CLI: use Node's module compile cache for faster startup. (#2808) Thanks @pi0.
|
- CLI: use Node's module compile cache for faster startup. (#2808) Thanks @pi0.
|
||||||
- CLI: add per-agent model status and auth order scoping. (#4780) Thanks @jlowin.
|
|
||||||
- Auth: show copyable Google auth URL after ASCII prompt. (#1787) Thanks @robbyczgw-cla.
|
- Auth: show copyable Google auth URL after ASCII prompt. (#1787) Thanks @robbyczgw-cla.
|
||||||
- Agents: add Kimi K2.5 to the synthetic model catalog. (#4407) Thanks @manikv12.
|
|
||||||
- TUI: avoid width overflow when rendering selection lists. (#1686) Thanks @mossein.
|
- TUI: avoid width overflow when rendering selection lists. (#1686) Thanks @mossein.
|
||||||
- macOS: finish OpenClaw app rename for macOS sources, bundle identifiers, and shared kit paths. (#2844) Thanks @fal3.
|
- macOS: finish OpenClaw app rename for macOS sources, bundle identifiers, and shared kit paths. (#2844) Thanks @fal3.
|
||||||
- Branding: update launchd labels, mobile bundle IDs, and logging subsystems to bot.molt (legacy bundle ID migrations). Thanks @thewilloftheshadow.
|
- Branding: update launchd labels, mobile bundle IDs, and logging subsystems to bot.molt (legacy bundle ID migrations). Thanks @thewilloftheshadow.
|
||||||
@@ -69,18 +254,15 @@ Status: stable.
|
|||||||
- Docs: credit both contributors for Control UI refresh. (#1852) Thanks @EnzeD.
|
- Docs: credit both contributors for Control UI refresh. (#1852) Thanks @EnzeD.
|
||||||
- Docs: keep docs header sticky so navbar stays visible while scrolling. (#2445) Thanks @chenyuan99.
|
- Docs: keep docs header sticky so navbar stays visible while scrolling. (#2445) Thanks @chenyuan99.
|
||||||
- Docs: update exe.dev install instructions. (#https://github.com/openclaw/openclaw/pull/3047) Thanks @zackerthescar.
|
- Docs: update exe.dev install instructions. (#https://github.com/openclaw/openclaw/pull/3047) Thanks @zackerthescar.
|
||||||
- Build: skip redundant UI install step in the Dockerfile. (#4584) Thanks @obviyus.
|
|
||||||
### Breaking
|
### Breaking
|
||||||
|
|
||||||
- **BREAKING:** Gateway auth mode "none" is removed; gateway now requires token/password (Tailscale Serve identity still allowed).
|
- **BREAKING:** Gateway auth mode "none" is removed; gateway now requires token/password (Tailscale Serve identity still allowed).
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
- Infra: resolve Control UI assets for npm global installs. (#4909) Thanks @YuriNachos.
|
|
||||||
- Gateway: prevent blank token prompts from storing "undefined". (#4873) Thanks @Hisleren.
|
- Skills: update session-logs paths to use ~/.openclaw. (#4502) Thanks @bonald.
|
||||||
- Telegram: use undici fetch for per-account proxy dispatcher. (#4456) Thanks @spiceoogway.
|
|
||||||
- Telegram: fix HTML nesting for overlapping styles and links. (#4578) Thanks @ThanhNguyxn.
|
|
||||||
- Telegram: avoid silent empty replies by tracking normalization skips before fallback. (#3796)
|
- Telegram: avoid silent empty replies by tracking normalization skips before fallback. (#3796)
|
||||||
- Telegram: accept numeric messageId/chatId in react action and honor channelId fallback. (#4533) Thanks @Ayush10.
|
|
||||||
- Telegram: scope native skill commands to bound agent per bot. (#4360) Thanks @robhparker.
|
|
||||||
- Mentions: honor mentionPatterns even when explicit mentions are present. (#3303) Thanks @HirokiKobayashi-R.
|
- Mentions: honor mentionPatterns even when explicit mentions are present. (#3303) Thanks @HirokiKobayashi-R.
|
||||||
- Discord: restore username directory lookup in target resolution. (#3131) Thanks @bonald.
|
- Discord: restore username directory lookup in target resolution. (#3131) Thanks @bonald.
|
||||||
- Agents: align MiniMax base URL test expectation with default provider config. (#3131) Thanks @bonald.
|
- Agents: align MiniMax base URL test expectation with default provider config. (#3131) Thanks @bonald.
|
||||||
@@ -92,7 +274,6 @@ Status: stable.
|
|||||||
- TTS: read OPENAI_TTS_BASE_URL at runtime instead of module load to honor config.env. (#3341) Thanks @hclsys.
|
- TTS: read OPENAI_TTS_BASE_URL at runtime instead of module load to honor config.env. (#3341) Thanks @hclsys.
|
||||||
- macOS: auto-scroll to bottom when sending a new message while scrolled up. (#2471) Thanks @kennyklee.
|
- macOS: auto-scroll to bottom when sending a new message while scrolled up. (#2471) Thanks @kennyklee.
|
||||||
- Web UI: auto-expand the chat compose textarea while typing (with sensible max height). (#2950) Thanks @shivamraut101.
|
- Web UI: auto-expand the chat compose textarea while typing (with sensible max height). (#2950) Thanks @shivamraut101.
|
||||||
- Web UI: refresh sessions after queued /new or /reset commands once the run completes.
|
|
||||||
- Gateway: prevent crashes on transient network errors (fetch failures, timeouts, DNS). Added fatal error detection to only exit on truly critical errors. Fixes #2895, #2879, #2873. (#2980) Thanks @elliotsecops.
|
- Gateway: prevent crashes on transient network errors (fetch failures, timeouts, DNS). Added fatal error detection to only exit on truly critical errors. Fixes #2895, #2879, #2873. (#2980) Thanks @elliotsecops.
|
||||||
- Agents: guard channel tool listActions to avoid plugin crashes. (#2859) Thanks @mbelinky.
|
- Agents: guard channel tool listActions to avoid plugin crashes. (#2859) Thanks @mbelinky.
|
||||||
- Discord: stop resolveDiscordTarget from passing directory params into messaging target parsers. Fixes #3167. Thanks @thewilloftheshadow.
|
- Discord: stop resolveDiscordTarget from passing directory params into messaging target parsers. Fixes #3167. Thanks @thewilloftheshadow.
|
||||||
@@ -133,6 +314,7 @@ Status: stable.
|
|||||||
## 2026.1.24-3
|
## 2026.1.24-3
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
- Slack: fix image downloads failing due to missing Authorization header on cross-origin redirects. (#1936) Thanks @sanderhelgesen.
|
- Slack: fix image downloads failing due to missing Authorization header on cross-origin redirects. (#1936) Thanks @sanderhelgesen.
|
||||||
- Gateway: harden reverse proxy handling for local-client detection and unauthenticated proxied connects. (#1795) Thanks @orlyjamie.
|
- Gateway: harden reverse proxy handling for local-client detection and unauthenticated proxied connects. (#1795) Thanks @orlyjamie.
|
||||||
- Security audit: flag loopback Control UI with auth disabled as critical. (#1795) Thanks @orlyjamie.
|
- Security audit: flag loopback Control UI with auth disabled as critical. (#1795) Thanks @orlyjamie.
|
||||||
@@ -141,16 +323,19 @@ Status: stable.
|
|||||||
## 2026.1.24-2
|
## 2026.1.24-2
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
- Packaging: include dist/link-understanding output in npm tarball (fixes missing apply.js import on install).
|
- Packaging: include dist/link-understanding output in npm tarball (fixes missing apply.js import on install).
|
||||||
|
|
||||||
## 2026.1.24-1
|
## 2026.1.24-1
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
- Packaging: include dist/shared output in npm tarball (fixes missing reasoning-tags import on install).
|
- Packaging: include dist/shared output in npm tarball (fixes missing reasoning-tags import on install).
|
||||||
|
|
||||||
## 2026.1.24
|
## 2026.1.24
|
||||||
|
|
||||||
### Highlights
|
### Highlights
|
||||||
|
|
||||||
- Providers: Ollama discovery + docs; Venice guide upgrades + cross-links. (#1606) Thanks @abhaymundhara. https://docs.openclaw.ai/providers/ollama https://docs.openclaw.ai/providers/venice
|
- Providers: Ollama discovery + docs; Venice guide upgrades + cross-links. (#1606) Thanks @abhaymundhara. https://docs.openclaw.ai/providers/ollama https://docs.openclaw.ai/providers/venice
|
||||||
- Channels: LINE plugin (Messaging API) with rich replies + quick replies. (#1630) Thanks @plum-dawg.
|
- Channels: LINE plugin (Messaging API) with rich replies + quick replies. (#1630) Thanks @plum-dawg.
|
||||||
- TTS: Edge fallback (keyless) + `/tts` auto modes. (#1668, #1667) Thanks @steipete, @sebslight. https://docs.openclaw.ai/tts
|
- TTS: Edge fallback (keyless) + `/tts` auto modes. (#1668, #1667) Thanks @steipete, @sebslight. https://docs.openclaw.ai/tts
|
||||||
@@ -158,6 +343,7 @@ Status: stable.
|
|||||||
- Telegram: DM topics as separate sessions + outbound link preview toggle. (#1597, #1700) Thanks @rohannagpal, @zerone0x. https://docs.openclaw.ai/channels/telegram
|
- Telegram: DM topics as separate sessions + outbound link preview toggle. (#1597, #1700) Thanks @rohannagpal, @zerone0x. https://docs.openclaw.ai/channels/telegram
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
|
|
||||||
- Channels: add LINE plugin (Messaging API) with rich replies, quick replies, and plugin HTTP registry. (#1630) Thanks @plum-dawg.
|
- Channels: add LINE plugin (Messaging API) with rich replies, quick replies, and plugin HTTP registry. (#1630) Thanks @plum-dawg.
|
||||||
- TTS: add Edge TTS provider fallback, defaulting to keyless Edge with MP3 retry on format failures. (#1668) Thanks @steipete. https://docs.openclaw.ai/tts
|
- TTS: add Edge TTS provider fallback, defaulting to keyless Edge with MP3 retry on format failures. (#1668) Thanks @steipete. https://docs.openclaw.ai/tts
|
||||||
- TTS: add auto mode enum (off/always/inbound/tagged) with per-session `/tts` override. (#1667) Thanks @sebslight. https://docs.openclaw.ai/tts
|
- TTS: add auto mode enum (off/always/inbound/tagged) with per-session `/tts` override. (#1667) Thanks @sebslight. https://docs.openclaw.ai/tts
|
||||||
@@ -176,6 +362,7 @@ Status: stable.
|
|||||||
- Dev: add prek pre-commit hooks + dependabot config for weekly updates. (#1720) Thanks @dguido.
|
- Dev: add prek pre-commit hooks + dependabot config for weekly updates. (#1720) Thanks @dguido.
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
- Web UI: fix config/debug layout overflow, scrolling, and code block sizing. (#1715) Thanks @saipreetham589.
|
- Web UI: fix config/debug layout overflow, scrolling, and code block sizing. (#1715) Thanks @saipreetham589.
|
||||||
- Web UI: show Stop button during active runs, swap back to New session when idle. (#1664) Thanks @ndbroadbent.
|
- Web UI: show Stop button during active runs, swap back to New session when idle. (#1664) Thanks @ndbroadbent.
|
||||||
- Web UI: clear stale disconnect banners on reconnect; allow form saves with unsupported schema paths but block missing schema. (#1707) Thanks @Glucksberg.
|
- Web UI: clear stale disconnect banners on reconnect; allow form saves with unsupported schema paths but block missing schema. (#1707) Thanks @Glucksberg.
|
||||||
@@ -219,11 +406,13 @@ Status: stable.
|
|||||||
## 2026.1.23-1
|
## 2026.1.23-1
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
- Packaging: include dist/tts output in npm tarball (fixes missing dist/tts/tts.js).
|
- Packaging: include dist/tts output in npm tarball (fixes missing dist/tts/tts.js).
|
||||||
|
|
||||||
## 2026.1.23
|
## 2026.1.23
|
||||||
|
|
||||||
### Highlights
|
### Highlights
|
||||||
|
|
||||||
- TTS: move Telegram TTS into core + enable model-driven TTS tags by default for expressive audio replies. (#1559) Thanks @Glucksberg. https://docs.openclaw.ai/tts
|
- TTS: move Telegram TTS into core + enable model-driven TTS tags by default for expressive audio replies. (#1559) Thanks @Glucksberg. https://docs.openclaw.ai/tts
|
||||||
- Gateway: add `/tools/invoke` HTTP endpoint for direct tool calls (auth + tool policy enforced). (#1575) Thanks @vignesh07. https://docs.openclaw.ai/gateway/tools-invoke-http-api
|
- Gateway: add `/tools/invoke` HTTP endpoint for direct tool calls (auth + tool policy enforced). (#1575) Thanks @vignesh07. https://docs.openclaw.ai/gateway/tools-invoke-http-api
|
||||||
- Heartbeat: per-channel visibility controls (OK/alerts/indicator). (#1452) Thanks @dlauer. https://docs.openclaw.ai/gateway/heartbeat
|
- Heartbeat: per-channel visibility controls (OK/alerts/indicator). (#1452) Thanks @dlauer. https://docs.openclaw.ai/gateway/heartbeat
|
||||||
@@ -231,6 +420,7 @@ Status: stable.
|
|||||||
- Channels: add Tlon/Urbit channel plugin (DMs, group mentions, thread replies). (#1544) Thanks @wca4a. https://docs.openclaw.ai/channels/tlon
|
- Channels: add Tlon/Urbit channel plugin (DMs, group mentions, thread replies). (#1544) Thanks @wca4a. https://docs.openclaw.ai/channels/tlon
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
|
|
||||||
- Channels: allow per-group tool allow/deny policies across built-in + plugin channels. (#1546) Thanks @adam91holt. https://docs.openclaw.ai/multi-agent-sandbox-tools
|
- Channels: allow per-group tool allow/deny policies across built-in + plugin channels. (#1546) Thanks @adam91holt. https://docs.openclaw.ai/multi-agent-sandbox-tools
|
||||||
- Agents: add Bedrock auto-discovery defaults + config overrides. (#1553) Thanks @fal3. https://docs.openclaw.ai/bedrock
|
- Agents: add Bedrock auto-discovery defaults + config overrides. (#1553) Thanks @fal3. https://docs.openclaw.ai/bedrock
|
||||||
- CLI: add `openclaw system` for system events + heartbeat controls; remove standalone `wake`. (commit 71203829d) https://docs.openclaw.ai/cli/system
|
- CLI: add `openclaw system` for system events + heartbeat controls; remove standalone `wake`. (commit 71203829d) https://docs.openclaw.ai/cli/system
|
||||||
@@ -245,6 +435,7 @@ Status: stable.
|
|||||||
- Docs: clarify HEARTBEAT.md empty file skips heartbeats, missing file still runs. (#1535) Thanks @JustYannicc. https://docs.openclaw.ai/gateway/heartbeat
|
- Docs: clarify HEARTBEAT.md empty file skips heartbeats, missing file still runs. (#1535) Thanks @JustYannicc. https://docs.openclaw.ai/gateway/heartbeat
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
- Sessions: accept non-UUID sessionIds for history/send/status while preserving agent scoping. (#1518)
|
- Sessions: accept non-UUID sessionIds for history/send/status while preserving agent scoping. (#1518)
|
||||||
- Heartbeat: accept plugin channel ids for heartbeat target validation + UI hints.
|
- Heartbeat: accept plugin channel ids for heartbeat target validation + UI hints.
|
||||||
- Messaging/Sessions: mirror outbound sends into target session keys (threads + dmScope), create session entries on send, and normalize session key casing. (#1520, commit 4b6cdd1d3)
|
- Messaging/Sessions: mirror outbound sends into target session keys (threads + dmScope), create session entries on send, and normalize session key casing. (#1520, commit 4b6cdd1d3)
|
||||||
@@ -283,6 +474,7 @@ Status: stable.
|
|||||||
## 2026.1.22
|
## 2026.1.22
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
|
|
||||||
- Highlight: Compaction safeguard now uses adaptive chunking, progressive fallback, and UI status + retries. (#1466) Thanks @dlauer.
|
- Highlight: Compaction safeguard now uses adaptive chunking, progressive fallback, and UI status + retries. (#1466) Thanks @dlauer.
|
||||||
- Providers: add Antigravity usage tracking to status output. (#1490) Thanks @patelhiren.
|
- Providers: add Antigravity usage tracking to status output. (#1490) Thanks @patelhiren.
|
||||||
- Slack: add chat-type reply threading overrides via `replyToModeByChatType`. (#1442) Thanks @stefangalescu.
|
- Slack: add chat-type reply threading overrides via `replyToModeByChatType`. (#1442) Thanks @stefangalescu.
|
||||||
@@ -290,6 +482,7 @@ Status: stable.
|
|||||||
- Onboarding: add hatch choice (TUI/Web/Later), token explainer, background dashboard seed on macOS, and showcase link.
|
- Onboarding: add hatch choice (TUI/Web/Later), token explainer, background dashboard seed on macOS, and showcase link.
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
- BlueBubbles: stop typing indicator on idle/no-reply. (#1439) Thanks @Nicell.
|
- BlueBubbles: stop typing indicator on idle/no-reply. (#1439) Thanks @Nicell.
|
||||||
- Message tool: keep path/filePath as-is for send; hydrate buffers only for sendAttachment. (#1444) Thanks @hopyky.
|
- Message tool: keep path/filePath as-is for send; hydrate buffers only for sendAttachment. (#1444) Thanks @hopyky.
|
||||||
- Auto-reply: only report a model switch when session state is available. (#1465) Thanks @robbyczgw-cla.
|
- Auto-reply: only report a model switch when session state is available. (#1465) Thanks @robbyczgw-cla.
|
||||||
@@ -320,12 +513,14 @@ Status: stable.
|
|||||||
## 2026.1.21-2
|
## 2026.1.21-2
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
- Control UI: ignore bootstrap identity placeholder text for avatar values and fall back to the default avatar. https://docs.openclaw.ai/cli/agents https://docs.openclaw.ai/web/control-ui
|
- Control UI: ignore bootstrap identity placeholder text for avatar values and fall back to the default avatar. https://docs.openclaw.ai/cli/agents https://docs.openclaw.ai/web/control-ui
|
||||||
- Slack: remove deprecated `filetype` field from `files.uploadV2` to eliminate API warnings. (#1447)
|
- Slack: remove deprecated `filetype` field from `files.uploadV2` to eliminate API warnings. (#1447)
|
||||||
|
|
||||||
## 2026.1.21
|
## 2026.1.21
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
|
|
||||||
- Highlight: Lobster optional plugin tool for typed workflows + approval gates. https://docs.openclaw.ai/tools/lobster
|
- Highlight: Lobster optional plugin tool for typed workflows + approval gates. https://docs.openclaw.ai/tools/lobster
|
||||||
- Lobster: allow workflow file args via `argsJson` in the plugin tool. https://docs.openclaw.ai/tools/lobster
|
- Lobster: allow workflow file args via `argsJson` in the plugin tool. https://docs.openclaw.ai/tools/lobster
|
||||||
- Heartbeat: allow running heartbeats in an explicit session key. (#1256) Thanks @zknicker.
|
- Heartbeat: allow running heartbeats in an explicit session key. (#1256) Thanks @zknicker.
|
||||||
@@ -348,10 +543,12 @@ Status: stable.
|
|||||||
- Docs: add per-message Gmail search example for gog. (#1220) Thanks @mbelinky.
|
- Docs: add per-message Gmail search example for gog. (#1220) Thanks @mbelinky.
|
||||||
|
|
||||||
### Breaking
|
### Breaking
|
||||||
|
|
||||||
- **BREAKING:** Control UI now rejects insecure HTTP without device identity by default. Use HTTPS (Tailscale Serve) or set `gateway.controlUi.allowInsecureAuth: true` to allow token-only auth. https://docs.openclaw.ai/web/control-ui#insecure-http
|
- **BREAKING:** Control UI now rejects insecure HTTP without device identity by default. Use HTTPS (Tailscale Serve) or set `gateway.controlUi.allowInsecureAuth: true` to allow token-only auth. https://docs.openclaw.ai/web/control-ui#insecure-http
|
||||||
- **BREAKING:** Envelope and system event timestamps now default to host-local time (was UTC) so agents don’t have to constantly convert.
|
- **BREAKING:** Envelope and system event timestamps now default to host-local time (was UTC) so agents don’t have to constantly convert.
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
- Nodes/macOS: prompt on allowlist miss for node exec approvals, persist allowlist decisions, and flatten node invoke errors. (#1394) Thanks @ngutman.
|
- Nodes/macOS: prompt on allowlist miss for node exec approvals, persist allowlist decisions, and flatten node invoke errors. (#1394) Thanks @ngutman.
|
||||||
- Gateway: keep auto bind loopback-first and add explicit tailnet binding to avoid Tailscale taking over local UI. (#1380)
|
- Gateway: keep auto bind loopback-first and add explicit tailnet binding to avoid Tailscale taking over local UI. (#1380)
|
||||||
- Memory: prevent CLI hangs by deferring vector probes, adding sqlite-vec/embedding timeouts, and showing sync progress early.
|
- Memory: prevent CLI hangs by deferring vector probes, adding sqlite-vec/embedding timeouts, and showing sync progress early.
|
||||||
@@ -374,6 +571,7 @@ Status: stable.
|
|||||||
## 2026.1.20
|
## 2026.1.20
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
|
|
||||||
- Control UI: add copy-as-markdown with error feedback. (#1345) https://docs.openclaw.ai/web/control-ui
|
- Control UI: add copy-as-markdown with error feedback. (#1345) https://docs.openclaw.ai/web/control-ui
|
||||||
- Control UI: drop the legacy list view. (#1345) https://docs.openclaw.ai/web/control-ui
|
- Control UI: drop the legacy list view. (#1345) https://docs.openclaw.ai/web/control-ui
|
||||||
- TUI: add syntax highlighting for code blocks. (#1200) https://docs.openclaw.ai/tui
|
- TUI: add syntax highlighting for code blocks. (#1200) https://docs.openclaw.ai/tui
|
||||||
@@ -452,9 +650,11 @@ Status: stable.
|
|||||||
- Swabble: use the tagged Commander Swift package release.
|
- Swabble: use the tagged Commander Swift package release.
|
||||||
|
|
||||||
### Breaking
|
### Breaking
|
||||||
|
|
||||||
- **BREAKING:** Reject invalid/unknown config entries and refuse to start the gateway for safety. Run `openclaw doctor --fix` to repair, then update plugins (`openclaw plugins update`) if you use any.
|
- **BREAKING:** Reject invalid/unknown config entries and refuse to start the gateway for safety. Run `openclaw doctor --fix` to repair, then update plugins (`openclaw plugins update`) if you use any.
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
- Discovery: shorten Bonjour DNS-SD service type to `_moltbot-gw._tcp` and update discovery clients/docs.
|
- Discovery: shorten Bonjour DNS-SD service type to `_moltbot-gw._tcp` and update discovery clients/docs.
|
||||||
- Diagnostics: export OTLP logs, correct queue depth tracking, and document message-flow telemetry.
|
- Diagnostics: export OTLP logs, correct queue depth tracking, and document message-flow telemetry.
|
||||||
- Diagnostics: emit message-flow diagnostics across channels via shared dispatch. (#1244)
|
- Diagnostics: emit message-flow diagnostics across channels via shared dispatch. (#1244)
|
||||||
@@ -554,20 +754,23 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
|
|||||||
## 2026.1.16-2
|
## 2026.1.16-2
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
|
|
||||||
- CLI: stamp build commit into dist metadata so banners show the commit in npm installs.
|
- CLI: stamp build commit into dist metadata so banners show the commit in npm installs.
|
||||||
- CLI: close memory manager after memory commands to avoid hanging processes. (#1127) — thanks @NicholasSpisak.
|
- CLI: close memory manager after memory commands to avoid hanging processes. (#1127) — thanks @NicholasSpisak.
|
||||||
|
|
||||||
## 2026.1.16-1
|
## 2026.1.16-1
|
||||||
|
|
||||||
### Highlights
|
### Highlights
|
||||||
|
|
||||||
- Hooks: add hooks system with bundled hooks, CLI tooling, and docs. (#1028) — thanks @ThomsenDrake. https://docs.openclaw.ai/hooks
|
- Hooks: add hooks system with bundled hooks, CLI tooling, and docs. (#1028) — thanks @ThomsenDrake. https://docs.openclaw.ai/hooks
|
||||||
- Media: add inbound media understanding (image/audio/video) with provider + CLI fallbacks. https://docs.openclaw.ai/nodes/media-understanding
|
- Media: add inbound media understanding (image/audio/video) with provider + CLI fallbacks. https://docs.openclaw.ai/nodes/media-understanding
|
||||||
- Plugins: add Zalo Personal plugin (`@openclaw/zalouser`) and unify channel directory for plugins. (#1032) — thanks @suminhthanh. https://docs.openclaw.ai/plugins/zalouser
|
- Plugins: add Zalo Personal plugin (`@openclaw/zalouser`) and unify channel directory for plugins. (#1032) — thanks @suminhthanh. https://docs.openclaw.ai/plugins/zalouser
|
||||||
- Models: add Vercel AI Gateway auth choice + onboarding updates. (#1016) — thanks @timolins. https://docs.openclaw.ai/providers/vercel-ai-gateway
|
- Models: add Vercel AI Gateway auth choice + onboarding updates. (#1016) — thanks @timolins. https://docs.openclaw.ai/providers/vercel-ai-gateway
|
||||||
- Sessions: add `session.identityLinks` for cross-platform DM session li nking. (#1033) — thanks @thewilloftheshadow. https://docs.openclaw.ai/concepts/session
|
- Sessions: add `session.identityLinks` for cross-platform DM session li nking. (#1033) — thanks @thewilloftheshadow. https://docs.openclaw.ai/concepts/session
|
||||||
- Web search: add `country`/`language` parameters (schema + Brave API) and docs. (#1046) — thanks @YuriNachos. https://docs.openclaw.ai/tools/web
|
- Web search: add `country`/`language` parameters (schema + Brave API) and docs. (#1046) — thanks @YuriNachos. https://docs.openclaw.ai/tools/web
|
||||||
|
|
||||||
### Breaking
|
### Breaking
|
||||||
|
|
||||||
- **BREAKING:** `openclaw message` and message tool now require `target` (dropping `to`/`channelId` for destinations). (#1034) — thanks @tobalsan.
|
- **BREAKING:** `openclaw message` and message tool now require `target` (dropping `to`/`channelId` for destinations). (#1034) — thanks @tobalsan.
|
||||||
- **BREAKING:** Channel auth now prefers config over env for Discord/Telegram/Matrix (env is fallback only). (#1040) — thanks @thewilloftheshadow.
|
- **BREAKING:** Channel auth now prefers config over env for Discord/Telegram/Matrix (env is fallback only). (#1040) — thanks @thewilloftheshadow.
|
||||||
- **BREAKING:** Drop legacy `chatType: "room"` support; use `chatType: "channel"`.
|
- **BREAKING:** Drop legacy `chatType: "room"` support; use `chatType: "channel"`.
|
||||||
@@ -576,6 +779,7 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
|
|||||||
- **BREAKING:** `openclaw plugins install <path>` now copies into `~/.openclaw/extensions` (use `--link` to keep path-based loading).
|
- **BREAKING:** `openclaw plugins install <path>` now copies into `~/.openclaw/extensions` (use `--link` to keep path-based loading).
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
|
|
||||||
- Plugins: ship bundled plugins disabled by default and allow overrides by installed versions. (#1066) — thanks @ItzR3NO.
|
- Plugins: ship bundled plugins disabled by default and allow overrides by installed versions. (#1066) — thanks @ItzR3NO.
|
||||||
- Plugins: add bundled Antigravity + Gemini CLI OAuth + Copilot Proxy provider plugins. (#1066) — thanks @ItzR3NO.
|
- Plugins: add bundled Antigravity + Gemini CLI OAuth + Copilot Proxy provider plugins. (#1066) — thanks @ItzR3NO.
|
||||||
- Tools: improve `web_fetch` extraction using Readability (with fallback).
|
- Tools: improve `web_fetch` extraction using Readability (with fallback).
|
||||||
@@ -611,6 +815,7 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
|
|||||||
- Plugins: add zip installs and `--link` to avoid copying local paths.
|
- Plugins: add zip installs and `--link` to avoid copying local paths.
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
- macOS: drain subprocess pipes before waiting to avoid deadlocks. (#1081) — thanks @thesash.
|
- macOS: drain subprocess pipes before waiting to avoid deadlocks. (#1081) — thanks @thesash.
|
||||||
- Verbose: wrap tool summaries/output in markdown only for markdown-capable channels.
|
- Verbose: wrap tool summaries/output in markdown only for markdown-capable channels.
|
||||||
- Tools: include provider/session context in elevated exec denial errors.
|
- Tools: include provider/session context in elevated exec denial errors.
|
||||||
@@ -667,17 +872,20 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
|
|||||||
## 2026.1.15
|
## 2026.1.15
|
||||||
|
|
||||||
### Highlights
|
### Highlights
|
||||||
|
|
||||||
- Plugins: add provider auth registry + `openclaw models auth login` for plugin-driven OAuth/API key flows.
|
- Plugins: add provider auth registry + `openclaw models auth login` for plugin-driven OAuth/API key flows.
|
||||||
- Browser: improve remote CDP/Browserless support (auth passthrough, `wss` upgrade, timeouts, clearer errors).
|
- Browser: improve remote CDP/Browserless support (auth passthrough, `wss` upgrade, timeouts, clearer errors).
|
||||||
- Heartbeat: per-agent configuration + 24h duplicate suppression. (#980) — thanks @voidserf.
|
- Heartbeat: per-agent configuration + 24h duplicate suppression. (#980) — thanks @voidserf.
|
||||||
- Security: audit warns on weak model tiers; app nodes store auth tokens encrypted (Keychain/SecurePrefs).
|
- Security: audit warns on weak model tiers; app nodes store auth tokens encrypted (Keychain/SecurePrefs).
|
||||||
|
|
||||||
### Breaking
|
### Breaking
|
||||||
|
|
||||||
- **BREAKING:** iOS minimum version is now 18.0 to support Textual markdown rendering in native chat. (#702)
|
- **BREAKING:** iOS minimum version is now 18.0 to support Textual markdown rendering in native chat. (#702)
|
||||||
- **BREAKING:** Microsoft Teams is now a plugin; install `@openclaw/msteams` via `openclaw plugins install @openclaw/msteams`.
|
- **BREAKING:** Microsoft Teams is now a plugin; install `@openclaw/msteams` via `openclaw plugins install @openclaw/msteams`.
|
||||||
- **BREAKING:** Channel auth now prefers config over env for Discord/Telegram/Matrix (env is fallback only). (#1040) — thanks @thewilloftheshadow.
|
- **BREAKING:** Channel auth now prefers config over env for Discord/Telegram/Matrix (env is fallback only). (#1040) — thanks @thewilloftheshadow.
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
|
|
||||||
- UI/Apps: move channel/config settings to schema-driven forms and rename Connections → Channels. (#1040) — thanks @thewilloftheshadow.
|
- UI/Apps: move channel/config settings to schema-driven forms and rename Connections → Channels. (#1040) — thanks @thewilloftheshadow.
|
||||||
- CLI: set process titles to `openclaw-<command>` for clearer process listings.
|
- CLI: set process titles to `openclaw-<command>` for clearer process listings.
|
||||||
- CLI/macOS: sync remote SSH target/identity to config and let `gateway status` auto-infer SSH targets (ssh-config aware).
|
- CLI/macOS: sync remote SSH target/identity to config and let `gateway status` auto-infer SSH targets (ssh-config aware).
|
||||||
@@ -717,6 +925,7 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
|
|||||||
- Discord: allow emoji/sticker uploads + channel actions in config defaults. (#870) — thanks @JDIVE.
|
- Discord: allow emoji/sticker uploads + channel actions in config defaults. (#870) — thanks @JDIVE.
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
- Messages: make `/stop` clear queued followups and pending session lane work for a hard abort.
|
- Messages: make `/stop` clear queued followups and pending session lane work for a hard abort.
|
||||||
- Messages: make `/stop` abort active sub-agent runs spawned from the requester session and report how many were stopped.
|
- Messages: make `/stop` abort active sub-agent runs spawned from the requester session and report how many were stopped.
|
||||||
- WhatsApp: report linked status consistently in channel status. (#1050) — thanks @YuriNachos.
|
- WhatsApp: report linked status consistently in channel status. (#1050) — thanks @YuriNachos.
|
||||||
@@ -753,12 +962,14 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
|
|||||||
## 2026.1.14-1
|
## 2026.1.14-1
|
||||||
|
|
||||||
### Highlights
|
### Highlights
|
||||||
|
|
||||||
- Web search: `web_search`/`web_fetch` tools (Brave API) + first-time setup in onboarding/configure.
|
- Web search: `web_search`/`web_fetch` tools (Brave API) + first-time setup in onboarding/configure.
|
||||||
- Browser control: Chrome extension relay takeover mode + remote browser control support.
|
- Browser control: Chrome extension relay takeover mode + remote browser control support.
|
||||||
- Plugins: channel plugins (gateway HTTP hooks) + Zalo plugin + onboarding install flow. (#854) — thanks @longmaba.
|
- Plugins: channel plugins (gateway HTTP hooks) + Zalo plugin + onboarding install flow. (#854) — thanks @longmaba.
|
||||||
- Security: expanded `openclaw security audit` (+ `--fix`), detect-secrets CI scan, and a `SECURITY.md` reporting policy.
|
- Security: expanded `openclaw security audit` (+ `--fix`), detect-secrets CI scan, and a `SECURITY.md` reporting policy.
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
|
|
||||||
- Docs: clarify per-agent auth stores, sandboxed skill binaries, and elevated semantics.
|
- Docs: clarify per-agent auth stores, sandboxed skill binaries, and elevated semantics.
|
||||||
- Docs: add FAQ entries for missing provider auth after adding agents and Gemini thinking signature errors.
|
- Docs: add FAQ entries for missing provider auth after adding agents and Gemini thinking signature errors.
|
||||||
- Agents: add optional auth-profile copy prompt on `agents add` and improve auth error messaging.
|
- Agents: add optional auth-profile copy prompt on `agents add` and improve auth error messaging.
|
||||||
@@ -775,6 +986,7 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
|
|||||||
- Browser: add Chrome extension relay takeover mode (toolbar button), plus `openclaw browser extension install/path` and remote browser control (standalone server + token auth).
|
- Browser: add Chrome extension relay takeover mode (toolbar button), plus `openclaw browser extension install/path` and remote browser control (standalone server + token auth).
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
- Sessions: refactor session store updates to lock + mutate per-entry, add chat.inject, and harden subagent cleanup flow. (#944) — thanks @tyler6204.
|
- Sessions: refactor session store updates to lock + mutate per-entry, add chat.inject, and harden subagent cleanup flow. (#944) — thanks @tyler6204.
|
||||||
- Browser: add tests for snapshot labels/efficient query params and labeled image responses.
|
- Browser: add tests for snapshot labels/efficient query params and labeled image responses.
|
||||||
- Google: downgrade unsigned thinking blocks before send to avoid missing signature errors.
|
- Google: downgrade unsigned thinking blocks before send to avoid missing signature errors.
|
||||||
@@ -796,6 +1008,7 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
|
|||||||
## 2026.1.14
|
## 2026.1.14
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
|
|
||||||
- Usage: add MiniMax coding plan usage tracking.
|
- Usage: add MiniMax coding plan usage tracking.
|
||||||
- Auth: label Claude Code CLI auth options. (#915) — thanks @SeanZoR.
|
- Auth: label Claude Code CLI auth options. (#915) — thanks @SeanZoR.
|
||||||
- Docs: standardize Claude Code CLI naming across docs and prompts. (follow-up to #915)
|
- Docs: standardize Claude Code CLI naming across docs and prompts. (follow-up to #915)
|
||||||
@@ -803,14 +1016,16 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
|
|||||||
- Config: add `channels.<provider>.configWrites` gating for channel-initiated config writes; migrate Slack channel IDs.
|
- Config: add `channels.<provider>.configWrites` gating for channel-initiated config writes; migrate Slack channel IDs.
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
- Mac: pass auth token/password to dashboard URL for authenticated access. (#918) — thanks @rahthakor.
|
|
||||||
- UI: use application-defined WebSocket close code (browser compatibility). (#918) — thanks @rahthakor.
|
- Mac: pass auth token/password to dashboard URL for authenticated access. (#918) — thanks @rahthakor.
|
||||||
|
- UI: use application-defined WebSocket close code (browser compatibility). (#918) — thanks @rahthakor.
|
||||||
- TUI: render picker overlays via the overlay stack so /models and /settings display. (#921) — thanks @grizzdank.
|
- TUI: render picker overlays via the overlay stack so /models and /settings display. (#921) — thanks @grizzdank.
|
||||||
- TUI: add a bright spinner + elapsed time in the status line for send/stream/run states.
|
- TUI: add a bright spinner + elapsed time in the status line for send/stream/run states.
|
||||||
- TUI: show LLM error messages (rate limits, auth, etc.) instead of `(no output)`.
|
- TUI: show LLM error messages (rate limits, auth, etc.) instead of `(no output)`.
|
||||||
- Gateway/Dev: ensure `pnpm gateway:dev` always uses the dev profile config + state (`~/.openclaw-dev`).
|
- Gateway/Dev: ensure `pnpm gateway:dev` always uses the dev profile config + state (`~/.openclaw-dev`).
|
||||||
|
|
||||||
#### Agents / Auth / Tools / Sandbox
|
#### Agents / Auth / Tools / Sandbox
|
||||||
|
|
||||||
- Agents: make user time zone and 24-hour time explicit in the system prompt. (#859) — thanks @CashWilliams.
|
- Agents: make user time zone and 24-hour time explicit in the system prompt. (#859) — thanks @CashWilliams.
|
||||||
- Agents: strip downgraded tool call text without eating adjacent replies and filter thinking-tag leaks. (#905) — thanks @erikpr1994.
|
- Agents: strip downgraded tool call text without eating adjacent replies and filter thinking-tag leaks. (#905) — thanks @erikpr1994.
|
||||||
- Agents: cap tool call IDs for OpenAI/OpenRouter to avoid request rejections. (#875) — thanks @j1philli.
|
- Agents: cap tool call IDs for OpenAI/OpenRouter to avoid request rejections. (#875) — thanks @j1philli.
|
||||||
@@ -823,6 +1038,7 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
|
|||||||
- Google: downgrade unsigned thinking blocks before send to avoid missing signature errors.
|
- Google: downgrade unsigned thinking blocks before send to avoid missing signature errors.
|
||||||
|
|
||||||
#### macOS / Apps
|
#### macOS / Apps
|
||||||
|
|
||||||
- macOS: ensure launchd log directory exists with a test-only override. (#909) — thanks @roshanasingh4.
|
- macOS: ensure launchd log directory exists with a test-only override. (#909) — thanks @roshanasingh4.
|
||||||
- macOS: format ConnectionsStore config to satisfy SwiftFormat lint. (#852) — thanks @mneves75.
|
- macOS: format ConnectionsStore config to satisfy SwiftFormat lint. (#852) — thanks @mneves75.
|
||||||
- macOS: pass auth token/password to dashboard URL for authenticated access. (#918) — thanks @rahthakor.
|
- macOS: pass auth token/password to dashboard URL for authenticated access. (#918) — thanks @rahthakor.
|
||||||
@@ -842,12 +1058,14 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
|
|||||||
## 2026.1.13
|
## 2026.1.13
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
- Postinstall: treat already-applied pnpm patches as no-ops to avoid npm/bun install failures.
|
- Postinstall: treat already-applied pnpm patches as no-ops to avoid npm/bun install failures.
|
||||||
- Packaging: pin `@mariozechner/pi-ai` to 0.45.7 and refresh patched dependency to match npm resolution.
|
- Packaging: pin `@mariozechner/pi-ai` to 0.45.7 and refresh patched dependency to match npm resolution.
|
||||||
|
|
||||||
## 2026.1.12-2
|
## 2026.1.12-2
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
- Packaging: include `dist/memory/**` in the npm tarball (fixes `ERR_MODULE_NOT_FOUND` for `dist/memory/index.js`).
|
- Packaging: include `dist/memory/**` in the npm tarball (fixes `ERR_MODULE_NOT_FOUND` for `dist/memory/index.js`).
|
||||||
- Agents: persist sub-agent registry across gateway restarts and resume announce flow safely. (#831) — thanks @roshanasingh4.
|
- Agents: persist sub-agent registry across gateway restarts and resume announce flow safely. (#831) — thanks @roshanasingh4.
|
||||||
- Agents: strip invalid Gemini thought signatures from OpenRouter history to avoid 400s. (#841, #845) — thanks @MatthieuBizien.
|
- Agents: strip invalid Gemini thought signatures from OpenRouter history to avoid 400s. (#841, #845) — thanks @MatthieuBizien.
|
||||||
@@ -855,11 +1073,13 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
|
|||||||
## 2026.1.12-1
|
## 2026.1.12-1
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
- Packaging: include `dist/channels/**` in the npm tarball (fixes `ERR_MODULE_NOT_FOUND` for `dist/channels/registry.js`).
|
- Packaging: include `dist/channels/**` in the npm tarball (fixes `ERR_MODULE_NOT_FOUND` for `dist/channels/registry.js`).
|
||||||
|
|
||||||
## 2026.1.12
|
## 2026.1.12
|
||||||
|
|
||||||
### Highlights
|
### Highlights
|
||||||
|
|
||||||
- **BREAKING:** rename chat “providers” (Slack/Telegram/WhatsApp/…) to **channels** across CLI/RPC/config; legacy config keys auto-migrate on load (and are written back as `channels.*`).
|
- **BREAKING:** rename chat “providers” (Slack/Telegram/WhatsApp/…) to **channels** across CLI/RPC/config; legacy config keys auto-migrate on load (and are written back as `channels.*`).
|
||||||
- Memory: add vector search for agent memories (Markdown-only) with SQLite index, chunking, lazy sync + file watch, and per-agent enablement/fallback.
|
- Memory: add vector search for agent memories (Markdown-only) with SQLite index, chunking, lazy sync + file watch, and per-agent enablement/fallback.
|
||||||
- Plugins: restore full voice-call plugin parity (Telnyx/Twilio, streaming, inbound policies, tools/CLI).
|
- Plugins: restore full voice-call plugin parity (Telnyx/Twilio, streaming, inbound policies, tools/CLI).
|
||||||
@@ -868,6 +1088,7 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
|
|||||||
- Agents: add compaction mode config with optional safeguard summarization and per-agent model fallbacks. (#700) — thanks @thewilloftheshadow; (#583) — thanks @mitschabaude-bot.
|
- Agents: add compaction mode config with optional safeguard summarization and per-agent model fallbacks. (#700) — thanks @thewilloftheshadow; (#583) — thanks @mitschabaude-bot.
|
||||||
|
|
||||||
### New & Improved
|
### New & Improved
|
||||||
|
|
||||||
- Memory: add custom OpenAI-compatible embedding endpoints; support OpenAI/local `node-llama-cpp` embeddings with per-agent overrides and provider metadata in tools/CLI. (#819) — thanks @mukhtharcm.
|
- Memory: add custom OpenAI-compatible embedding endpoints; support OpenAI/local `node-llama-cpp` embeddings with per-agent overrides and provider metadata in tools/CLI. (#819) — thanks @mukhtharcm.
|
||||||
- Memory: new `openclaw memory` CLI plus `memory_search`/`memory_get` tools with snippets + line ranges; index stored under `~/.openclaw/memory/{agentId}.sqlite` with watch-on-by-default.
|
- Memory: new `openclaw memory` CLI plus `memory_search`/`memory_get` tools with snippets + line ranges; index stored under `~/.openclaw/memory/{agentId}.sqlite` with watch-on-by-default.
|
||||||
- Agents: strengthen memory recall guidance; make workspace bootstrap truncation configurable (default 20k) with warnings; add default sub-agent model config.
|
- Agents: strengthen memory recall guidance; make workspace bootstrap truncation configurable (default 20k) with warnings; add default sub-agent model config.
|
||||||
@@ -881,9 +1102,11 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
|
|||||||
- Heartbeat: default `ackMaxChars` to 300 so short `HEARTBEAT_OK` replies stay internal.
|
- Heartbeat: default `ackMaxChars` to 300 so short `HEARTBEAT_OK` replies stay internal.
|
||||||
|
|
||||||
### Installer
|
### Installer
|
||||||
|
|
||||||
- Install: run `openclaw doctor --non-interactive` after git installs/updates and nudge daemon restarts when detected.
|
- Install: run `openclaw doctor --non-interactive` after git installs/updates and nudge daemon restarts when detected.
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
- Doctor: warn on pnpm workspace mismatches, missing Control UI assets, and missing tsx binaries; offer UI rebuilds.
|
- Doctor: warn on pnpm workspace mismatches, missing Control UI assets, and missing tsx binaries; offer UI rebuilds.
|
||||||
- Tools: apply global tool allow/deny even when agent-specific tool policy is set.
|
- Tools: apply global tool allow/deny even when agent-specific tool policy is set.
|
||||||
- Models/Providers: treat credential validation failures as auth errors to trigger fallback; normalize `${ENV_VAR}` apiKey values and auto-fill missing provider keys; preserve explicit GitHub Copilot provider config + agent-dir auth profiles. (#822) — thanks @sebslight; (#705) — thanks @TAGOOZ.
|
- Models/Providers: treat credential validation failures as auth errors to trigger fallback; normalize `${ENV_VAR}` apiKey values and auto-fill missing provider keys; preserve explicit GitHub Copilot provider config + agent-dir auth profiles. (#822) — thanks @sebslight; (#705) — thanks @TAGOOZ.
|
||||||
@@ -908,6 +1131,7 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
|
|||||||
- Connections UI: polish multi-account account cards. (#816) — thanks @steipete.
|
- Connections UI: polish multi-account account cards. (#816) — thanks @steipete.
|
||||||
|
|
||||||
### Maintenance
|
### Maintenance
|
||||||
|
|
||||||
- Dependencies: bump Pi packages to 0.45.3 and refresh patched pi-ai.
|
- Dependencies: bump Pi packages to 0.45.3 and refresh patched pi-ai.
|
||||||
- Testing: update Vitest + browser-playwright to 4.0.17.
|
- Testing: update Vitest + browser-playwright to 4.0.17.
|
||||||
- Docs: add Amazon Bedrock provider notes and link from models/FAQ.
|
- Docs: add Amazon Bedrock provider notes and link from models/FAQ.
|
||||||
@@ -915,12 +1139,14 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
|
|||||||
## 2026.1.11
|
## 2026.1.11
|
||||||
|
|
||||||
### Highlights
|
### Highlights
|
||||||
|
|
||||||
- Plugins are now first-class: loader + CLI management, plus the new Voice Call plugin.
|
- Plugins are now first-class: loader + CLI management, plus the new Voice Call plugin.
|
||||||
- Config: modular `$include` support for split config files. (#731) — thanks @pasogott.
|
- Config: modular `$include` support for split config files. (#731) — thanks @pasogott.
|
||||||
- Agents/Pi: reserve compaction headroom so pre-compaction memory writes can run before auto-compaction.
|
- Agents/Pi: reserve compaction headroom so pre-compaction memory writes can run before auto-compaction.
|
||||||
- Agents: automatic pre-compaction memory flush turn to store durable memories before compaction.
|
- Agents: automatic pre-compaction memory flush turn to store durable memories before compaction.
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
|
|
||||||
- CLI/Onboarding: simplify MiniMax auth choice to a single M2.1 option.
|
- CLI/Onboarding: simplify MiniMax auth choice to a single M2.1 option.
|
||||||
- CLI: configure section selection now loops until Continue.
|
- CLI: configure section selection now loops until Continue.
|
||||||
- Docs: explain MiniMax vs MiniMax Lightning (speed vs cost) and restore LM Studio example.
|
- Docs: explain MiniMax vs MiniMax Lightning (speed vs cost) and restore LM Studio example.
|
||||||
@@ -956,6 +1182,7 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
|
|||||||
- macOS: remove the attach-only gateway setting; local mode now always manages launchd while still attaching to an existing gateway if present.
|
- macOS: remove the attach-only gateway setting; local mode now always manages launchd while still attaching to an existing gateway if present.
|
||||||
|
|
||||||
### Installer
|
### Installer
|
||||||
|
|
||||||
- Postinstall: replace `git apply` with builtin JS patcher (works npm/pnpm/bun; no git dependency) plus regression tests.
|
- Postinstall: replace `git apply` with builtin JS patcher (works npm/pnpm/bun; no git dependency) plus regression tests.
|
||||||
- Postinstall: skip pnpm patch fallback when the new patcher is active.
|
- Postinstall: skip pnpm patch fallback when the new patcher is active.
|
||||||
- Installer tests: add root+non-root docker smokes, CI workflow to fetch openclaw.ai scripts and run install sh/cli with onboarding skipped.
|
- Installer tests: add root+non-root docker smokes, CI workflow to fetch openclaw.ai scripts and run install sh/cli with onboarding skipped.
|
||||||
@@ -964,6 +1191,7 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
|
|||||||
- Installer UX: add `--install-method git|npm` and auto-detect source checkouts (prompt to update git checkout vs migrate to npm).
|
- Installer UX: add `--install-method git|npm` and auto-detect source checkouts (prompt to update git checkout vs migrate to npm).
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
- Models/Onboarding: configure MiniMax (minimax.io) via Anthropic-compatible `/anthropic` endpoint by default (keep `minimax-api` as a legacy alias).
|
- Models/Onboarding: configure MiniMax (minimax.io) via Anthropic-compatible `/anthropic` endpoint by default (keep `minimax-api` as a legacy alias).
|
||||||
- Models: normalize Gemini 3 Pro/Flash IDs to preview names for live model lookups. (#769) — thanks @steipete.
|
- Models: normalize Gemini 3 Pro/Flash IDs to preview names for live model lookups. (#769) — thanks @steipete.
|
||||||
- CLI: fix guardCancel typing for configure prompts. (#769) — thanks @steipete.
|
- CLI: fix guardCancel typing for configure prompts. (#769) — thanks @steipete.
|
||||||
@@ -1003,12 +1231,14 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
|
|||||||
## 2026.1.10
|
## 2026.1.10
|
||||||
|
|
||||||
### Highlights
|
### Highlights
|
||||||
|
|
||||||
- CLI: `openclaw status` now table-based + shows OS/update/gateway/daemon/agents/sessions; `status --all` adds a full read-only debug report (tables, log tails, Tailscale summary, and scan progress via OSC-9 + spinner).
|
- CLI: `openclaw status` now table-based + shows OS/update/gateway/daemon/agents/sessions; `status --all` adds a full read-only debug report (tables, log tails, Tailscale summary, and scan progress via OSC-9 + spinner).
|
||||||
- CLI Backends: add Codex CLI fallback with resume support (text output) and JSONL parsing for new runs, plus a live CLI resume probe.
|
- CLI Backends: add Codex CLI fallback with resume support (text output) and JSONL parsing for new runs, plus a live CLI resume probe.
|
||||||
- CLI: add `openclaw update` (safe-ish git checkout update) + `--update` shorthand. (#673) — thanks @fm1randa.
|
- CLI: add `openclaw update` (safe-ish git checkout update) + `--update` shorthand. (#673) — thanks @fm1randa.
|
||||||
- Gateway: add OpenAI-compatible `/v1/chat/completions` HTTP endpoint (auth, SSE streaming, per-agent routing). (#680).
|
- Gateway: add OpenAI-compatible `/v1/chat/completions` HTTP endpoint (auth, SSE streaming, per-agent routing). (#680).
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
|
|
||||||
- Onboarding/Models: add first-class Z.AI (GLM) auth choice (`zai-api-key`) + `--zai-api-key` flag.
|
- Onboarding/Models: add first-class Z.AI (GLM) auth choice (`zai-api-key`) + `--zai-api-key` flag.
|
||||||
- CLI/Onboarding: add OpenRouter API key auth option in configure/onboard. (#703) — thanks @mteam88.
|
- CLI/Onboarding: add OpenRouter API key auth option in configure/onboard. (#703) — thanks @mteam88.
|
||||||
- Agents: add human-delay pacing between block replies (modes: off/natural/custom, per-agent configurable). (#446) — thanks @tony-freedomology.
|
- Agents: add human-delay pacing between block replies (modes: off/natural/custom, per-agent configurable). (#446) — thanks @tony-freedomology.
|
||||||
@@ -1022,6 +1252,7 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
|
|||||||
- Docker: allow optional home volume + extra bind mounts in `docker-setup.sh`. (#679) — thanks @gabriel-trigo.
|
- Docker: allow optional home volume + extra bind mounts in `docker-setup.sh`. (#679) — thanks @gabriel-trigo.
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
- Auto-reply: suppress draft/typing streaming for `NO_REPLY` (silent system ops) so it doesn’t leak partial output.
|
- Auto-reply: suppress draft/typing streaming for `NO_REPLY` (silent system ops) so it doesn’t leak partial output.
|
||||||
- CLI/Status: expand tables to full terminal width; clarify provider setup vs runtime warnings; richer per-provider detail; token previews in `status` while keeping `status --all` redacted; add troubleshooting link footer; keep log tails pasteable; show gateway auth used when reachable; surface provider runtime errors (Signal/iMessage/Slack); harden `tailscale status --json` parsing; make `status --all` scan progress determinate; and replace the footer with a 3-line “Next steps” recommendation (share/debug/probe).
|
- CLI/Status: expand tables to full terminal width; clarify provider setup vs runtime warnings; richer per-provider detail; token previews in `status` while keeping `status --all` redacted; add troubleshooting link footer; keep log tails pasteable; show gateway auth used when reachable; surface provider runtime errors (Signal/iMessage/Slack); harden `tailscale status --json` parsing; make `status --all` scan progress determinate; and replace the footer with a 3-line “Next steps” recommendation (share/debug/probe).
|
||||||
- CLI/Gateway: clarify that `openclaw gateway status` reports RPC health (connect + RPC) and shows RPC failures separately from connect failures.
|
- CLI/Gateway: clarify that `openclaw gateway status` reports RPC health (connect + RPC) and shows RPC failures separately from connect failures.
|
||||||
@@ -1093,10 +1324,10 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
|
|||||||
- Agents: repair session transcripts by dropping duplicate tool results across the whole history (unblocks Anthropic-compatible APIs after retries).
|
- Agents: repair session transcripts by dropping duplicate tool results across the whole history (unblocks Anthropic-compatible APIs after retries).
|
||||||
- Tests/Live: reset the gateway session between model runs to avoid cross-provider transcript incompatibilities (notably OpenAI Responses reasoning replay rules).
|
- Tests/Live: reset the gateway session between model runs to avoid cross-provider transcript incompatibilities (notably OpenAI Responses reasoning replay rules).
|
||||||
|
|
||||||
|
|
||||||
## 2026.1.9
|
## 2026.1.9
|
||||||
|
|
||||||
### Highlights
|
### Highlights
|
||||||
|
|
||||||
- Microsoft Teams provider: polling, attachments, outbound CLI send, per-channel policy.
|
- Microsoft Teams provider: polling, attachments, outbound CLI send, per-channel policy.
|
||||||
- Models/Auth expansion: OpenCode Zen + MiniMax API onboarding; token auth profiles + auth order; OAuth health in doctor/status.
|
- Models/Auth expansion: OpenCode Zen + MiniMax API onboarding; token auth profiles + auth order; OAuth health in doctor/status.
|
||||||
- CLI/Gateway UX: message subcommands, gateway discover/status/SSH, /config + /debug, sandbox CLI.
|
- CLI/Gateway UX: message subcommands, gateway discover/status/SSH, /config + /debug, sandbox CLI.
|
||||||
@@ -1105,10 +1336,12 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
|
|||||||
- Control UI/TUI: queued messages, session links, reasoning view, mobile polish, logs UX.
|
- Control UI/TUI: queued messages, session links, reasoning view, mobile polish, logs UX.
|
||||||
|
|
||||||
### Breaking
|
### Breaking
|
||||||
|
|
||||||
- CLI: `openclaw message` now subcommands (`message send|poll|...`) and requires `--provider` unless only one provider configured.
|
- CLI: `openclaw message` now subcommands (`message send|poll|...`) and requires `--provider` unless only one provider configured.
|
||||||
- Commands/Tools: `/restart` and gateway restart tool disabled by default; enable with `commands.restart=true`.
|
- Commands/Tools: `/restart` and gateway restart tool disabled by default; enable with `commands.restart=true`.
|
||||||
|
|
||||||
### New Features and Changes
|
### New Features and Changes
|
||||||
|
|
||||||
- Models/Auth: OpenCode Zen onboarding (#623) — thanks @magimetal; MiniMax Anthropic-compatible API + hosted onboarding (#590, #495) — thanks @mneves75, @tobiasbischoff.
|
- Models/Auth: OpenCode Zen onboarding (#623) — thanks @magimetal; MiniMax Anthropic-compatible API + hosted onboarding (#590, #495) — thanks @mneves75, @tobiasbischoff.
|
||||||
- Models/Auth: setup-token + token auth profiles; `openclaw models auth order {get,set,clear}`; per-agent auth candidates in `/model status`; OAuth expiry checks in doctor/status.
|
- Models/Auth: setup-token + token auth profiles; `openclaw models auth order {get,set,clear}`; per-agent auth candidates in `/model status`; OAuth expiry checks in doctor/status.
|
||||||
- Agent/System: claude-cli runner; `session_status` tool (and sandbox allow); adaptive context pruning default; system prompt messaging guidance + no auto self-update; eligible skills list injection; sub-agent context trimmed.
|
- Agent/System: claude-cli runner; `session_status` tool (and sandbox allow); adaptive context pruning default; system prompt messaging guidance + no auto self-update; eligible skills list injection; sub-agent context trimmed.
|
||||||
@@ -1130,6 +1363,7 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
|
|||||||
- Apps/Branding: refreshed iOS/Android/macOS icons (#521) — thanks @fishfisher.
|
- Apps/Branding: refreshed iOS/Android/macOS icons (#521) — thanks @fishfisher.
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
- Packaging: include MS Teams send module in npm tarball.
|
- Packaging: include MS Teams send module in npm tarball.
|
||||||
- Sandbox/Browser: auto-start CDP endpoint; proxy CDP out of container for attachOnly; relax Bun fetch typing; align sandbox list output with config images.
|
- Sandbox/Browser: auto-start CDP endpoint; proxy CDP out of container for attachOnly; relax Bun fetch typing; align sandbox list output with config images.
|
||||||
- Agents/Runtime: gate heartbeat prompt to default sessions; /stop aborts between tool calls; require explicit system-event session keys; guard small context windows; fix model fallback stringification; sessions_spawn inherits provider; failover on billing/credits; respect auth cooldown ordering; restore Anthropic OAuth tool dispatch + tool-name bypass; avoid OpenAI invalid reasoning replay; harden Gmail hook model defaults.
|
- Agents/Runtime: gate heartbeat prompt to default sessions; /stop aborts between tool calls; require explicit system-event session keys; guard small context windows; fix model fallback stringification; sessions_spawn inherits provider; failover on billing/credits; respect auth cooldown ordering; restore Anthropic OAuth tool dispatch + tool-name bypass; avoid OpenAI invalid reasoning replay; harden Gmail hook model defaults.
|
||||||
@@ -1147,7 +1381,8 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
|
|||||||
- Onboarding/Configure: QuickStart single-select provider picker; avoid Codex CLI false-expiry warnings; clarify WhatsApp owner prompt; fix Minimax hosted onboarding (agents.defaults + msteams heartbeat target); remove configure Control UI prompt; honor gateway --dev flag.
|
- Onboarding/Configure: QuickStart single-select provider picker; avoid Codex CLI false-expiry warnings; clarify WhatsApp owner prompt; fix Minimax hosted onboarding (agents.defaults + msteams heartbeat target); remove configure Control UI prompt; honor gateway --dev flag.
|
||||||
|
|
||||||
### Maintenance
|
### Maintenance
|
||||||
- Dependencies: bump pi-* stack to 0.42.2.
|
|
||||||
|
- Dependencies: bump pi-\* stack to 0.42.2.
|
||||||
- Dependencies: Pi 0.40.0 bump (#543) — thanks @mcinteerj.
|
- Dependencies: Pi 0.40.0 bump (#543) — thanks @mcinteerj.
|
||||||
- Build: Docker build cache layer (#605) — thanks @zknicker.
|
- Build: Docker build cache layer (#605) — thanks @zknicker.
|
||||||
|
|
||||||
@@ -1156,6 +1391,7 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
|
|||||||
## 2026.1.8
|
## 2026.1.8
|
||||||
|
|
||||||
### Highlights
|
### Highlights
|
||||||
|
|
||||||
- Security: DMs locked down by default across providers; pairing-first + allowlist guidance.
|
- Security: DMs locked down by default across providers; pairing-first + allowlist guidance.
|
||||||
- Sandbox: per-agent scope defaults + workspace access controls; tool/session isolation tuned.
|
- Sandbox: per-agent scope defaults + workspace access controls; tool/session isolation tuned.
|
||||||
- Agent loop: compaction, pruning, streaming, and error handling hardened.
|
- Agent loop: compaction, pruning, streaming, and error handling hardened.
|
||||||
@@ -1164,6 +1400,7 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
|
|||||||
- CLI/Gateway/Doctor: daemon/logs/status, auth migration, and diagnostics significantly expanded.
|
- CLI/Gateway/Doctor: daemon/logs/status, auth migration, and diagnostics significantly expanded.
|
||||||
|
|
||||||
### Breaking
|
### Breaking
|
||||||
|
|
||||||
- **SECURITY (update ASAP):** inbound DMs are now **locked down by default** on Telegram/WhatsApp/Signal/iMessage/Discord/Slack.
|
- **SECURITY (update ASAP):** inbound DMs are now **locked down by default** on Telegram/WhatsApp/Signal/iMessage/Discord/Slack.
|
||||||
- Previously, if you didn’t configure an allowlist, your bot could be **open to anyone** (especially discoverable Telegram bots).
|
- Previously, if you didn’t configure an allowlist, your bot could be **open to anyone** (especially discoverable Telegram bots).
|
||||||
- New default: DM pairing (`dmPolicy="pairing"` / `discord.dm.policy="pairing"` / `slack.dm.policy="pairing"`).
|
- New default: DM pairing (`dmPolicy="pairing"` / `discord.dm.policy="pairing"` / `slack.dm.policy="pairing"`).
|
||||||
@@ -1178,6 +1415,7 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
|
|||||||
- CLI: remove `update`, `gateway-daemon`, `gateway {install|uninstall|start|stop|restart|daemon status|wake|send|agent}`, and `telegram` commands; move `login/logout` to `providers login/logout` (top-level aliases hidden); use `daemon` for service control, `send`/`agent`/`wake` for RPC, and `nodes canvas` for canvas ops.
|
- CLI: remove `update`, `gateway-daemon`, `gateway {install|uninstall|start|stop|restart|daemon status|wake|send|agent}`, and `telegram` commands; move `login/logout` to `providers login/logout` (top-level aliases hidden); use `daemon` for service control, `send`/`agent`/`wake` for RPC, and `nodes canvas` for canvas ops.
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
- **CLI/Gateway/Doctor:** daemon runtime selection + improved logs/status/health/errors; auth/password handling for local CLI; richer close/timeout details; auto-migrate legacy config/sessions/state; integrity checks + repair prompts; `--yes`/`--non-interactive`; `--deep` gateway scans; better restart/service hints.
|
- **CLI/Gateway/Doctor:** daemon runtime selection + improved logs/status/health/errors; auth/password handling for local CLI; richer close/timeout details; auto-migrate legacy config/sessions/state; integrity checks + repair prompts; `--yes`/`--non-interactive`; `--deep` gateway scans; better restart/service hints.
|
||||||
- **Agent loop + compaction:** compaction/pruning tuning, overflow handling, safer bootstrap context, and per-provider threading/confirmations; opt-in tool-result pruning + compact tracking.
|
- **Agent loop + compaction:** compaction/pruning tuning, overflow handling, safer bootstrap context, and per-provider threading/confirmations; opt-in tool-result pruning + compact tracking.
|
||||||
- **Sandbox + tools:** per-agent sandbox overrides, workspaceAccess controls, session tool visibility, tool policy overrides, process isolation, and tool schema/timeout/reaction unification.
|
- **Sandbox + tools:** per-agent sandbox overrides, workspaceAccess controls, session tool visibility, tool policy overrides, process isolation, and tool schema/timeout/reaction unification.
|
||||||
@@ -1189,13 +1427,15 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
|
|||||||
- **Docs:** new FAQ/ClawHub/config examples/showcase entries and clarified auth, sandbox, and systemd docs.
|
- **Docs:** new FAQ/ClawHub/config examples/showcase entries and clarified auth, sandbox, and systemd docs.
|
||||||
|
|
||||||
### Maintenance
|
### Maintenance
|
||||||
|
|
||||||
- Skills additions (Himalaya email, CodexBar, 1Password).
|
- Skills additions (Himalaya email, CodexBar, 1Password).
|
||||||
- Dependency refreshes (pi-* stack, Slack SDK, discord-api-types, file-type, zod, Biome, Vite).
|
- Dependency refreshes (pi-\* stack, Slack SDK, discord-api-types, file-type, zod, Biome, Vite).
|
||||||
- Refactors: centralized group allowlist/mention policy; lint/import cleanup; switch tsx → bun for TS execution.
|
- Refactors: centralized group allowlist/mention policy; lint/import cleanup; switch tsx → bun for TS execution.
|
||||||
|
|
||||||
## 2026.1.5
|
## 2026.1.5
|
||||||
|
|
||||||
### Highlights
|
### Highlights
|
||||||
|
|
||||||
- Models: add image-specific model config (`agent.imageModel` + fallbacks) and scan support.
|
- Models: add image-specific model config (`agent.imageModel` + fallbacks) and scan support.
|
||||||
- Agent tools: new `image` tool routed to the image model (when configured).
|
- Agent tools: new `image` tool routed to the image model (when configured).
|
||||||
- Config: default model shorthands (`opus`, `sonnet`, `gpt`, `gpt-mini`, `gemini`, `gemini-flash`).
|
- Config: default model shorthands (`opus`, `sonnet`, `gpt`, `gpt-mini`, `gemini`, `gemini-flash`).
|
||||||
@@ -1203,6 +1443,7 @@ Thanks @AlexMikhalev, @CoreyH, @John-Rood, @KrauseFx, @MaudeBot, @Nachx639, @Nic
|
|||||||
- Bun: optional local install/build workflow without maintaining a Bun lockfile (see `docs/bun.md`).
|
- Bun: optional local install/build workflow without maintaining a Bun lockfile (see `docs/bun.md`).
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
- Control UI: render Markdown in tool result cards.
|
- Control UI: render Markdown in tool result cards.
|
||||||
- Control UI: prevent overlapping action buttons in Discord guild rules on narrow layouts.
|
- Control UI: prevent overlapping action buttons in Discord guild rules on narrow layouts.
|
||||||
- Android: tapping the foreground service notification brings the app to the front. (#179) — thanks @Syhids
|
- Android: tapping the foreground service notification brings the app to the front. (#179) — thanks @Syhids
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
Welcome to the lobster tank! 🦞
|
Welcome to the lobster tank! 🦞
|
||||||
|
|
||||||
## Quick Links
|
## Quick Links
|
||||||
|
|
||||||
- **GitHub:** https://github.com/openclaw/openclaw
|
- **GitHub:** https://github.com/openclaw/openclaw
|
||||||
- **Discord:** https://discord.gg/qkhbAGHRBT
|
- **Discord:** https://discord.gg/qkhbAGHRBT
|
||||||
- **X/Twitter:** [@steipete](https://x.com/steipete) / [@openclaw](https://x.com/openclaw)
|
- **X/Twitter:** [@steipete](https://x.com/steipete) / [@openclaw](https://x.com/openclaw)
|
||||||
@@ -18,22 +19,43 @@ Welcome to the lobster tank! 🦞
|
|||||||
- **Jos** - Telegram, API, Nix mode
|
- **Jos** - Telegram, API, Nix mode
|
||||||
- GitHub: [@joshp123](https://github.com/joshp123) · X: [@jjpcodes](https://x.com/jjpcodes)
|
- GitHub: [@joshp123](https://github.com/joshp123) · X: [@jjpcodes](https://x.com/jjpcodes)
|
||||||
|
|
||||||
|
- **Christoph Nakazawa** - JS Infra
|
||||||
|
- GitHub: [@cpojer](https://github.com/cpojer) · X: [@cnakazawa](https://x.com/cnakazawa)
|
||||||
|
|
||||||
## How to Contribute
|
## How to Contribute
|
||||||
|
|
||||||
1. **Bugs & small fixes** → Open a PR!
|
1. **Bugs & small fixes** → Open a PR!
|
||||||
2. **New features / architecture** → Start a [GitHub Discussion](https://github.com/openclaw/openclaw/discussions) or ask in Discord first
|
2. **New features / architecture** → Start a [GitHub Discussion](https://github.com/openclaw/openclaw/discussions) or ask in Discord first
|
||||||
3. **Questions** → Discord #setup-help
|
3. **Questions** → Discord #setup-help
|
||||||
|
|
||||||
## Before You PR
|
## Before You PR
|
||||||
|
|
||||||
- Test locally with your OpenClaw instance
|
- Test locally with your OpenClaw instance
|
||||||
- Run linter: `npm run lint`
|
- Run tests: `pnpm build && pnpm check && pnpm test`
|
||||||
- Keep PRs focused (one thing per PR)
|
- Keep PRs focused (one thing per PR)
|
||||||
- Describe what & why
|
- Describe what & why
|
||||||
|
|
||||||
|
## Control UI Decorators
|
||||||
|
|
||||||
|
The Control UI uses Lit with **legacy** decorators (current Rollup parsing does not support
|
||||||
|
`accessor` fields required for standard decorators). When adding reactive fields, keep the
|
||||||
|
legacy style:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
@state() foo = "bar";
|
||||||
|
@property({ type: Number }) count = 0;
|
||||||
|
```
|
||||||
|
|
||||||
|
The root `tsconfig.json` is configured for legacy decorators (`experimentalDecorators: true`)
|
||||||
|
with `useDefineForClassFields: false`. Avoid flipping these unless you are also updating the UI
|
||||||
|
build tooling to support standard decorators.
|
||||||
|
|
||||||
## AI/Vibe-Coded PRs Welcome! 🤖
|
## AI/Vibe-Coded PRs Welcome! 🤖
|
||||||
|
|
||||||
Built with Codex, Claude, or other AI tools? **Awesome - just mark it!**
|
Built with Codex, Claude, or other AI tools? **Awesome - just mark it!**
|
||||||
|
|
||||||
Please include in your PR:
|
Please include in your PR:
|
||||||
|
|
||||||
- [ ] Mark as AI-assisted in the PR title or description
|
- [ ] Mark as AI-assisted in the PR title or description
|
||||||
- [ ] Note the degree of testing (untested / lightly tested / fully tested)
|
- [ ] Note the degree of testing (untested / lightly tested / fully tested)
|
||||||
- [ ] Include prompts or session logs if possible (super helpful!)
|
- [ ] Include prompts or session logs if possible (super helpful!)
|
||||||
@@ -44,6 +66,7 @@ AI PRs are first-class citizens here. We just want transparency so reviewers kno
|
|||||||
## Current Focus & Roadmap 🗺
|
## Current Focus & Roadmap 🗺
|
||||||
|
|
||||||
We are currently prioritizing:
|
We are currently prioritizing:
|
||||||
|
|
||||||
- **Stability**: Fixing edge cases in channel connections (WhatsApp/Telegram).
|
- **Stability**: Fixing edge cases in channel connections (WhatsApp/Telegram).
|
||||||
- **UX**: Improving the onboarding wizard and error messages.
|
- **UX**: Improving the onboarding wizard and error messages.
|
||||||
- **Skills**: Expanding the library of bundled skills and improving the Skill Creation developer experience.
|
- **Skills**: Expanding the library of bundled skills and improving the Skill Creation developer experience.
|
||||||
|
|||||||
11
Dockerfile
11
Dockerfile
@@ -31,9 +31,18 @@ RUN pnpm ui:build
|
|||||||
|
|
||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
|
|
||||||
|
# Allow non-root user to write temp files during runtime/tests.
|
||||||
|
RUN chown -R node:node /app
|
||||||
|
|
||||||
# Security hardening: Run as non-root user
|
# Security hardening: Run as non-root user
|
||||||
# The node:22-bookworm image includes a 'node' user (uid 1000)
|
# The node:22-bookworm image includes a 'node' user (uid 1000)
|
||||||
# This reduces the attack surface by preventing container escape via root privileges
|
# This reduces the attack surface by preventing container escape via root privileges
|
||||||
USER node
|
USER node
|
||||||
|
|
||||||
CMD ["node", "dist/index.js"]
|
# Start gateway server with default config.
|
||||||
|
# Binds to loopback (127.0.0.1) by default for security.
|
||||||
|
#
|
||||||
|
# For container platforms requiring external health checks:
|
||||||
|
# 1. Set OPENCLAW_GATEWAY_TOKEN or OPENCLAW_GATEWAY_PASSWORD env var
|
||||||
|
# 2. Override CMD: ["node","dist/index.js","gateway","--allow-unconfigured","--bind","lan"]
|
||||||
|
CMD ["node", "dist/index.js", "gateway", "--allow-unconfigured"]
|
||||||
|
|||||||
114
README.md
114
README.md
@@ -18,7 +18,7 @@
|
|||||||
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue.svg?style=for-the-badge" alt="MIT License"></a>
|
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue.svg?style=for-the-badge" alt="MIT License"></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
**OpenClaw** is a *personal AI assistant* you run on your own devices.
|
**OpenClaw** is a _personal AI assistant_ you run on your own devices.
|
||||||
It answers you on the channels you already use (WhatsApp, Telegram, Slack, Discord, Google Chat, Signal, iMessage, Microsoft Teams, WebChat), plus extension channels like BlueBubbles, Matrix, Zalo, and Zalo Personal. It can speak and listen on macOS/iOS/Android, and can render a live Canvas you control. The Gateway is just the control plane — the product is the assistant.
|
It answers you on the channels you already use (WhatsApp, Telegram, Slack, Discord, Google Chat, Signal, iMessage, Microsoft Teams, WebChat), plus extension channels like BlueBubbles, Matrix, Zalo, and Zalo Personal. It can speak and listen on macOS/iOS/Android, and can render a live Canvas you control. The Gateway is just the control plane — the product is the assistant.
|
||||||
|
|
||||||
If you want a personal, single-user assistant that feels local, fast, and always-on, this is it.
|
If you want a personal, single-user assistant that feels local, fast, and always-on, this is it.
|
||||||
@@ -30,6 +30,7 @@ Works with npm, pnpm, or bun.
|
|||||||
New install? Start here: [Getting started](https://docs.openclaw.ai/start/getting-started)
|
New install? Start here: [Getting started](https://docs.openclaw.ai/start/getting-started)
|
||||||
|
|
||||||
**Subscriptions (OAuth):**
|
**Subscriptions (OAuth):**
|
||||||
|
|
||||||
- **[Anthropic](https://www.anthropic.com/)** (Claude Pro/Max)
|
- **[Anthropic](https://www.anthropic.com/)** (Claude Pro/Max)
|
||||||
- **[OpenAI](https://openai.com/)** (ChatGPT/Codex)
|
- **[OpenAI](https://openai.com/)** (ChatGPT/Codex)
|
||||||
|
|
||||||
@@ -109,6 +110,7 @@ OpenClaw connects to real messaging surfaces. Treat inbound DMs as **untrusted i
|
|||||||
Full security guide: [Security](https://docs.openclaw.ai/gateway/security)
|
Full security guide: [Security](https://docs.openclaw.ai/gateway/security)
|
||||||
|
|
||||||
Default behavior on Telegram/WhatsApp/Signal/iMessage/Microsoft Teams/Discord/Google Chat/Slack:
|
Default behavior on Telegram/WhatsApp/Signal/iMessage/Microsoft Teams/Discord/Google Chat/Slack:
|
||||||
|
|
||||||
- **DM pairing** (`dmPolicy="pairing"` / `channels.discord.dm.policy="pairing"` / `channels.slack.dm.policy="pairing"`): unknown senders receive a short pairing code and the bot does not process their message.
|
- **DM pairing** (`dmPolicy="pairing"` / `channels.discord.dm.policy="pairing"` / `channels.slack.dm.policy="pairing"`): unknown senders receive a short pairing code and the bot does not process their message.
|
||||||
- Approve with: `openclaw pairing approve <channel> <code>` (then the sender is added to a local allowlist store).
|
- Approve with: `openclaw pairing approve <channel> <code>` (then the sender is added to a local allowlist store).
|
||||||
- Public inbound DMs require an explicit opt-in: set `dmPolicy="open"` and include `"*"` in the channel allowlist (`allowFrom` / `channels.discord.dm.allowFrom` / `channels.slack.dm.allowFrom`).
|
- Public inbound DMs require an explicit opt-in: set `dmPolicy="open"` and include `"*"` in the channel allowlist (`allowFrom` / `channels.discord.dm.allowFrom` / `channels.slack.dm.allowFrom`).
|
||||||
@@ -133,6 +135,7 @@ Run `openclaw doctor` to surface risky/misconfigured DM policies.
|
|||||||
## Everything we built so far
|
## Everything we built so far
|
||||||
|
|
||||||
### Core platform
|
### Core platform
|
||||||
|
|
||||||
- [Gateway WS control plane](https://docs.openclaw.ai/gateway) with sessions, presence, config, cron, webhooks, [Control UI](https://docs.openclaw.ai/web), and [Canvas host](https://docs.openclaw.ai/platforms/mac/canvas#canvas-a2ui).
|
- [Gateway WS control plane](https://docs.openclaw.ai/gateway) with sessions, presence, config, cron, webhooks, [Control UI](https://docs.openclaw.ai/web), and [Canvas host](https://docs.openclaw.ai/platforms/mac/canvas#canvas-a2ui).
|
||||||
- [CLI surface](https://docs.openclaw.ai/tools/agent-send): gateway, agent, send, [wizard](https://docs.openclaw.ai/start/wizard), and [doctor](https://docs.openclaw.ai/gateway/doctor).
|
- [CLI surface](https://docs.openclaw.ai/tools/agent-send): gateway, agent, send, [wizard](https://docs.openclaw.ai/start/wizard), and [doctor](https://docs.openclaw.ai/gateway/doctor).
|
||||||
- [Pi agent runtime](https://docs.openclaw.ai/concepts/agent) in RPC mode with tool streaming and block streaming.
|
- [Pi agent runtime](https://docs.openclaw.ai/concepts/agent) in RPC mode with tool streaming and block streaming.
|
||||||
@@ -140,16 +143,19 @@ Run `openclaw doctor` to surface risky/misconfigured DM policies.
|
|||||||
- [Media pipeline](https://docs.openclaw.ai/nodes/images): images/audio/video, transcription hooks, size caps, temp file lifecycle. Audio details: [Audio](https://docs.openclaw.ai/nodes/audio).
|
- [Media pipeline](https://docs.openclaw.ai/nodes/images): images/audio/video, transcription hooks, size caps, temp file lifecycle. Audio details: [Audio](https://docs.openclaw.ai/nodes/audio).
|
||||||
|
|
||||||
### Channels
|
### Channels
|
||||||
|
|
||||||
- [Channels](https://docs.openclaw.ai/channels): [WhatsApp](https://docs.openclaw.ai/channels/whatsapp) (Baileys), [Telegram](https://docs.openclaw.ai/channels/telegram) (grammY), [Slack](https://docs.openclaw.ai/channels/slack) (Bolt), [Discord](https://docs.openclaw.ai/channels/discord) (discord.js), [Google Chat](https://docs.openclaw.ai/channels/googlechat) (Chat API), [Signal](https://docs.openclaw.ai/channels/signal) (signal-cli), [iMessage](https://docs.openclaw.ai/channels/imessage) (imsg), [BlueBubbles](https://docs.openclaw.ai/channels/bluebubbles) (extension), [Microsoft Teams](https://docs.openclaw.ai/channels/msteams) (extension), [Matrix](https://docs.openclaw.ai/channels/matrix) (extension), [Zalo](https://docs.openclaw.ai/channels/zalo) (extension), [Zalo Personal](https://docs.openclaw.ai/channels/zalouser) (extension), [WebChat](https://docs.openclaw.ai/web/webchat).
|
- [Channels](https://docs.openclaw.ai/channels): [WhatsApp](https://docs.openclaw.ai/channels/whatsapp) (Baileys), [Telegram](https://docs.openclaw.ai/channels/telegram) (grammY), [Slack](https://docs.openclaw.ai/channels/slack) (Bolt), [Discord](https://docs.openclaw.ai/channels/discord) (discord.js), [Google Chat](https://docs.openclaw.ai/channels/googlechat) (Chat API), [Signal](https://docs.openclaw.ai/channels/signal) (signal-cli), [iMessage](https://docs.openclaw.ai/channels/imessage) (imsg), [BlueBubbles](https://docs.openclaw.ai/channels/bluebubbles) (extension), [Microsoft Teams](https://docs.openclaw.ai/channels/msteams) (extension), [Matrix](https://docs.openclaw.ai/channels/matrix) (extension), [Zalo](https://docs.openclaw.ai/channels/zalo) (extension), [Zalo Personal](https://docs.openclaw.ai/channels/zalouser) (extension), [WebChat](https://docs.openclaw.ai/web/webchat).
|
||||||
- [Group routing](https://docs.openclaw.ai/concepts/group-messages): mention gating, reply tags, per-channel chunking and routing. Channel rules: [Channels](https://docs.openclaw.ai/channels).
|
- [Group routing](https://docs.openclaw.ai/concepts/group-messages): mention gating, reply tags, per-channel chunking and routing. Channel rules: [Channels](https://docs.openclaw.ai/channels).
|
||||||
|
|
||||||
### Apps + nodes
|
### Apps + nodes
|
||||||
|
|
||||||
- [macOS app](https://docs.openclaw.ai/platforms/macos): menu bar control plane, [Voice Wake](https://docs.openclaw.ai/nodes/voicewake)/PTT, [Talk Mode](https://docs.openclaw.ai/nodes/talk) overlay, [WebChat](https://docs.openclaw.ai/web/webchat), debug tools, [remote gateway](https://docs.openclaw.ai/gateway/remote) control.
|
- [macOS app](https://docs.openclaw.ai/platforms/macos): menu bar control plane, [Voice Wake](https://docs.openclaw.ai/nodes/voicewake)/PTT, [Talk Mode](https://docs.openclaw.ai/nodes/talk) overlay, [WebChat](https://docs.openclaw.ai/web/webchat), debug tools, [remote gateway](https://docs.openclaw.ai/gateway/remote) control.
|
||||||
- [iOS node](https://docs.openclaw.ai/platforms/ios): [Canvas](https://docs.openclaw.ai/platforms/mac/canvas), [Voice Wake](https://docs.openclaw.ai/nodes/voicewake), [Talk Mode](https://docs.openclaw.ai/nodes/talk), camera, screen recording, Bonjour pairing.
|
- [iOS node](https://docs.openclaw.ai/platforms/ios): [Canvas](https://docs.openclaw.ai/platforms/mac/canvas), [Voice Wake](https://docs.openclaw.ai/nodes/voicewake), [Talk Mode](https://docs.openclaw.ai/nodes/talk), camera, screen recording, Bonjour pairing.
|
||||||
- [Android node](https://docs.openclaw.ai/platforms/android): [Canvas](https://docs.openclaw.ai/platforms/mac/canvas), [Talk Mode](https://docs.openclaw.ai/nodes/talk), camera, screen recording, optional SMS.
|
- [Android node](https://docs.openclaw.ai/platforms/android): [Canvas](https://docs.openclaw.ai/platforms/mac/canvas), [Talk Mode](https://docs.openclaw.ai/nodes/talk), camera, screen recording, optional SMS.
|
||||||
- [macOS node mode](https://docs.openclaw.ai/nodes): system.run/notify + canvas/camera exposure.
|
- [macOS node mode](https://docs.openclaw.ai/nodes): system.run/notify + canvas/camera exposure.
|
||||||
|
|
||||||
### Tools + automation
|
### Tools + automation
|
||||||
|
|
||||||
- [Browser control](https://docs.openclaw.ai/tools/browser): dedicated openclaw Chrome/Chromium, snapshots, actions, uploads, profiles.
|
- [Browser control](https://docs.openclaw.ai/tools/browser): dedicated openclaw Chrome/Chromium, snapshots, actions, uploads, profiles.
|
||||||
- [Canvas](https://docs.openclaw.ai/platforms/mac/canvas): [A2UI](https://docs.openclaw.ai/platforms/mac/canvas#canvas-a2ui) push/reset, eval, snapshot.
|
- [Canvas](https://docs.openclaw.ai/platforms/mac/canvas): [A2UI](https://docs.openclaw.ai/platforms/mac/canvas#canvas-a2ui) push/reset, eval, snapshot.
|
||||||
- [Nodes](https://docs.openclaw.ai/nodes): camera snap/clip, screen record, [location.get](https://docs.openclaw.ai/nodes/location-command), notifications.
|
- [Nodes](https://docs.openclaw.ai/nodes): camera snap/clip, screen record, [location.get](https://docs.openclaw.ai/nodes/location-command), notifications.
|
||||||
@@ -157,12 +163,14 @@ Run `openclaw doctor` to surface risky/misconfigured DM policies.
|
|||||||
- [Skills platform](https://docs.openclaw.ai/tools/skills): bundled, managed, and workspace skills with install gating + UI.
|
- [Skills platform](https://docs.openclaw.ai/tools/skills): bundled, managed, and workspace skills with install gating + UI.
|
||||||
|
|
||||||
### Runtime + safety
|
### Runtime + safety
|
||||||
|
|
||||||
- [Channel routing](https://docs.openclaw.ai/concepts/channel-routing), [retry policy](https://docs.openclaw.ai/concepts/retry), and [streaming/chunking](https://docs.openclaw.ai/concepts/streaming).
|
- [Channel routing](https://docs.openclaw.ai/concepts/channel-routing), [retry policy](https://docs.openclaw.ai/concepts/retry), and [streaming/chunking](https://docs.openclaw.ai/concepts/streaming).
|
||||||
- [Presence](https://docs.openclaw.ai/concepts/presence), [typing indicators](https://docs.openclaw.ai/concepts/typing-indicators), and [usage tracking](https://docs.openclaw.ai/concepts/usage-tracking).
|
- [Presence](https://docs.openclaw.ai/concepts/presence), [typing indicators](https://docs.openclaw.ai/concepts/typing-indicators), and [usage tracking](https://docs.openclaw.ai/concepts/usage-tracking).
|
||||||
- [Models](https://docs.openclaw.ai/concepts/models), [model failover](https://docs.openclaw.ai/concepts/model-failover), and [session pruning](https://docs.openclaw.ai/concepts/session-pruning).
|
- [Models](https://docs.openclaw.ai/concepts/models), [model failover](https://docs.openclaw.ai/concepts/model-failover), and [session pruning](https://docs.openclaw.ai/concepts/session-pruning).
|
||||||
- [Security](https://docs.openclaw.ai/gateway/security) and [troubleshooting](https://docs.openclaw.ai/channels/troubleshooting).
|
- [Security](https://docs.openclaw.ai/gateway/security) and [troubleshooting](https://docs.openclaw.ai/channels/troubleshooting).
|
||||||
|
|
||||||
### Ops + packaging
|
### Ops + packaging
|
||||||
|
|
||||||
- [Control UI](https://docs.openclaw.ai/web) + [WebChat](https://docs.openclaw.ai/web/webchat) served directly from the Gateway.
|
- [Control UI](https://docs.openclaw.ai/web) + [WebChat](https://docs.openclaw.ai/web/webchat) served directly from the Gateway.
|
||||||
- [Tailscale Serve/Funnel](https://docs.openclaw.ai/gateway/tailscale) or [SSH tunnels](https://docs.openclaw.ai/gateway/remote) with token/password auth.
|
- [Tailscale Serve/Funnel](https://docs.openclaw.ai/gateway/tailscale) or [SSH tunnels](https://docs.openclaw.ai/gateway/remote) with token/password auth.
|
||||||
- [Nix mode](https://docs.openclaw.ai/install/nix) for declarative config; [Docker](https://docs.openclaw.ai/install/docker)-based installs.
|
- [Nix mode](https://docs.openclaw.ai/install/nix) for declarative config; [Docker](https://docs.openclaw.ai/install/docker)-based installs.
|
||||||
@@ -205,6 +213,7 @@ OpenClaw can auto-configure Tailscale **Serve** (tailnet-only) or **Funnel** (pu
|
|||||||
- `funnel`: public HTTPS via `tailscale funnel` (requires shared password auth).
|
- `funnel`: public HTTPS via `tailscale funnel` (requires shared password auth).
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
- `gateway.bind` must stay `loopback` when Serve/Funnel is enabled (OpenClaw enforces this).
|
- `gateway.bind` must stay `loopback` when Serve/Funnel is enabled (OpenClaw enforces this).
|
||||||
- Serve can be forced to require a password by setting `gateway.auth.mode: "password"` or `gateway.auth.allowTailscale: false`.
|
- Serve can be forced to require a password by setting `gateway.auth.mode: "password"` or `gateway.auth.allowTailscale: false`.
|
||||||
- Funnel refuses to start unless `gateway.auth.mode: "password"` is set.
|
- Funnel refuses to start unless `gateway.auth.mode: "password"` is set.
|
||||||
@@ -218,7 +227,7 @@ It’s perfectly fine to run the Gateway on a small Linux instance. Clients (mac
|
|||||||
|
|
||||||
- **Gateway host** runs the exec tool and channel connections by default.
|
- **Gateway host** runs the exec tool and channel connections by default.
|
||||||
- **Device nodes** run device‑local actions (`system.run`, camera, screen recording, notifications) via `node.invoke`.
|
- **Device nodes** run device‑local actions (`system.run`, camera, screen recording, notifications) via `node.invoke`.
|
||||||
In short: exec runs where the Gateway lives; device actions run where the device lives.
|
In short: exec runs where the Gateway lives; device actions run where the device lives.
|
||||||
|
|
||||||
Details: [Remote access](https://docs.openclaw.ai/gateway/remote) · [Nodes](https://docs.openclaw.ai/nodes) · [Security](https://docs.openclaw.ai/gateway/security)
|
Details: [Remote access](https://docs.openclaw.ai/gateway/remote) · [Nodes](https://docs.openclaw.ai/nodes) · [Security](https://docs.openclaw.ai/gateway/security)
|
||||||
|
|
||||||
@@ -237,7 +246,7 @@ Elevated bash (host permissions) is separate from macOS TCC:
|
|||||||
|
|
||||||
Details: [Nodes](https://docs.openclaw.ai/nodes) · [macOS app](https://docs.openclaw.ai/platforms/macos) · [Gateway protocol](https://docs.openclaw.ai/concepts/architecture)
|
Details: [Nodes](https://docs.openclaw.ai/nodes) · [macOS app](https://docs.openclaw.ai/platforms/macos) · [Gateway protocol](https://docs.openclaw.ai/concepts/architecture)
|
||||||
|
|
||||||
## Agent to Agent (sessions_* tools)
|
## Agent to Agent (sessions\_\* tools)
|
||||||
|
|
||||||
- Use these to coordinate work across sessions without jumping between chat surfaces.
|
- Use these to coordinate work across sessions without jumping between chat surfaces.
|
||||||
- `sessions_list` — discover active sessions (agents) and their metadata.
|
- `sessions_list` — discover active sessions (agents) and their metadata.
|
||||||
@@ -307,8 +316,8 @@ Minimal `~/.openclaw/openclaw.json` (model + defaults):
|
|||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
agent: {
|
agent: {
|
||||||
model: "anthropic/claude-opus-4-5"
|
model: "anthropic/claude-opus-4-5",
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -331,15 +340,15 @@ Details: [Security guide](https://docs.openclaw.ai/gateway/security) · [Docker
|
|||||||
### [Telegram](https://docs.openclaw.ai/channels/telegram)
|
### [Telegram](https://docs.openclaw.ai/channels/telegram)
|
||||||
|
|
||||||
- Set `TELEGRAM_BOT_TOKEN` or `channels.telegram.botToken` (env wins).
|
- Set `TELEGRAM_BOT_TOKEN` or `channels.telegram.botToken` (env wins).
|
||||||
- Optional: set `channels.telegram.groups` (with `channels.telegram.groups."*".requireMention`); when set, it is a group allowlist (include `"*"` to allow all). Also `channels.telegram.allowFrom` or `channels.telegram.webhookUrl` as needed.
|
- Optional: set `channels.telegram.groups` (with `channels.telegram.groups."*".requireMention`); when set, it is a group allowlist (include `"*"` to allow all). Also `channels.telegram.allowFrom` or `channels.telegram.webhookUrl` + `channels.telegram.webhookSecret` as needed.
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
channels: {
|
channels: {
|
||||||
telegram: {
|
telegram: {
|
||||||
botToken: "123456:ABCDEF"
|
botToken: "123456:ABCDEF",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -356,9 +365,9 @@ Details: [Security guide](https://docs.openclaw.ai/gateway/security) · [Docker
|
|||||||
{
|
{
|
||||||
channels: {
|
channels: {
|
||||||
discord: {
|
discord: {
|
||||||
token: "1234abcd"
|
token: "1234abcd",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -386,14 +395,15 @@ Browser control (optional):
|
|||||||
{
|
{
|
||||||
browser: {
|
browser: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
color: "#FF4500"
|
color: "#FF4500",
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Docs
|
## Docs
|
||||||
|
|
||||||
Use these when you’re past the onboarding flow and want the deeper reference.
|
Use these when you’re past the onboarding flow and want the deeper reference.
|
||||||
|
|
||||||
- [Start with the docs index for navigation and “what’s where.”](https://docs.openclaw.ai)
|
- [Start with the docs index for navigation and “what’s where.”](https://docs.openclaw.ai)
|
||||||
- [Read the architecture overview for the gateway + protocol model.](https://docs.openclaw.ai/concepts/architecture)
|
- [Read the architecture overview for the gateway + protocol model.](https://docs.openclaw.ai/concepts/architecture)
|
||||||
- [Use the full configuration reference when you need every key and example.](https://docs.openclaw.ai/gateway/configuration)
|
- [Use the full configuration reference when you need every key and example.](https://docs.openclaw.ai/gateway/configuration)
|
||||||
@@ -480,40 +490,44 @@ Special thanks to Adam Doppelt for lobster.bot.
|
|||||||
Thanks to all clawtributors:
|
Thanks to all clawtributors:
|
||||||
|
|
||||||
<p align="left">
|
<p align="left">
|
||||||
<a href="https://github.com/steipete"><img src="https://avatars.githubusercontent.com/u/58493?v=4&s=48" width="48" height="48" alt="steipete" title="steipete"/></a> <a href="https://github.com/plum-dawg"><img src="https://avatars.githubusercontent.com/u/5909950?v=4&s=48" width="48" height="48" alt="plum-dawg" title="plum-dawg"/></a> <a href="https://github.com/bohdanpodvirnyi"><img src="https://avatars.githubusercontent.com/u/31819391?v=4&s=48" width="48" height="48" alt="bohdanpodvirnyi" title="bohdanpodvirnyi"/></a> <a href="https://github.com/iHildy"><img src="https://avatars.githubusercontent.com/u/25069719?v=4&s=48" width="48" height="48" alt="iHildy" title="iHildy"/></a> <a href="https://github.com/jaydenfyi"><img src="https://avatars.githubusercontent.com/u/213395523?v=4&s=48" width="48" height="48" alt="jaydenfyi" title="jaydenfyi"/></a> <a href="https://github.com/joaohlisboa"><img src="https://avatars.githubusercontent.com/u/8200873?v=4&s=48" width="48" height="48" alt="joaohlisboa" title="joaohlisboa"/></a> <a href="https://github.com/mneves75"><img src="https://avatars.githubusercontent.com/u/2423436?v=4&s=48" width="48" height="48" alt="mneves75" title="mneves75"/></a> <a href="https://github.com/MatthieuBizien"><img src="https://avatars.githubusercontent.com/u/173090?v=4&s=48" width="48" height="48" alt="MatthieuBizien" title="MatthieuBizien"/></a> <a href="https://github.com/MaudeBot"><img src="https://avatars.githubusercontent.com/u/255777700?v=4&s=48" width="48" height="48" alt="MaudeBot" title="MaudeBot"/></a> <a href="https://github.com/Glucksberg"><img src="https://avatars.githubusercontent.com/u/80581902?v=4&s=48" width="48" height="48" alt="Glucksberg" title="Glucksberg"/></a>
|
<a href="https://github.com/steipete"><img src="https://avatars.githubusercontent.com/u/58493?v=4&s=48" width="48" height="48" alt="steipete" title="steipete"/></a> <a href="https://github.com/cpojer"><img src="https://avatars.githubusercontent.com/u/13352?v=4&s=48" width="48" height="48" alt="cpojer" title="cpojer"/></a> <a href="https://github.com/plum-dawg"><img src="https://avatars.githubusercontent.com/u/5909950?v=4&s=48" width="48" height="48" alt="plum-dawg" title="plum-dawg"/></a> <a href="https://github.com/bohdanpodvirnyi"><img src="https://avatars.githubusercontent.com/u/31819391?v=4&s=48" width="48" height="48" alt="bohdanpodvirnyi" title="bohdanpodvirnyi"/></a> <a href="https://github.com/iHildy"><img src="https://avatars.githubusercontent.com/u/25069719?v=4&s=48" width="48" height="48" alt="iHildy" title="iHildy"/></a> <a href="https://github.com/jaydenfyi"><img src="https://avatars.githubusercontent.com/u/213395523?v=4&s=48" width="48" height="48" alt="jaydenfyi" title="jaydenfyi"/></a> <a href="https://github.com/joshp123"><img src="https://avatars.githubusercontent.com/u/1497361?v=4&s=48" width="48" height="48" alt="joshp123" title="joshp123"/></a> <a href="https://github.com/joaohlisboa"><img src="https://avatars.githubusercontent.com/u/8200873?v=4&s=48" width="48" height="48" alt="joaohlisboa" title="joaohlisboa"/></a> <a href="https://github.com/mneves75"><img src="https://avatars.githubusercontent.com/u/2423436?v=4&s=48" width="48" height="48" alt="mneves75" title="mneves75"/></a> <a href="https://github.com/MatthieuBizien"><img src="https://avatars.githubusercontent.com/u/173090?v=4&s=48" width="48" height="48" alt="MatthieuBizien" title="MatthieuBizien"/></a>
|
||||||
<a href="https://github.com/rahthakor"><img src="https://avatars.githubusercontent.com/u/8470553?v=4&s=48" width="48" height="48" alt="rahthakor" title="rahthakor"/></a> <a href="https://github.com/vrknetha"><img src="https://avatars.githubusercontent.com/u/20596261?v=4&s=48" width="48" height="48" alt="vrknetha" title="vrknetha"/></a> <a href="https://github.com/radek-paclt"><img src="https://avatars.githubusercontent.com/u/50451445?v=4&s=48" width="48" height="48" alt="radek-paclt" title="radek-paclt"/></a> <a href="https://github.com/vignesh07"><img src="https://avatars.githubusercontent.com/u/1436853?v=4&s=48" width="48" height="48" alt="vignesh07" title="vignesh07"/></a> <a href="https://github.com/joshp123"><img src="https://avatars.githubusercontent.com/u/1497361?v=4&s=48" width="48" height="48" alt="joshp123" title="joshp123"/></a> <a href="https://github.com/tobiasbischoff"><img src="https://avatars.githubusercontent.com/u/711564?v=4&s=48" width="48" height="48" alt="Tobias Bischoff" title="Tobias Bischoff"/></a> <a href="https://github.com/czekaj"><img src="https://avatars.githubusercontent.com/u/1464539?v=4&s=48" width="48" height="48" alt="czekaj" title="czekaj"/></a> <a href="https://github.com/mukhtharcm"><img src="https://avatars.githubusercontent.com/u/56378562?v=4&s=48" width="48" height="48" alt="mukhtharcm" title="mukhtharcm"/></a> <a href="https://github.com/sebslight"><img src="https://avatars.githubusercontent.com/u/19554889?v=4&s=48" width="48" height="48" alt="sebslight" title="sebslight"/></a> <a href="https://github.com/maxsumrall"><img src="https://avatars.githubusercontent.com/u/628843?v=4&s=48" width="48" height="48" alt="maxsumrall" title="maxsumrall"/></a>
|
<a href="https://github.com/MaudeBot"><img src="https://avatars.githubusercontent.com/u/255777700?v=4&s=48" width="48" height="48" alt="MaudeBot" title="MaudeBot"/></a> <a href="https://github.com/Glucksberg"><img src="https://avatars.githubusercontent.com/u/80581902?v=4&s=48" width="48" height="48" alt="Glucksberg" title="Glucksberg"/></a> <a href="https://github.com/rahthakor"><img src="https://avatars.githubusercontent.com/u/8470553?v=4&s=48" width="48" height="48" alt="rahthakor" title="rahthakor"/></a> <a href="https://github.com/vrknetha"><img src="https://avatars.githubusercontent.com/u/20596261?v=4&s=48" width="48" height="48" alt="vrknetha" title="vrknetha"/></a> <a href="https://github.com/radek-paclt"><img src="https://avatars.githubusercontent.com/u/50451445?v=4&s=48" width="48" height="48" alt="radek-paclt" title="radek-paclt"/></a> <a href="https://github.com/vignesh07"><img src="https://avatars.githubusercontent.com/u/1436853?v=4&s=48" width="48" height="48" alt="vignesh07" title="vignesh07"/></a> <a href="https://github.com/tobiasbischoff"><img src="https://avatars.githubusercontent.com/u/711564?v=4&s=48" width="48" height="48" alt="Tobias Bischoff" title="Tobias Bischoff"/></a> <a href="https://github.com/sebslight"><img src="https://avatars.githubusercontent.com/u/19554889?v=4&s=48" width="48" height="48" alt="sebslight" title="sebslight"/></a> <a href="https://github.com/czekaj"><img src="https://avatars.githubusercontent.com/u/1464539?v=4&s=48" width="48" height="48" alt="czekaj" title="czekaj"/></a> <a href="https://github.com/mukhtharcm"><img src="https://avatars.githubusercontent.com/u/56378562?v=4&s=48" width="48" height="48" alt="mukhtharcm" title="mukhtharcm"/></a>
|
||||||
<a href="https://github.com/xadenryan"><img src="https://avatars.githubusercontent.com/u/165437834?v=4&s=48" width="48" height="48" alt="xadenryan" title="xadenryan"/></a> <a href="https://github.com/rodrigouroz"><img src="https://avatars.githubusercontent.com/u/384037?v=4&s=48" width="48" height="48" alt="rodrigouroz" title="rodrigouroz"/></a> <a href="https://github.com/juanpablodlc"><img src="https://avatars.githubusercontent.com/u/92012363?v=4&s=48" width="48" height="48" alt="juanpablodlc" title="juanpablodlc"/></a> <a href="https://github.com/tyler6204"><img src="https://avatars.githubusercontent.com/u/64381258?v=4&s=48" width="48" height="48" alt="tyler6204" title="tyler6204"/></a> <a href="https://github.com/hsrvc"><img src="https://avatars.githubusercontent.com/u/129702169?v=4&s=48" width="48" height="48" alt="hsrvc" title="hsrvc"/></a> <a href="https://github.com/magimetal"><img src="https://avatars.githubusercontent.com/u/36491250?v=4&s=48" width="48" height="48" alt="magimetal" title="magimetal"/></a> <a href="https://github.com/zerone0x"><img src="https://avatars.githubusercontent.com/u/39543393?v=4&s=48" width="48" height="48" alt="zerone0x" title="zerone0x"/></a> <a href="https://github.com/meaningfool"><img src="https://avatars.githubusercontent.com/u/2862331?v=4&s=48" width="48" height="48" alt="meaningfool" title="meaningfool"/></a> <a href="https://github.com/patelhiren"><img src="https://avatars.githubusercontent.com/u/172098?v=4&s=48" width="48" height="48" alt="patelhiren" title="patelhiren"/></a> <a href="https://github.com/NicholasSpisak"><img src="https://avatars.githubusercontent.com/u/129075147?v=4&s=48" width="48" height="48" alt="NicholasSpisak" title="NicholasSpisak"/></a>
|
<a href="https://github.com/maxsumrall"><img src="https://avatars.githubusercontent.com/u/628843?v=4&s=48" width="48" height="48" alt="maxsumrall" title="maxsumrall"/></a> <a href="https://github.com/xadenryan"><img src="https://avatars.githubusercontent.com/u/165437834?v=4&s=48" width="48" height="48" alt="xadenryan" title="xadenryan"/></a> <a href="https://github.com/VACInc"><img src="https://avatars.githubusercontent.com/u/3279061?v=4&s=48" width="48" height="48" alt="VACInc" title="VACInc"/></a> <a href="https://github.com/mbelinky"><img src="https://avatars.githubusercontent.com/u/132747814?v=4&s=48" width="48" height="48" alt="Mariano Belinky" title="Mariano Belinky"/></a> <a href="https://github.com/rodrigouroz"><img src="https://avatars.githubusercontent.com/u/384037?v=4&s=48" width="48" height="48" alt="rodrigouroz" title="rodrigouroz"/></a> <a href="https://github.com/tyler6204"><img src="https://avatars.githubusercontent.com/u/64381258?v=4&s=48" width="48" height="48" alt="tyler6204" title="tyler6204"/></a> <a href="https://github.com/juanpablodlc"><img src="https://avatars.githubusercontent.com/u/92012363?v=4&s=48" width="48" height="48" alt="juanpablodlc" title="juanpablodlc"/></a> <a href="https://github.com/conroywhitney"><img src="https://avatars.githubusercontent.com/u/249891?v=4&s=48" width="48" height="48" alt="conroywhitney" title="conroywhitney"/></a> <a href="https://github.com/hsrvc"><img src="https://avatars.githubusercontent.com/u/129702169?v=4&s=48" width="48" height="48" alt="hsrvc" title="hsrvc"/></a> <a href="https://github.com/magimetal"><img src="https://avatars.githubusercontent.com/u/36491250?v=4&s=48" width="48" height="48" alt="magimetal" title="magimetal"/></a>
|
||||||
<a href="https://github.com/jonisjongithub"><img src="https://avatars.githubusercontent.com/u/86072337?v=4&s=48" width="48" height="48" alt="jonisjongithub" title="jonisjongithub"/></a> <a href="https://github.com/AbhisekBasu1"><img src="https://avatars.githubusercontent.com/u/40645221?v=4&s=48" width="48" height="48" alt="abhisekbasu1" title="abhisekbasu1"/></a> <a href="https://github.com/jamesgroat"><img src="https://avatars.githubusercontent.com/u/2634024?v=4&s=48" width="48" height="48" alt="jamesgroat" title="jamesgroat"/></a> <a href="https://github.com/claude"><img src="https://avatars.githubusercontent.com/u/81847?v=4&s=48" width="48" height="48" alt="claude" title="claude"/></a> <a href="https://github.com/JustYannicc"><img src="https://avatars.githubusercontent.com/u/52761674?v=4&s=48" width="48" height="48" alt="JustYannicc" title="JustYannicc"/></a> <a href="https://github.com/mbelinky"><img src="https://avatars.githubusercontent.com/u/132747814?v=4&s=48" width="48" height="48" alt="Mariano Belinky" title="Mariano Belinky"/></a> <a href="https://github.com/Hyaxia"><img src="https://avatars.githubusercontent.com/u/36747317?v=4&s=48" width="48" height="48" alt="Hyaxia" title="Hyaxia"/></a> <a href="https://github.com/dantelex"><img src="https://avatars.githubusercontent.com/u/631543?v=4&s=48" width="48" height="48" alt="dantelex" title="dantelex"/></a> <a href="https://github.com/SocialNerd42069"><img src="https://avatars.githubusercontent.com/u/118244303?v=4&s=48" width="48" height="48" alt="SocialNerd42069" title="SocialNerd42069"/></a> <a href="https://github.com/daveonkels"><img src="https://avatars.githubusercontent.com/u/533642?v=4&s=48" width="48" height="48" alt="daveonkels" title="daveonkels"/></a>
|
<a href="https://github.com/zerone0x"><img src="https://avatars.githubusercontent.com/u/39543393?v=4&s=48" width="48" height="48" alt="zerone0x" title="zerone0x"/></a> <a href="https://github.com/meaningfool"><img src="https://avatars.githubusercontent.com/u/2862331?v=4&s=48" width="48" height="48" alt="meaningfool" title="meaningfool"/></a> <a href="https://github.com/patelhiren"><img src="https://avatars.githubusercontent.com/u/172098?v=4&s=48" width="48" height="48" alt="patelhiren" title="patelhiren"/></a> <a href="https://github.com/NicholasSpisak"><img src="https://avatars.githubusercontent.com/u/129075147?v=4&s=48" width="48" height="48" alt="NicholasSpisak" title="NicholasSpisak"/></a> <a href="https://github.com/jonisjongithub"><img src="https://avatars.githubusercontent.com/u/86072337?v=4&s=48" width="48" height="48" alt="jonisjongithub" title="jonisjongithub"/></a> <a href="https://github.com/AbhisekBasu1"><img src="https://avatars.githubusercontent.com/u/40645221?v=4&s=48" width="48" height="48" alt="abhisekbasu1" title="abhisekbasu1"/></a> <a href="https://github.com/jamesgroat"><img src="https://avatars.githubusercontent.com/u/2634024?v=4&s=48" width="48" height="48" alt="jamesgroat" title="jamesgroat"/></a> <a href="https://github.com/claude"><img src="https://avatars.githubusercontent.com/u/81847?v=4&s=48" width="48" height="48" alt="claude" title="claude"/></a> <a href="https://github.com/JustYannicc"><img src="https://avatars.githubusercontent.com/u/52761674?v=4&s=48" width="48" height="48" alt="JustYannicc" title="JustYannicc"/></a> <a href="https://github.com/Hyaxia"><img src="https://avatars.githubusercontent.com/u/36747317?v=4&s=48" width="48" height="48" alt="Hyaxia" title="Hyaxia"/></a>
|
||||||
<a href="https://github.com/apps/google-labs-jules"><img src="https://avatars.githubusercontent.com/in/842251?v=4&s=48" width="48" height="48" alt="google-labs-jules[bot]" title="google-labs-jules[bot]"/></a> <a href="https://github.com/lc0rp"><img src="https://avatars.githubusercontent.com/u/2609441?v=4&s=48" width="48" height="48" alt="lc0rp" title="lc0rp"/></a> <a href="https://github.com/mousberg"><img src="https://avatars.githubusercontent.com/u/57605064?v=4&s=48" width="48" height="48" alt="mousberg" title="mousberg"/></a> <a href="https://github.com/adam91holt"><img src="https://avatars.githubusercontent.com/u/9592417?v=4&s=48" width="48" height="48" alt="adam91holt" title="adam91holt"/></a> <a href="https://github.com/hougangdev"><img src="https://avatars.githubusercontent.com/u/105773686?v=4&s=48" width="48" height="48" alt="hougangdev" title="hougangdev"/></a> <a href="https://github.com/gumadeiras"><img src="https://avatars.githubusercontent.com/u/5599352?v=4&s=48" width="48" height="48" alt="gumadeiras" title="gumadeiras"/></a> <a href="https://github.com/shakkernerd"><img src="https://avatars.githubusercontent.com/u/165377636?v=4&s=48" width="48" height="48" alt="shakkernerd" title="shakkernerd"/></a> <a href="https://github.com/mteam88"><img src="https://avatars.githubusercontent.com/u/84196639?v=4&s=48" width="48" height="48" alt="mteam88" title="mteam88"/></a> <a href="https://github.com/hirefrank"><img src="https://avatars.githubusercontent.com/u/183158?v=4&s=48" width="48" height="48" alt="hirefrank" title="hirefrank"/></a> <a href="https://github.com/joeynyc"><img src="https://avatars.githubusercontent.com/u/17919866?v=4&s=48" width="48" height="48" alt="joeynyc" title="joeynyc"/></a>
|
<a href="https://github.com/dantelex"><img src="https://avatars.githubusercontent.com/u/631543?v=4&s=48" width="48" height="48" alt="dantelex" title="dantelex"/></a> <a href="https://github.com/SocialNerd42069"><img src="https://avatars.githubusercontent.com/u/118244303?v=4&s=48" width="48" height="48" alt="SocialNerd42069" title="SocialNerd42069"/></a> <a href="https://github.com/daveonkels"><img src="https://avatars.githubusercontent.com/u/533642?v=4&s=48" width="48" height="48" alt="daveonkels" title="daveonkels"/></a> <a href="https://github.com/apps/google-labs-jules"><img src="https://avatars.githubusercontent.com/in/842251?v=4&s=48" width="48" height="48" alt="google-labs-jules[bot]" title="google-labs-jules[bot]"/></a> <a href="https://github.com/lc0rp"><img src="https://avatars.githubusercontent.com/u/2609441?v=4&s=48" width="48" height="48" alt="lc0rp" title="lc0rp"/></a> <a href="https://github.com/mousberg"><img src="https://avatars.githubusercontent.com/u/57605064?v=4&s=48" width="48" height="48" alt="mousberg" title="mousberg"/></a> <a href="https://github.com/adam91holt"><img src="https://avatars.githubusercontent.com/u/9592417?v=4&s=48" width="48" height="48" alt="adam91holt" title="adam91holt"/></a> <a href="https://github.com/hougangdev"><img src="https://avatars.githubusercontent.com/u/105773686?v=4&s=48" width="48" height="48" alt="hougangdev" title="hougangdev"/></a> <a href="https://github.com/gumadeiras"><img src="https://avatars.githubusercontent.com/u/5599352?v=4&s=48" width="48" height="48" alt="gumadeiras" title="gumadeiras"/></a> <a href="https://github.com/shakkernerd"><img src="https://avatars.githubusercontent.com/u/165377636?v=4&s=48" width="48" height="48" alt="shakkernerd" title="shakkernerd"/></a>
|
||||||
<a href="https://github.com/orlyjamie"><img src="https://avatars.githubusercontent.com/u/6668807?v=4&s=48" width="48" height="48" alt="orlyjamie" title="orlyjamie"/></a> <a href="https://github.com/dbhurley"><img src="https://avatars.githubusercontent.com/u/5251425?v=4&s=48" width="48" height="48" alt="dbhurley" title="dbhurley"/></a> <a href="https://github.com/omniwired"><img src="https://avatars.githubusercontent.com/u/322761?v=4&s=48" width="48" height="48" alt="Eng. Juan Combetto" title="Eng. Juan Combetto"/></a> <a href="https://github.com/TSavo"><img src="https://avatars.githubusercontent.com/u/877990?v=4&s=48" width="48" height="48" alt="TSavo" title="TSavo"/></a> <a href="https://github.com/julianengel"><img src="https://avatars.githubusercontent.com/u/10634231?v=4&s=48" width="48" height="48" alt="julianengel" title="julianengel"/></a> <a href="https://github.com/bradleypriest"><img src="https://avatars.githubusercontent.com/u/167215?v=4&s=48" width="48" height="48" alt="bradleypriest" title="bradleypriest"/></a> <a href="https://github.com/benithors"><img src="https://avatars.githubusercontent.com/u/20652882?v=4&s=48" width="48" height="48" alt="benithors" title="benithors"/></a> <a href="https://github.com/rohannagpal"><img src="https://avatars.githubusercontent.com/u/4009239?v=4&s=48" width="48" height="48" alt="rohannagpal" title="rohannagpal"/></a> <a href="https://github.com/timolins"><img src="https://avatars.githubusercontent.com/u/1440854?v=4&s=48" width="48" height="48" alt="timolins" title="timolins"/></a> <a href="https://github.com/f-trycua"><img src="https://avatars.githubusercontent.com/u/195596869?v=4&s=48" width="48" height="48" alt="f-trycua" title="f-trycua"/></a>
|
<a href="https://github.com/mteam88"><img src="https://avatars.githubusercontent.com/u/84196639?v=4&s=48" width="48" height="48" alt="mteam88" title="mteam88"/></a> <a href="https://github.com/hirefrank"><img src="https://avatars.githubusercontent.com/u/183158?v=4&s=48" width="48" height="48" alt="hirefrank" title="hirefrank"/></a> <a href="https://github.com/joeynyc"><img src="https://avatars.githubusercontent.com/u/17919866?v=4&s=48" width="48" height="48" alt="joeynyc" title="joeynyc"/></a> <a href="https://github.com/orlyjamie"><img src="https://avatars.githubusercontent.com/u/6668807?v=4&s=48" width="48" height="48" alt="orlyjamie" title="orlyjamie"/></a> <a href="https://github.com/dbhurley"><img src="https://avatars.githubusercontent.com/u/5251425?v=4&s=48" width="48" height="48" alt="dbhurley" title="dbhurley"/></a> <a href="https://github.com/omniwired"><img src="https://avatars.githubusercontent.com/u/322761?v=4&s=48" width="48" height="48" alt="Eng. Juan Combetto" title="Eng. Juan Combetto"/></a> <a href="https://github.com/TSavo"><img src="https://avatars.githubusercontent.com/u/877990?v=4&s=48" width="48" height="48" alt="TSavo" title="TSavo"/></a> <a href="https://github.com/aerolalit"><img src="https://avatars.githubusercontent.com/u/17166039?v=4&s=48" width="48" height="48" alt="aerolalit" title="aerolalit"/></a> <a href="https://github.com/julianengel"><img src="https://avatars.githubusercontent.com/u/10634231?v=4&s=48" width="48" height="48" alt="julianengel" title="julianengel"/></a> <a href="https://github.com/bradleypriest"><img src="https://avatars.githubusercontent.com/u/167215?v=4&s=48" width="48" height="48" alt="bradleypriest" title="bradleypriest"/></a>
|
||||||
<a href="https://github.com/benostein"><img src="https://avatars.githubusercontent.com/u/31802821?v=4&s=48" width="48" height="48" alt="benostein" title="benostein"/></a> <a href="https://github.com/elliotsecops"><img src="https://avatars.githubusercontent.com/u/141947839?v=4&s=48" width="48" height="48" alt="elliotsecops" title="elliotsecops"/></a> <a href="https://github.com/Nachx639"><img src="https://avatars.githubusercontent.com/u/71144023?v=4&s=48" width="48" height="48" alt="nachx639" title="nachx639"/></a> <a href="https://github.com/pvoo"><img src="https://avatars.githubusercontent.com/u/20116814?v=4&s=48" width="48" height="48" alt="pvoo" title="pvoo"/></a> <a href="https://github.com/sreekaransrinath"><img src="https://avatars.githubusercontent.com/u/50989977?v=4&s=48" width="48" height="48" alt="sreekaransrinath" title="sreekaransrinath"/></a> <a href="https://github.com/gupsammy"><img src="https://avatars.githubusercontent.com/u/20296019?v=4&s=48" width="48" height="48" alt="gupsammy" title="gupsammy"/></a> <a href="https://github.com/cristip73"><img src="https://avatars.githubusercontent.com/u/24499421?v=4&s=48" width="48" height="48" alt="cristip73" title="cristip73"/></a> <a href="https://github.com/stefangalescu"><img src="https://avatars.githubusercontent.com/u/52995748?v=4&s=48" width="48" height="48" alt="stefangalescu" title="stefangalescu"/></a> <a href="https://github.com/nachoiacovino"><img src="https://avatars.githubusercontent.com/u/50103937?v=4&s=48" width="48" height="48" alt="nachoiacovino" title="nachoiacovino"/></a> <a href="https://github.com/vsabavat"><img src="https://avatars.githubusercontent.com/u/50385532?v=4&s=48" width="48" height="48" alt="Vasanth Rao Naik Sabavat" title="Vasanth Rao Naik Sabavat"/></a>
|
<a href="https://github.com/benithors"><img src="https://avatars.githubusercontent.com/u/20652882?v=4&s=48" width="48" height="48" alt="benithors" title="benithors"/></a> <a href="https://github.com/rohannagpal"><img src="https://avatars.githubusercontent.com/u/4009239?v=4&s=48" width="48" height="48" alt="rohannagpal" title="rohannagpal"/></a> <a href="https://github.com/timolins"><img src="https://avatars.githubusercontent.com/u/1440854?v=4&s=48" width="48" height="48" alt="timolins" title="timolins"/></a> <a href="https://github.com/f-trycua"><img src="https://avatars.githubusercontent.com/u/195596869?v=4&s=48" width="48" height="48" alt="f-trycua" title="f-trycua"/></a> <a href="https://github.com/benostein"><img src="https://avatars.githubusercontent.com/u/31802821?v=4&s=48" width="48" height="48" alt="benostein" title="benostein"/></a> <a href="https://github.com/elliotsecops"><img src="https://avatars.githubusercontent.com/u/141947839?v=4&s=48" width="48" height="48" alt="elliotsecops" title="elliotsecops"/></a> <a href="https://github.com/christianklotz"><img src="https://avatars.githubusercontent.com/u/69443?v=4&s=48" width="48" height="48" alt="christianklotz" title="christianklotz"/></a> <a href="https://github.com/Nachx639"><img src="https://avatars.githubusercontent.com/u/71144023?v=4&s=48" width="48" height="48" alt="nachx639" title="nachx639"/></a> <a href="https://github.com/pvoo"><img src="https://avatars.githubusercontent.com/u/20116814?v=4&s=48" width="48" height="48" alt="pvoo" title="pvoo"/></a> <a href="https://github.com/sreekaransrinath"><img src="https://avatars.githubusercontent.com/u/50989977?v=4&s=48" width="48" height="48" alt="sreekaransrinath" title="sreekaransrinath"/></a>
|
||||||
<a href="https://github.com/petter-b"><img src="https://avatars.githubusercontent.com/u/62076402?v=4&s=48" width="48" height="48" alt="petter-b" title="petter-b"/></a> <a href="https://github.com/thewilloftheshadow"><img src="https://avatars.githubusercontent.com/u/35580099?v=4&s=48" width="48" height="48" alt="thewilloftheshadow" title="thewilloftheshadow"/></a> <a href="https://github.com/cpojer"><img src="https://avatars.githubusercontent.com/u/13352?v=4&s=48" width="48" height="48" alt="cpojer" title="cpojer"/></a> <a href="https://github.com/scald"><img src="https://avatars.githubusercontent.com/u/1215913?v=4&s=48" width="48" height="48" alt="scald" title="scald"/></a> <a href="https://github.com/andranik-sahakyan"><img src="https://avatars.githubusercontent.com/u/8908029?v=4&s=48" width="48" height="48" alt="andranik-sahakyan" title="andranik-sahakyan"/></a> <a href="https://github.com/davidguttman"><img src="https://avatars.githubusercontent.com/u/431696?v=4&s=48" width="48" height="48" alt="davidguttman" title="davidguttman"/></a> <a href="https://github.com/sleontenko"><img src="https://avatars.githubusercontent.com/u/7135949?v=4&s=48" width="48" height="48" alt="sleontenko" title="sleontenko"/></a> <a href="https://github.com/denysvitali"><img src="https://avatars.githubusercontent.com/u/4939519?v=4&s=48" width="48" height="48" alt="denysvitali" title="denysvitali"/></a> <a href="https://github.com/sircrumpet"><img src="https://avatars.githubusercontent.com/u/4436535?v=4&s=48" width="48" height="48" alt="sircrumpet" title="sircrumpet"/></a> <a href="https://github.com/peschee"><img src="https://avatars.githubusercontent.com/u/63866?v=4&s=48" width="48" height="48" alt="peschee" title="peschee"/></a>
|
<a href="https://github.com/gupsammy"><img src="https://avatars.githubusercontent.com/u/20296019?v=4&s=48" width="48" height="48" alt="gupsammy" title="gupsammy"/></a> <a href="https://github.com/cristip73"><img src="https://avatars.githubusercontent.com/u/24499421?v=4&s=48" width="48" height="48" alt="cristip73" title="cristip73"/></a> <a href="https://github.com/stefangalescu"><img src="https://avatars.githubusercontent.com/u/52995748?v=4&s=48" width="48" height="48" alt="stefangalescu" title="stefangalescu"/></a> <a href="https://github.com/nachoiacovino"><img src="https://avatars.githubusercontent.com/u/50103937?v=4&s=48" width="48" height="48" alt="nachoiacovino" title="nachoiacovino"/></a> <a href="https://github.com/vsabavat"><img src="https://avatars.githubusercontent.com/u/50385532?v=4&s=48" width="48" height="48" alt="Vasanth Rao Naik Sabavat" title="Vasanth Rao Naik Sabavat"/></a> <a href="https://github.com/petter-b"><img src="https://avatars.githubusercontent.com/u/62076402?v=4&s=48" width="48" height="48" alt="petter-b" title="petter-b"/></a> <a href="https://github.com/thewilloftheshadow"><img src="https://avatars.githubusercontent.com/u/35580099?v=4&s=48" width="48" height="48" alt="thewilloftheshadow" title="thewilloftheshadow"/></a> <a href="https://github.com/leszekszpunar"><img src="https://avatars.githubusercontent.com/u/13106764?v=4&s=48" width="48" height="48" alt="leszekszpunar" title="leszekszpunar"/></a> <a href="https://github.com/scald"><img src="https://avatars.githubusercontent.com/u/1215913?v=4&s=48" width="48" height="48" alt="scald" title="scald"/></a> <a href="https://github.com/andranik-sahakyan"><img src="https://avatars.githubusercontent.com/u/8908029?v=4&s=48" width="48" height="48" alt="andranik-sahakyan" title="andranik-sahakyan"/></a>
|
||||||
<a href="https://github.com/nonggialiang"><img src="https://avatars.githubusercontent.com/u/14367839?v=4&s=48" width="48" height="48" alt="nonggialiang" title="nonggialiang"/></a> <a href="https://github.com/rafaelreis-r"><img src="https://avatars.githubusercontent.com/u/57492577?v=4&s=48" width="48" height="48" alt="rafaelreis-r" title="rafaelreis-r"/></a> <a href="https://github.com/dominicnunez"><img src="https://avatars.githubusercontent.com/u/43616264?v=4&s=48" width="48" height="48" alt="dominicnunez" title="dominicnunez"/></a> <a href="https://github.com/lploc94"><img src="https://avatars.githubusercontent.com/u/28453843?v=4&s=48" width="48" height="48" alt="lploc94" title="lploc94"/></a> <a href="https://github.com/ratulsarna"><img src="https://avatars.githubusercontent.com/u/105903728?v=4&s=48" width="48" height="48" alt="ratulsarna" title="ratulsarna"/></a> <a href="https://github.com/lutr0"><img src="https://avatars.githubusercontent.com/u/76906369?v=4&s=48" width="48" height="48" alt="lutr0" title="lutr0"/></a> <a href="https://github.com/kiranjd"><img src="https://avatars.githubusercontent.com/u/25822851?v=4&s=48" width="48" height="48" alt="kiranjd" title="kiranjd"/></a> <a href="https://github.com/danielz1z"><img src="https://avatars.githubusercontent.com/u/235270390?v=4&s=48" width="48" height="48" alt="danielz1z" title="danielz1z"/></a> <a href="https://github.com/AdeboyeDN"><img src="https://avatars.githubusercontent.com/u/65312338?v=4&s=48" width="48" height="48" alt="AdeboyeDN" title="AdeboyeDN"/></a> <a href="https://github.com/Alg0rix"><img src="https://avatars.githubusercontent.com/u/53804949?v=4&s=48" width="48" height="48" alt="Alg0rix" title="Alg0rix"/></a>
|
<a href="https://github.com/davidguttman"><img src="https://avatars.githubusercontent.com/u/431696?v=4&s=48" width="48" height="48" alt="davidguttman" title="davidguttman"/></a> <a href="https://github.com/sleontenko"><img src="https://avatars.githubusercontent.com/u/7135949?v=4&s=48" width="48" height="48" alt="sleontenko" title="sleontenko"/></a> <a href="https://github.com/denysvitali"><img src="https://avatars.githubusercontent.com/u/4939519?v=4&s=48" width="48" height="48" alt="denysvitali" title="denysvitali"/></a> <a href="https://github.com/sircrumpet"><img src="https://avatars.githubusercontent.com/u/4436535?v=4&s=48" width="48" height="48" alt="sircrumpet" title="sircrumpet"/></a> <a href="https://github.com/peschee"><img src="https://avatars.githubusercontent.com/u/63866?v=4&s=48" width="48" height="48" alt="peschee" title="peschee"/></a> <a href="https://github.com/nonggialiang"><img src="https://avatars.githubusercontent.com/u/14367839?v=4&s=48" width="48" height="48" alt="nonggialiang" title="nonggialiang"/></a> <a href="https://github.com/rafaelreis-r"><img src="https://avatars.githubusercontent.com/u/57492577?v=4&s=48" width="48" height="48" alt="rafaelreis-r" title="rafaelreis-r"/></a> <a href="https://github.com/dominicnunez"><img src="https://avatars.githubusercontent.com/u/43616264?v=4&s=48" width="48" height="48" alt="dominicnunez" title="dominicnunez"/></a> <a href="https://github.com/lploc94"><img src="https://avatars.githubusercontent.com/u/28453843?v=4&s=48" width="48" height="48" alt="lploc94" title="lploc94"/></a> <a href="https://github.com/ratulsarna"><img src="https://avatars.githubusercontent.com/u/105903728?v=4&s=48" width="48" height="48" alt="ratulsarna" title="ratulsarna"/></a>
|
||||||
<a href="https://github.com/papago2355"><img src="https://avatars.githubusercontent.com/u/68721273?v=4&s=48" width="48" height="48" alt="papago2355" title="papago2355"/></a> <a href="https://github.com/emanuelst"><img src="https://avatars.githubusercontent.com/u/9994339?v=4&s=48" width="48" height="48" alt="emanuelst" title="emanuelst"/></a> <a href="https://github.com/KristijanJovanovski"><img src="https://avatars.githubusercontent.com/u/8942284?v=4&s=48" width="48" height="48" alt="KristijanJovanovski" title="KristijanJovanovski"/></a> <a href="https://github.com/rdev"><img src="https://avatars.githubusercontent.com/u/8418866?v=4&s=48" width="48" height="48" alt="rdev" title="rdev"/></a> <a href="https://github.com/rhuanssauro"><img src="https://avatars.githubusercontent.com/u/164682191?v=4&s=48" width="48" height="48" alt="rhuanssauro" title="rhuanssauro"/></a> <a href="https://github.com/joshrad-dev"><img src="https://avatars.githubusercontent.com/u/62785552?v=4&s=48" width="48" height="48" alt="joshrad-dev" title="joshrad-dev"/></a> <a href="https://github.com/osolmaz"><img src="https://avatars.githubusercontent.com/u/2453968?v=4&s=48" width="48" height="48" alt="osolmaz" title="osolmaz"/></a> <a href="https://github.com/adityashaw2"><img src="https://avatars.githubusercontent.com/u/41204444?v=4&s=48" width="48" height="48" alt="adityashaw2" title="adityashaw2"/></a> <a href="https://github.com/CashWilliams"><img src="https://avatars.githubusercontent.com/u/613573?v=4&s=48" width="48" height="48" alt="CashWilliams" title="CashWilliams"/></a> <a href="https://github.com/search?q=sheeek"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="sheeek" title="sheeek"/></a>
|
<a href="https://github.com/sfo2001"><img src="https://avatars.githubusercontent.com/u/103369858?v=4&s=48" width="48" height="48" alt="sfo2001" title="sfo2001"/></a> <a href="https://github.com/lutr0"><img src="https://avatars.githubusercontent.com/u/76906369?v=4&s=48" width="48" height="48" alt="lutr0" title="lutr0"/></a> <a href="https://github.com/kiranjd"><img src="https://avatars.githubusercontent.com/u/25822851?v=4&s=48" width="48" height="48" alt="kiranjd" title="kiranjd"/></a> <a href="https://github.com/danielz1z"><img src="https://avatars.githubusercontent.com/u/235270390?v=4&s=48" width="48" height="48" alt="danielz1z" title="danielz1z"/></a> <a href="https://github.com/AdeboyeDN"><img src="https://avatars.githubusercontent.com/u/65312338?v=4&s=48" width="48" height="48" alt="AdeboyeDN" title="AdeboyeDN"/></a> <a href="https://github.com/Alg0rix"><img src="https://avatars.githubusercontent.com/u/53804949?v=4&s=48" width="48" height="48" alt="Alg0rix" title="Alg0rix"/></a> <a href="https://github.com/Takhoffman"><img src="https://avatars.githubusercontent.com/u/781889?v=4&s=48" width="48" height="48" alt="Takhoffman" title="Takhoffman"/></a> <a href="https://github.com/papago2355"><img src="https://avatars.githubusercontent.com/u/68721273?v=4&s=48" width="48" height="48" alt="papago2355" title="papago2355"/></a> <a href="https://github.com/apps/clawdinator"><img src="https://avatars.githubusercontent.com/in/2607181?v=4&s=48" width="48" height="48" alt="clawdinator[bot]" title="clawdinator[bot]"/></a> <a href="https://github.com/emanuelst"><img src="https://avatars.githubusercontent.com/u/9994339?v=4&s=48" width="48" height="48" alt="emanuelst" title="emanuelst"/></a>
|
||||||
<a href="https://github.com/ryancontent"><img src="https://avatars.githubusercontent.com/u/39743613?v=4&s=48" width="48" height="48" alt="ryancontent" title="ryancontent"/></a> <a href="https://github.com/artuskg"><img src="https://avatars.githubusercontent.com/u/11966157?v=4&s=48" width="48" height="48" alt="artuskg" title="artuskg"/></a> <a href="https://github.com/Takhoffman"><img src="https://avatars.githubusercontent.com/u/781889?v=4&s=48" width="48" height="48" alt="Takhoffman" title="Takhoffman"/></a> <a href="https://github.com/onutc"><img src="https://avatars.githubusercontent.com/u/152018508?v=4&s=48" width="48" height="48" alt="onutc" title="onutc"/></a> <a href="https://github.com/pauloportella"><img src="https://avatars.githubusercontent.com/u/22947229?v=4&s=48" width="48" height="48" alt="pauloportella" title="pauloportella"/></a> <a href="https://github.com/HirokiKobayashi-R"><img src="https://avatars.githubusercontent.com/u/37167840?v=4&s=48" width="48" height="48" alt="HirokiKobayashi-R" title="HirokiKobayashi-R"/></a> <a href="https://github.com/neooriginal"><img src="https://avatars.githubusercontent.com/u/54811660?v=4&s=48" width="48" height="48" alt="neooriginal" title="neooriginal"/></a> <a href="https://github.com/obviyus"><img src="https://avatars.githubusercontent.com/u/22031114?v=4&s=48" width="48" height="48" alt="obviyus" title="obviyus"/></a> <a href="https://github.com/ManuelHettich"><img src="https://avatars.githubusercontent.com/u/17690367?v=4&s=48" width="48" height="48" alt="manuelhettich" title="manuelhettich"/></a> <a href="https://github.com/minghinmatthewlam"><img src="https://avatars.githubusercontent.com/u/14224566?v=4&s=48" width="48" height="48" alt="minghinmatthewlam" title="minghinmatthewlam"/></a>
|
<a href="https://github.com/evanotero"><img src="https://avatars.githubusercontent.com/u/13204105?v=4&s=48" width="48" height="48" alt="evanotero" title="evanotero"/></a> <a href="https://github.com/KristijanJovanovski"><img src="https://avatars.githubusercontent.com/u/8942284?v=4&s=48" width="48" height="48" alt="KristijanJovanovski" title="KristijanJovanovski"/></a> <a href="https://github.com/jlowin"><img src="https://avatars.githubusercontent.com/u/153965?v=4&s=48" width="48" height="48" alt="jlowin" title="jlowin"/></a> <a href="https://github.com/rdev"><img src="https://avatars.githubusercontent.com/u/8418866?v=4&s=48" width="48" height="48" alt="rdev" title="rdev"/></a> <a href="https://github.com/rhuanssauro"><img src="https://avatars.githubusercontent.com/u/164682191?v=4&s=48" width="48" height="48" alt="rhuanssauro" title="rhuanssauro"/></a> <a href="https://github.com/joshrad-dev"><img src="https://avatars.githubusercontent.com/u/62785552?v=4&s=48" width="48" height="48" alt="joshrad-dev" title="joshrad-dev"/></a> <a href="https://github.com/obviyus"><img src="https://avatars.githubusercontent.com/u/22031114?v=4&s=48" width="48" height="48" alt="obviyus" title="obviyus"/></a> <a href="https://github.com/osolmaz"><img src="https://avatars.githubusercontent.com/u/2453968?v=4&s=48" width="48" height="48" alt="osolmaz" title="osolmaz"/></a> <a href="https://github.com/adityashaw2"><img src="https://avatars.githubusercontent.com/u/41204444?v=4&s=48" width="48" height="48" alt="adityashaw2" title="adityashaw2"/></a> <a href="https://github.com/CashWilliams"><img src="https://avatars.githubusercontent.com/u/613573?v=4&s=48" width="48" height="48" alt="CashWilliams" title="CashWilliams"/></a>
|
||||||
<a href="https://github.com/manikv12"><img src="https://avatars.githubusercontent.com/u/49544491?v=4&s=48" width="48" height="48" alt="manikv12" title="manikv12"/></a> <a href="https://github.com/myfunc"><img src="https://avatars.githubusercontent.com/u/19294627?v=4&s=48" width="48" height="48" alt="myfunc" title="myfunc"/></a> <a href="https://github.com/travisirby"><img src="https://avatars.githubusercontent.com/u/5958376?v=4&s=48" width="48" height="48" alt="travisirby" title="travisirby"/></a> <a href="https://github.com/buddyh"><img src="https://avatars.githubusercontent.com/u/31752869?v=4&s=48" width="48" height="48" alt="buddyh" title="buddyh"/></a> <a href="https://github.com/connorshea"><img src="https://avatars.githubusercontent.com/u/2977353?v=4&s=48" width="48" height="48" alt="connorshea" title="connorshea"/></a> <a href="https://github.com/kyleok"><img src="https://avatars.githubusercontent.com/u/58307870?v=4&s=48" width="48" height="48" alt="kyleok" title="kyleok"/></a> <a href="https://github.com/mcinteerj"><img src="https://avatars.githubusercontent.com/u/3613653?v=4&s=48" width="48" height="48" alt="mcinteerj" title="mcinteerj"/></a> <a href="https://github.com/apps/dependabot"><img src="https://avatars.githubusercontent.com/in/29110?v=4&s=48" width="48" height="48" alt="dependabot[bot]" title="dependabot[bot]"/></a> <a href="https://github.com/John-Rood"><img src="https://avatars.githubusercontent.com/u/62669593?v=4&s=48" width="48" height="48" alt="John-Rood" title="John-Rood"/></a> <a href="https://github.com/timkrase"><img src="https://avatars.githubusercontent.com/u/38947626?v=4&s=48" width="48" height="48" alt="timkrase" title="timkrase"/></a>
|
<a href="https://github.com/search?q=sheeek"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="sheeek" title="sheeek"/></a> <a href="https://github.com/ryancontent"><img src="https://avatars.githubusercontent.com/u/39743613?v=4&s=48" width="48" height="48" alt="ryancontent" title="ryancontent"/></a> <a href="https://github.com/jasonsschin"><img src="https://avatars.githubusercontent.com/u/1456889?v=4&s=48" width="48" height="48" alt="jasonsschin" title="jasonsschin"/></a> <a href="https://github.com/artuskg"><img src="https://avatars.githubusercontent.com/u/11966157?v=4&s=48" width="48" height="48" alt="artuskg" title="artuskg"/></a> <a href="https://github.com/onutc"><img src="https://avatars.githubusercontent.com/u/152018508?v=4&s=48" width="48" height="48" alt="onutc" title="onutc"/></a> <a href="https://github.com/pauloportella"><img src="https://avatars.githubusercontent.com/u/22947229?v=4&s=48" width="48" height="48" alt="pauloportella" title="pauloportella"/></a> <a href="https://github.com/HirokiKobayashi-R"><img src="https://avatars.githubusercontent.com/u/37167840?v=4&s=48" width="48" height="48" alt="HirokiKobayashi-R" title="HirokiKobayashi-R"/></a> <a href="https://github.com/ThanhNguyxn"><img src="https://avatars.githubusercontent.com/u/74597207?v=4&s=48" width="48" height="48" alt="ThanhNguyxn" title="ThanhNguyxn"/></a> <a href="https://github.com/kimitaka"><img src="https://avatars.githubusercontent.com/u/167225?v=4&s=48" width="48" height="48" alt="kimitaka" title="kimitaka"/></a> <a href="https://github.com/yuting0624"><img src="https://avatars.githubusercontent.com/u/32728916?v=4&s=48" width="48" height="48" alt="yuting0624" title="yuting0624"/></a>
|
||||||
<a href="https://github.com/uos-status"><img src="https://avatars.githubusercontent.com/u/255712580?v=4&s=48" width="48" height="48" alt="uos-status" title="uos-status"/></a> <a href="https://github.com/gerardward2007"><img src="https://avatars.githubusercontent.com/u/3002155?v=4&s=48" width="48" height="48" alt="gerardward2007" title="gerardward2007"/></a> <a href="https://github.com/roshanasingh4"><img src="https://avatars.githubusercontent.com/u/88576930?v=4&s=48" width="48" height="48" alt="roshanasingh4" title="roshanasingh4"/></a> <a href="https://github.com/tosh-hamburg"><img src="https://avatars.githubusercontent.com/u/58424326?v=4&s=48" width="48" height="48" alt="tosh-hamburg" title="tosh-hamburg"/></a> <a href="https://github.com/azade-c"><img src="https://avatars.githubusercontent.com/u/252790079?v=4&s=48" width="48" height="48" alt="azade-c" title="azade-c"/></a> <a href="https://github.com/dlauer"><img src="https://avatars.githubusercontent.com/u/757041?v=4&s=48" width="48" height="48" alt="dlauer" title="dlauer"/></a> <a href="https://github.com/JonUleis"><img src="https://avatars.githubusercontent.com/u/7644941?v=4&s=48" width="48" height="48" alt="JonUleis" title="JonUleis"/></a> <a href="https://github.com/shivamraut101"><img src="https://avatars.githubusercontent.com/u/110457469?v=4&s=48" width="48" height="48" alt="shivamraut101" title="shivamraut101"/></a> <a href="https://github.com/bjesuiter"><img src="https://avatars.githubusercontent.com/u/2365676?v=4&s=48" width="48" height="48" alt="bjesuiter" title="bjesuiter"/></a> <a href="https://github.com/cheeeee"><img src="https://avatars.githubusercontent.com/u/21245729?v=4&s=48" width="48" height="48" alt="cheeeee" title="cheeeee"/></a>
|
<a href="https://github.com/neooriginal"><img src="https://avatars.githubusercontent.com/u/54811660?v=4&s=48" width="48" height="48" alt="neooriginal" title="neooriginal"/></a> <a href="https://github.com/ManuelHettich"><img src="https://avatars.githubusercontent.com/u/17690367?v=4&s=48" width="48" height="48" alt="manuelhettich" title="manuelhettich"/></a> <a href="https://github.com/minghinmatthewlam"><img src="https://avatars.githubusercontent.com/u/14224566?v=4&s=48" width="48" height="48" alt="minghinmatthewlam" title="minghinmatthewlam"/></a> <a href="https://github.com/baccula"><img src="https://avatars.githubusercontent.com/u/22080883?v=4&s=48" width="48" height="48" alt="baccula" title="baccula"/></a> <a href="https://github.com/manikv12"><img src="https://avatars.githubusercontent.com/u/49544491?v=4&s=48" width="48" height="48" alt="manikv12" title="manikv12"/></a> <a href="https://github.com/myfunc"><img src="https://avatars.githubusercontent.com/u/19294627?v=4&s=48" width="48" height="48" alt="myfunc" title="myfunc"/></a> <a href="https://github.com/travisirby"><img src="https://avatars.githubusercontent.com/u/5958376?v=4&s=48" width="48" height="48" alt="travisirby" title="travisirby"/></a> <a href="https://github.com/buddyh"><img src="https://avatars.githubusercontent.com/u/31752869?v=4&s=48" width="48" height="48" alt="buddyh" title="buddyh"/></a> <a href="https://github.com/connorshea"><img src="https://avatars.githubusercontent.com/u/2977353?v=4&s=48" width="48" height="48" alt="connorshea" title="connorshea"/></a> <a href="https://github.com/kyleok"><img src="https://avatars.githubusercontent.com/u/58307870?v=4&s=48" width="48" height="48" alt="kyleok" title="kyleok"/></a>
|
||||||
<a href="https://github.com/robbyczgw-cla"><img src="https://avatars.githubusercontent.com/u/239660374?v=4&s=48" width="48" height="48" alt="robbyczgw-cla" title="robbyczgw-cla"/></a> <a href="https://github.com/conroywhitney"><img src="https://avatars.githubusercontent.com/u/249891?v=4&s=48" width="48" height="48" alt="conroywhitney" title="conroywhitney"/></a> <a href="https://github.com/j1philli"><img src="https://avatars.githubusercontent.com/u/3744255?v=4&s=48" width="48" height="48" alt="Josh Phillips" title="Josh Phillips"/></a> <a href="https://github.com/YuriNachos"><img src="https://avatars.githubusercontent.com/u/19365375?v=4&s=48" width="48" height="48" alt="YuriNachos" title="YuriNachos"/></a> <a href="https://github.com/pookNast"><img src="https://avatars.githubusercontent.com/u/14242552?v=4&s=48" width="48" height="48" alt="pookNast" title="pookNast"/></a> <a href="https://github.com/Whoaa512"><img src="https://avatars.githubusercontent.com/u/1581943?v=4&s=48" width="48" height="48" alt="Whoaa512" title="Whoaa512"/></a> <a href="https://github.com/chriseidhof"><img src="https://avatars.githubusercontent.com/u/5382?v=4&s=48" width="48" height="48" alt="chriseidhof" title="chriseidhof"/></a> <a href="https://github.com/ngutman"><img src="https://avatars.githubusercontent.com/u/1540134?v=4&s=48" width="48" height="48" alt="ngutman" title="ngutman"/></a> <a href="https://github.com/ysqander"><img src="https://avatars.githubusercontent.com/u/80843820?v=4&s=48" width="48" height="48" alt="ysqander" title="ysqander"/></a> <a href="https://github.com/aj47"><img src="https://avatars.githubusercontent.com/u/8023513?v=4&s=48" width="48" height="48" alt="aj47" title="aj47"/></a>
|
<a href="https://github.com/mcinteerj"><img src="https://avatars.githubusercontent.com/u/3613653?v=4&s=48" width="48" height="48" alt="mcinteerj" title="mcinteerj"/></a> <a href="https://github.com/apps/dependabot"><img src="https://avatars.githubusercontent.com/in/29110?v=4&s=48" width="48" height="48" alt="dependabot[bot]" title="dependabot[bot]"/></a> <a href="https://github.com/amitbiswal007"><img src="https://avatars.githubusercontent.com/u/108086198?v=4&s=48" width="48" height="48" alt="amitbiswal007" title="amitbiswal007"/></a> <a href="https://github.com/John-Rood"><img src="https://avatars.githubusercontent.com/u/62669593?v=4&s=48" width="48" height="48" alt="John-Rood" title="John-Rood"/></a> <a href="https://github.com/timkrase"><img src="https://avatars.githubusercontent.com/u/38947626?v=4&s=48" width="48" height="48" alt="timkrase" title="timkrase"/></a> <a href="https://github.com/uos-status"><img src="https://avatars.githubusercontent.com/u/255712580?v=4&s=48" width="48" height="48" alt="uos-status" title="uos-status"/></a> <a href="https://github.com/gerardward2007"><img src="https://avatars.githubusercontent.com/u/3002155?v=4&s=48" width="48" height="48" alt="gerardward2007" title="gerardward2007"/></a> <a href="https://github.com/roshanasingh4"><img src="https://avatars.githubusercontent.com/u/88576930?v=4&s=48" width="48" height="48" alt="roshanasingh4" title="roshanasingh4"/></a> <a href="https://github.com/tosh-hamburg"><img src="https://avatars.githubusercontent.com/u/58424326?v=4&s=48" width="48" height="48" alt="tosh-hamburg" title="tosh-hamburg"/></a> <a href="https://github.com/azade-c"><img src="https://avatars.githubusercontent.com/u/252790079?v=4&s=48" width="48" height="48" alt="azade-c" title="azade-c"/></a>
|
||||||
<a href="https://github.com/kennyklee"><img src="https://avatars.githubusercontent.com/u/1432489?v=4&s=48" width="48" height="48" alt="kennyklee" title="kennyklee"/></a> <a href="https://github.com/superman32432432"><img src="https://avatars.githubusercontent.com/u/7228420?v=4&s=48" width="48" height="48" alt="superman32432432" title="superman32432432"/></a> <a href="https://github.com/search?q=Yurii%20Chukhlib"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Yurii Chukhlib" title="Yurii Chukhlib"/></a> <a href="https://github.com/grp06"><img src="https://avatars.githubusercontent.com/u/1573959?v=4&s=48" width="48" height="48" alt="grp06" title="grp06"/></a> <a href="https://github.com/antons"><img src="https://avatars.githubusercontent.com/u/129705?v=4&s=48" width="48" height="48" alt="antons" title="antons"/></a> <a href="https://github.com/austinm911"><img src="https://avatars.githubusercontent.com/u/31991302?v=4&s=48" width="48" height="48" alt="austinm911" title="austinm911"/></a> <a href="https://github.com/apps/blacksmith-sh"><img src="https://avatars.githubusercontent.com/in/807020?v=4&s=48" width="48" height="48" alt="blacksmith-sh[bot]" title="blacksmith-sh[bot]"/></a> <a href="https://github.com/damoahdominic"><img src="https://avatars.githubusercontent.com/u/4623434?v=4&s=48" width="48" height="48" alt="damoahdominic" title="damoahdominic"/></a> <a href="https://github.com/dan-dr"><img src="https://avatars.githubusercontent.com/u/6669808?v=4&s=48" width="48" height="48" alt="dan-dr" title="dan-dr"/></a> <a href="https://github.com/HeimdallStrategy"><img src="https://avatars.githubusercontent.com/u/223014405?v=4&s=48" width="48" height="48" alt="HeimdallStrategy" title="HeimdallStrategy"/></a>
|
<a href="https://github.com/badlogic"><img src="https://avatars.githubusercontent.com/u/514052?v=4&s=48" width="48" height="48" alt="badlogic" title="badlogic"/></a> <a href="https://github.com/dlauer"><img src="https://avatars.githubusercontent.com/u/757041?v=4&s=48" width="48" height="48" alt="dlauer" title="dlauer"/></a> <a href="https://github.com/JonUleis"><img src="https://avatars.githubusercontent.com/u/7644941?v=4&s=48" width="48" height="48" alt="JonUleis" title="JonUleis"/></a> <a href="https://github.com/shivamraut101"><img src="https://avatars.githubusercontent.com/u/110457469?v=4&s=48" width="48" height="48" alt="shivamraut101" title="shivamraut101"/></a> <a href="https://github.com/bjesuiter"><img src="https://avatars.githubusercontent.com/u/2365676?v=4&s=48" width="48" height="48" alt="bjesuiter" title="bjesuiter"/></a> <a href="https://github.com/cheeeee"><img src="https://avatars.githubusercontent.com/u/21245729?v=4&s=48" width="48" height="48" alt="cheeeee" title="cheeeee"/></a> <a href="https://github.com/robbyczgw-cla"><img src="https://avatars.githubusercontent.com/u/239660374?v=4&s=48" width="48" height="48" alt="robbyczgw-cla" title="robbyczgw-cla"/></a> <a href="https://github.com/YuriNachos"><img src="https://avatars.githubusercontent.com/u/19365375?v=4&s=48" width="48" height="48" alt="YuriNachos" title="YuriNachos"/></a> <a href="https://github.com/j1philli"><img src="https://avatars.githubusercontent.com/u/3744255?v=4&s=48" width="48" height="48" alt="Josh Phillips" title="Josh Phillips"/></a> <a href="https://github.com/pookNast"><img src="https://avatars.githubusercontent.com/u/14242552?v=4&s=48" width="48" height="48" alt="pookNast" title="pookNast"/></a>
|
||||||
<a href="https://github.com/imfing"><img src="https://avatars.githubusercontent.com/u/5097752?v=4&s=48" width="48" height="48" alt="imfing" title="imfing"/></a> <a href="https://github.com/jalehman"><img src="https://avatars.githubusercontent.com/u/550978?v=4&s=48" width="48" height="48" alt="jalehman" title="jalehman"/></a> <a href="https://github.com/jarvis-medmatic"><img src="https://avatars.githubusercontent.com/u/252428873?v=4&s=48" width="48" height="48" alt="jarvis-medmatic" title="jarvis-medmatic"/></a> <a href="https://github.com/kkarimi"><img src="https://avatars.githubusercontent.com/u/875218?v=4&s=48" width="48" height="48" alt="kkarimi" title="kkarimi"/></a> <a href="https://github.com/mahmoudashraf93"><img src="https://avatars.githubusercontent.com/u/9130129?v=4&s=48" width="48" height="48" alt="mahmoudashraf93" title="mahmoudashraf93"/></a> <a href="https://github.com/pkrmf"><img src="https://avatars.githubusercontent.com/u/1714267?v=4&s=48" width="48" height="48" alt="pkrmf" title="pkrmf"/></a> <a href="https://github.com/RandyVentures"><img src="https://avatars.githubusercontent.com/u/149904821?v=4&s=48" width="48" height="48" alt="RandyVentures" title="RandyVentures"/></a> <a href="https://github.com/robhparker"><img src="https://avatars.githubusercontent.com/u/7404740?v=4&s=48" width="48" height="48" alt="robhparker" title="robhparker"/></a> <a href="https://github.com/search?q=Ryan%20Lisse"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Ryan Lisse" title="Ryan Lisse"/></a> <a href="https://github.com/dougvk"><img src="https://avatars.githubusercontent.com/u/401660?v=4&s=48" width="48" height="48" alt="dougvk" title="dougvk"/></a>
|
<a href="https://github.com/Whoaa512"><img src="https://avatars.githubusercontent.com/u/1581943?v=4&s=48" width="48" height="48" alt="Whoaa512" title="Whoaa512"/></a> <a href="https://github.com/chriseidhof"><img src="https://avatars.githubusercontent.com/u/5382?v=4&s=48" width="48" height="48" alt="chriseidhof" title="chriseidhof"/></a> <a href="https://github.com/ngutman"><img src="https://avatars.githubusercontent.com/u/1540134?v=4&s=48" width="48" height="48" alt="ngutman" title="ngutman"/></a> <a href="https://github.com/ysqander"><img src="https://avatars.githubusercontent.com/u/80843820?v=4&s=48" width="48" height="48" alt="ysqander" title="ysqander"/></a> <a href="https://github.com/search?q=Yurii%20Chukhlib"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Yurii Chukhlib" title="Yurii Chukhlib"/></a> <a href="https://github.com/aj47"><img src="https://avatars.githubusercontent.com/u/8023513?v=4&s=48" width="48" height="48" alt="aj47" title="aj47"/></a> <a href="https://github.com/kennyklee"><img src="https://avatars.githubusercontent.com/u/1432489?v=4&s=48" width="48" height="48" alt="kennyklee" title="kennyklee"/></a> <a href="https://github.com/superman32432432"><img src="https://avatars.githubusercontent.com/u/7228420?v=4&s=48" width="48" height="48" alt="superman32432432" title="superman32432432"/></a> <a href="https://github.com/grp06"><img src="https://avatars.githubusercontent.com/u/1573959?v=4&s=48" width="48" height="48" alt="grp06" title="grp06"/></a> <a href="https://github.com/Hisleren"><img src="https://avatars.githubusercontent.com/u/83217244?v=4&s=48" width="48" height="48" alt="Hisleren" title="Hisleren"/></a>
|
||||||
<a href="https://github.com/erikpr1994"><img src="https://avatars.githubusercontent.com/u/6299331?v=4&s=48" width="48" height="48" alt="erikpr1994" title="erikpr1994"/></a> <a href="https://github.com/fal3"><img src="https://avatars.githubusercontent.com/u/6484295?v=4&s=48" width="48" height="48" alt="fal3" title="fal3"/></a> <a href="https://github.com/search?q=Ghost"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Ghost" title="Ghost"/></a> <a href="https://github.com/jonasjancarik"><img src="https://avatars.githubusercontent.com/u/2459191?v=4&s=48" width="48" height="48" alt="jonasjancarik" title="jonasjancarik"/></a> <a href="https://github.com/search?q=Keith%20the%20Silly%20Goose"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Keith the Silly Goose" title="Keith the Silly Goose"/></a> <a href="https://github.com/search?q=L36%20Server"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="L36 Server" title="L36 Server"/></a> <a href="https://github.com/search?q=Marc"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Marc" title="Marc"/></a> <a href="https://github.com/mitschabaude-bot"><img src="https://avatars.githubusercontent.com/u/247582884?v=4&s=48" width="48" height="48" alt="mitschabaude-bot" title="mitschabaude-bot"/></a> <a href="https://github.com/mkbehr"><img src="https://avatars.githubusercontent.com/u/1285?v=4&s=48" width="48" height="48" alt="mkbehr" title="mkbehr"/></a> <a href="https://github.com/neist"><img src="https://avatars.githubusercontent.com/u/1029724?v=4&s=48" width="48" height="48" alt="neist" title="neist"/></a>
|
<a href="https://github.com/shatner"><img src="https://avatars.githubusercontent.com/u/17735435?v=4&s=48" width="48" height="48" alt="shatner" title="shatner"/></a> <a href="https://github.com/antons"><img src="https://avatars.githubusercontent.com/u/129705?v=4&s=48" width="48" height="48" alt="antons" title="antons"/></a> <a href="https://github.com/austinm911"><img src="https://avatars.githubusercontent.com/u/31991302?v=4&s=48" width="48" height="48" alt="austinm911" title="austinm911"/></a> <a href="https://github.com/apps/blacksmith-sh"><img src="https://avatars.githubusercontent.com/in/807020?v=4&s=48" width="48" height="48" alt="blacksmith-sh[bot]" title="blacksmith-sh[bot]"/></a> <a href="https://github.com/damoahdominic"><img src="https://avatars.githubusercontent.com/u/4623434?v=4&s=48" width="48" height="48" alt="damoahdominic" title="damoahdominic"/></a> <a href="https://github.com/dan-dr"><img src="https://avatars.githubusercontent.com/u/6669808?v=4&s=48" width="48" height="48" alt="dan-dr" title="dan-dr"/></a> <a href="https://github.com/GHesericsu"><img src="https://avatars.githubusercontent.com/u/60202455?v=4&s=48" width="48" height="48" alt="GHesericsu" title="GHesericsu"/></a> <a href="https://github.com/HeimdallStrategy"><img src="https://avatars.githubusercontent.com/u/223014405?v=4&s=48" width="48" height="48" alt="HeimdallStrategy" title="HeimdallStrategy"/></a> <a href="https://github.com/imfing"><img src="https://avatars.githubusercontent.com/u/5097752?v=4&s=48" width="48" height="48" alt="imfing" title="imfing"/></a> <a href="https://github.com/jalehman"><img src="https://avatars.githubusercontent.com/u/550978?v=4&s=48" width="48" height="48" alt="jalehman" title="jalehman"/></a>
|
||||||
<a href="https://github.com/sibbl"><img src="https://avatars.githubusercontent.com/u/866535?v=4&s=48" width="48" height="48" alt="sibbl" title="sibbl"/></a> <a href="https://github.com/chrisrodz"><img src="https://avatars.githubusercontent.com/u/2967620?v=4&s=48" width="48" height="48" alt="chrisrodz" title="chrisrodz"/></a> <a href="https://github.com/search?q=Friederike%20Seiler"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Friederike Seiler" title="Friederike Seiler"/></a> <a href="https://github.com/gabriel-trigo"><img src="https://avatars.githubusercontent.com/u/38991125?v=4&s=48" width="48" height="48" alt="gabriel-trigo" title="gabriel-trigo"/></a> <a href="https://github.com/Iamadig"><img src="https://avatars.githubusercontent.com/u/102129234?v=4&s=48" width="48" height="48" alt="iamadig" title="iamadig"/></a> <a href="https://github.com/jdrhyne"><img src="https://avatars.githubusercontent.com/u/7828464?v=4&s=48" width="48" height="48" alt="Jonathan D. Rhyne (DJ-D)" title="Jonathan D. Rhyne (DJ-D)"/></a> <a href="https://github.com/search?q=Joshua%20Mitchell"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Joshua Mitchell" title="Joshua Mitchell"/></a> <a href="https://github.com/search?q=Kit"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Kit" title="Kit"/></a> <a href="https://github.com/koala73"><img src="https://avatars.githubusercontent.com/u/996596?v=4&s=48" width="48" height="48" alt="koala73" title="koala73"/></a> <a href="https://github.com/manmal"><img src="https://avatars.githubusercontent.com/u/142797?v=4&s=48" width="48" height="48" alt="manmal" title="manmal"/></a>
|
<a href="https://github.com/jarvis-medmatic"><img src="https://avatars.githubusercontent.com/u/252428873?v=4&s=48" width="48" height="48" alt="jarvis-medmatic" title="jarvis-medmatic"/></a> <a href="https://github.com/kkarimi"><img src="https://avatars.githubusercontent.com/u/875218?v=4&s=48" width="48" height="48" alt="kkarimi" title="kkarimi"/></a> <a href="https://github.com/mahmoudashraf93"><img src="https://avatars.githubusercontent.com/u/9130129?v=4&s=48" width="48" height="48" alt="mahmoudashraf93" title="mahmoudashraf93"/></a> <a href="https://github.com/pkrmf"><img src="https://avatars.githubusercontent.com/u/1714267?v=4&s=48" width="48" height="48" alt="pkrmf" title="pkrmf"/></a> <a href="https://github.com/RandyVentures"><img src="https://avatars.githubusercontent.com/u/149904821?v=4&s=48" width="48" height="48" alt="RandyVentures" title="RandyVentures"/></a> <a href="https://github.com/robhparker"><img src="https://avatars.githubusercontent.com/u/7404740?v=4&s=48" width="48" height="48" alt="robhparker" title="robhparker"/></a> <a href="https://github.com/search?q=Ryan%20Lisse"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Ryan Lisse" title="Ryan Lisse"/></a> <a href="https://github.com/dougvk"><img src="https://avatars.githubusercontent.com/u/401660?v=4&s=48" width="48" height="48" alt="dougvk" title="dougvk"/></a> <a href="https://github.com/erikpr1994"><img src="https://avatars.githubusercontent.com/u/6299331?v=4&s=48" width="48" height="48" alt="erikpr1994" title="erikpr1994"/></a> <a href="https://github.com/fal3"><img src="https://avatars.githubusercontent.com/u/6484295?v=4&s=48" width="48" height="48" alt="fal3" title="fal3"/></a>
|
||||||
<a href="https://github.com/ogulcancelik"><img src="https://avatars.githubusercontent.com/u/7064011?v=4&s=48" width="48" height="48" alt="ogulcancelik" title="ogulcancelik"/></a> <a href="https://github.com/pasogott"><img src="https://avatars.githubusercontent.com/u/23458152?v=4&s=48" width="48" height="48" alt="pasogott" title="pasogott"/></a> <a href="https://github.com/petradonka"><img src="https://avatars.githubusercontent.com/u/7353770?v=4&s=48" width="48" height="48" alt="petradonka" title="petradonka"/></a> <a href="https://github.com/rubyrunsstuff"><img src="https://avatars.githubusercontent.com/u/246602379?v=4&s=48" width="48" height="48" alt="rubyrunsstuff" title="rubyrunsstuff"/></a> <a href="https://github.com/siddhantjain"><img src="https://avatars.githubusercontent.com/u/4835232?v=4&s=48" width="48" height="48" alt="siddhantjain" title="siddhantjain"/></a> <a href="https://github.com/suminhthanh"><img src="https://avatars.githubusercontent.com/u/2907636?v=4&s=48" width="48" height="48" alt="suminhthanh" title="suminhthanh"/></a> <a href="https://github.com/svkozak"><img src="https://avatars.githubusercontent.com/u/31941359?v=4&s=48" width="48" height="48" alt="svkozak" title="svkozak"/></a> <a href="https://github.com/VACInc"><img src="https://avatars.githubusercontent.com/u/3279061?v=4&s=48" width="48" height="48" alt="VACInc" title="VACInc"/></a> <a href="https://github.com/wes-davis"><img src="https://avatars.githubusercontent.com/u/16506720?v=4&s=48" width="48" height="48" alt="wes-davis" title="wes-davis"/></a> <a href="https://github.com/zats"><img src="https://avatars.githubusercontent.com/u/2688806?v=4&s=48" width="48" height="48" alt="zats" title="zats"/></a>
|
<a href="https://github.com/search?q=Ghost"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Ghost" title="Ghost"/></a> <a href="https://github.com/jonasjancarik"><img src="https://avatars.githubusercontent.com/u/2459191?v=4&s=48" width="48" height="48" alt="jonasjancarik" title="jonasjancarik"/></a> <a href="https://github.com/search?q=Keith%20the%20Silly%20Goose"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Keith the Silly Goose" title="Keith the Silly Goose"/></a> <a href="https://github.com/search?q=L36%20Server"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="L36 Server" title="L36 Server"/></a> <a href="https://github.com/search?q=Marc"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Marc" title="Marc"/></a> <a href="https://github.com/mitschabaude-bot"><img src="https://avatars.githubusercontent.com/u/247582884?v=4&s=48" width="48" height="48" alt="mitschabaude-bot" title="mitschabaude-bot"/></a> <a href="https://github.com/mkbehr"><img src="https://avatars.githubusercontent.com/u/1285?v=4&s=48" width="48" height="48" alt="mkbehr" title="mkbehr"/></a> <a href="https://github.com/neist"><img src="https://avatars.githubusercontent.com/u/1029724?v=4&s=48" width="48" height="48" alt="neist" title="neist"/></a> <a href="https://github.com/sibbl"><img src="https://avatars.githubusercontent.com/u/866535?v=4&s=48" width="48" height="48" alt="sibbl" title="sibbl"/></a> <a href="https://github.com/abhijeet117"><img src="https://avatars.githubusercontent.com/u/192859219?v=4&s=48" width="48" height="48" alt="abhijeet117" title="abhijeet117"/></a>
|
||||||
<a href="https://github.com/24601"><img src="https://avatars.githubusercontent.com/u/1157207?v=4&s=48" width="48" height="48" alt="24601" title="24601"/></a> <a href="https://github.com/ameno-"><img src="https://avatars.githubusercontent.com/u/2416135?v=4&s=48" width="48" height="48" alt="ameno-" title="ameno-"/></a> <a href="https://github.com/search?q=Chris%20Taylor"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Chris Taylor" title="Chris Taylor"/></a> <a href="https://github.com/dguido"><img src="https://avatars.githubusercontent.com/u/294844?v=4&s=48" width="48" height="48" alt="dguido" title="dguido"/></a> <a href="https://github.com/djangonavarro220"><img src="https://avatars.githubusercontent.com/u/251162586?v=4&s=48" width="48" height="48" alt="Django Navarro" title="Django Navarro"/></a> <a href="https://github.com/evalexpr"><img src="https://avatars.githubusercontent.com/u/23485511?v=4&s=48" width="48" height="48" alt="evalexpr" title="evalexpr"/></a> <a href="https://github.com/henrino3"><img src="https://avatars.githubusercontent.com/u/4260288?v=4&s=48" width="48" height="48" alt="henrino3" title="henrino3"/></a> <a href="https://github.com/humanwritten"><img src="https://avatars.githubusercontent.com/u/206531610?v=4&s=48" width="48" height="48" alt="humanwritten" title="humanwritten"/></a> <a href="https://github.com/larlyssa"><img src="https://avatars.githubusercontent.com/u/13128869?v=4&s=48" width="48" height="48" alt="larlyssa" title="larlyssa"/></a> <a href="https://github.com/Lukavyi"><img src="https://avatars.githubusercontent.com/u/1013690?v=4&s=48" width="48" height="48" alt="Lukavyi" title="Lukavyi"/></a>
|
<a href="https://github.com/chrisrodz"><img src="https://avatars.githubusercontent.com/u/2967620?v=4&s=48" width="48" height="48" alt="chrisrodz" title="chrisrodz"/></a> <a href="https://github.com/search?q=Friederike%20Seiler"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Friederike Seiler" title="Friederike Seiler"/></a> <a href="https://github.com/gabriel-trigo"><img src="https://avatars.githubusercontent.com/u/38991125?v=4&s=48" width="48" height="48" alt="gabriel-trigo" title="gabriel-trigo"/></a> <a href="https://github.com/Iamadig"><img src="https://avatars.githubusercontent.com/u/102129234?v=4&s=48" width="48" height="48" alt="iamadig" title="iamadig"/></a> <a href="https://github.com/itsjling"><img src="https://avatars.githubusercontent.com/u/2521993?v=4&s=48" width="48" height="48" alt="itsjling" title="itsjling"/></a> <a href="https://github.com/jdrhyne"><img src="https://avatars.githubusercontent.com/u/7828464?v=4&s=48" width="48" height="48" alt="Jonathan D. Rhyne (DJ-D)" title="Jonathan D. Rhyne (DJ-D)"/></a> <a href="https://github.com/search?q=Joshua%20Mitchell"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Joshua Mitchell" title="Joshua Mitchell"/></a> <a href="https://github.com/search?q=Kit"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Kit" title="Kit"/></a> <a href="https://github.com/koala73"><img src="https://avatars.githubusercontent.com/u/996596?v=4&s=48" width="48" height="48" alt="koala73" title="koala73"/></a> <a href="https://github.com/manmal"><img src="https://avatars.githubusercontent.com/u/142797?v=4&s=48" width="48" height="48" alt="manmal" title="manmal"/></a>
|
||||||
<a href="https://github.com/odysseus0"><img src="https://avatars.githubusercontent.com/u/8635094?v=4&s=48" width="48" height="48" alt="odysseus0" title="odysseus0"/></a> <a href="https://github.com/oswalpalash"><img src="https://avatars.githubusercontent.com/u/6431196?v=4&s=48" width="48" height="48" alt="oswalpalash" title="oswalpalash"/></a> <a href="https://github.com/pcty-nextgen-service-account"><img src="https://avatars.githubusercontent.com/u/112553441?v=4&s=48" width="48" height="48" alt="pcty-nextgen-service-account" title="pcty-nextgen-service-account"/></a> <a href="https://github.com/pi0"><img src="https://avatars.githubusercontent.com/u/5158436?v=4&s=48" width="48" height="48" alt="pi0" title="pi0"/></a> <a href="https://github.com/rmorse"><img src="https://avatars.githubusercontent.com/u/853547?v=4&s=48" width="48" height="48" alt="rmorse" title="rmorse"/></a> <a href="https://github.com/search?q=Roopak%20Nijhara"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Roopak Nijhara" title="Roopak Nijhara"/></a> <a href="https://github.com/Syhids"><img src="https://avatars.githubusercontent.com/u/671202?v=4&s=48" width="48" height="48" alt="Syhids" title="Syhids"/></a> <a href="https://github.com/search?q=Aaron%20Konyer"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Aaron Konyer" title="Aaron Konyer"/></a> <a href="https://github.com/aaronveklabs"><img src="https://avatars.githubusercontent.com/u/225997828?v=4&s=48" width="48" height="48" alt="aaronveklabs" title="aaronveklabs"/></a> <a href="https://github.com/andreabadesso"><img src="https://avatars.githubusercontent.com/u/3586068?v=4&s=48" width="48" height="48" alt="andreabadesso" title="andreabadesso"/></a>
|
<a href="https://github.com/ogulcancelik"><img src="https://avatars.githubusercontent.com/u/7064011?v=4&s=48" width="48" height="48" alt="ogulcancelik" title="ogulcancelik"/></a> <a href="https://github.com/pasogott"><img src="https://avatars.githubusercontent.com/u/23458152?v=4&s=48" width="48" height="48" alt="pasogott" title="pasogott"/></a> <a href="https://github.com/petradonka"><img src="https://avatars.githubusercontent.com/u/7353770?v=4&s=48" width="48" height="48" alt="petradonka" title="petradonka"/></a> <a href="https://github.com/rubyrunsstuff"><img src="https://avatars.githubusercontent.com/u/246602379?v=4&s=48" width="48" height="48" alt="rubyrunsstuff" title="rubyrunsstuff"/></a> <a href="https://github.com/siddhantjain"><img src="https://avatars.githubusercontent.com/u/4835232?v=4&s=48" width="48" height="48" alt="siddhantjain" title="siddhantjain"/></a> <a href="https://github.com/spiceoogway"><img src="https://avatars.githubusercontent.com/u/105812383?v=4&s=48" width="48" height="48" alt="spiceoogway" title="spiceoogway"/></a> <a href="https://github.com/suminhthanh"><img src="https://avatars.githubusercontent.com/u/2907636?v=4&s=48" width="48" height="48" alt="suminhthanh" title="suminhthanh"/></a> <a href="https://github.com/svkozak"><img src="https://avatars.githubusercontent.com/u/31941359?v=4&s=48" width="48" height="48" alt="svkozak" title="svkozak"/></a> <a href="https://github.com/wes-davis"><img src="https://avatars.githubusercontent.com/u/16506720?v=4&s=48" width="48" height="48" alt="wes-davis" title="wes-davis"/></a> <a href="https://github.com/zats"><img src="https://avatars.githubusercontent.com/u/2688806?v=4&s=48" width="48" height="48" alt="zats" title="zats"/></a>
|
||||||
<a href="https://github.com/search?q=Andrii"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Andrii" title="Andrii"/></a> <a href="https://github.com/cash-echo-bot"><img src="https://avatars.githubusercontent.com/u/252747386?v=4&s=48" width="48" height="48" alt="cash-echo-bot" title="cash-echo-bot"/></a> <a href="https://github.com/search?q=Clawd"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Clawd" title="Clawd"/></a> <a href="https://github.com/search?q=ClawdFx"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="ClawdFx" title="ClawdFx"/></a> <a href="https://github.com/EnzeD"><img src="https://avatars.githubusercontent.com/u/9866900?v=4&s=48" width="48" height="48" alt="EnzeD" title="EnzeD"/></a> <a href="https://github.com/erik-agens"><img src="https://avatars.githubusercontent.com/u/80908960?v=4&s=48" width="48" height="48" alt="erik-agens" title="erik-agens"/></a> <a href="https://github.com/Evizero"><img src="https://avatars.githubusercontent.com/u/10854026?v=4&s=48" width="48" height="48" alt="Evizero" title="Evizero"/></a> <a href="https://github.com/fcatuhe"><img src="https://avatars.githubusercontent.com/u/17382215?v=4&s=48" width="48" height="48" alt="fcatuhe" title="fcatuhe"/></a> <a href="https://github.com/itsjaydesu"><img src="https://avatars.githubusercontent.com/u/220390?v=4&s=48" width="48" height="48" alt="itsjaydesu" title="itsjaydesu"/></a> <a href="https://github.com/ivancasco"><img src="https://avatars.githubusercontent.com/u/2452858?v=4&s=48" width="48" height="48" alt="ivancasco" title="ivancasco"/></a>
|
<a href="https://github.com/24601"><img src="https://avatars.githubusercontent.com/u/1157207?v=4&s=48" width="48" height="48" alt="24601" title="24601"/></a> <a href="https://github.com/ameno-"><img src="https://avatars.githubusercontent.com/u/2416135?v=4&s=48" width="48" height="48" alt="ameno-" title="ameno-"/></a> <a href="https://github.com/bonald"><img src="https://avatars.githubusercontent.com/u/12394874?v=4&s=48" width="48" height="48" alt="bonald" title="bonald"/></a> <a href="https://github.com/bravostation"><img src="https://avatars.githubusercontent.com/u/257991910?v=4&s=48" width="48" height="48" alt="bravostation" title="bravostation"/></a> <a href="https://github.com/search?q=Chris%20Taylor"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Chris Taylor" title="Chris Taylor"/></a> <a href="https://github.com/dguido"><img src="https://avatars.githubusercontent.com/u/294844?v=4&s=48" width="48" height="48" alt="dguido" title="dguido"/></a> <a href="https://github.com/djangonavarro220"><img src="https://avatars.githubusercontent.com/u/251162586?v=4&s=48" width="48" height="48" alt="Django Navarro" title="Django Navarro"/></a> <a href="https://github.com/evalexpr"><img src="https://avatars.githubusercontent.com/u/23485511?v=4&s=48" width="48" height="48" alt="evalexpr" title="evalexpr"/></a> <a href="https://github.com/henrino3"><img src="https://avatars.githubusercontent.com/u/4260288?v=4&s=48" width="48" height="48" alt="henrino3" title="henrino3"/></a> <a href="https://github.com/humanwritten"><img src="https://avatars.githubusercontent.com/u/206531610?v=4&s=48" width="48" height="48" alt="humanwritten" title="humanwritten"/></a>
|
||||||
<a href="https://github.com/ivanrvpereira"><img src="https://avatars.githubusercontent.com/u/183991?v=4&s=48" width="48" height="48" alt="ivanrvpereira" title="ivanrvpereira"/></a> <a href="https://github.com/search?q=Jarvis"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Jarvis" title="Jarvis"/></a> <a href="https://github.com/jayhickey"><img src="https://avatars.githubusercontent.com/u/1676460?v=4&s=48" width="48" height="48" alt="jayhickey" title="jayhickey"/></a> <a href="https://github.com/jeffersonwarrior"><img src="https://avatars.githubusercontent.com/u/89030989?v=4&s=48" width="48" height="48" alt="jeffersonwarrior" title="jeffersonwarrior"/></a> <a href="https://github.com/search?q=jeffersonwarrior"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="jeffersonwarrior" title="jeffersonwarrior"/></a> <a href="https://github.com/jverdi"><img src="https://avatars.githubusercontent.com/u/345050?v=4&s=48" width="48" height="48" alt="jverdi" title="jverdi"/></a> <a href="https://github.com/longmaba"><img src="https://avatars.githubusercontent.com/u/9361500?v=4&s=48" width="48" height="48" alt="longmaba" title="longmaba"/></a> <a href="https://github.com/MarvinCui"><img src="https://avatars.githubusercontent.com/u/130876763?v=4&s=48" width="48" height="48" alt="MarvinCui" title="MarvinCui"/></a> <a href="https://github.com/mjrussell"><img src="https://avatars.githubusercontent.com/u/1641895?v=4&s=48" width="48" height="48" alt="mjrussell" title="mjrussell"/></a> <a href="https://github.com/odnxe"><img src="https://avatars.githubusercontent.com/u/403141?v=4&s=48" width="48" height="48" alt="odnxe" title="odnxe"/></a>
|
<a href="https://github.com/larlyssa"><img src="https://avatars.githubusercontent.com/u/13128869?v=4&s=48" width="48" height="48" alt="larlyssa" title="larlyssa"/></a> <a href="https://github.com/Lukavyi"><img src="https://avatars.githubusercontent.com/u/1013690?v=4&s=48" width="48" height="48" alt="Lukavyi" title="Lukavyi"/></a> <a href="https://github.com/mitsuhiko"><img src="https://avatars.githubusercontent.com/u/7396?v=4&s=48" width="48" height="48" alt="mitsuhiko" title="mitsuhiko"/></a> <a href="https://github.com/odysseus0"><img src="https://avatars.githubusercontent.com/u/8635094?v=4&s=48" width="48" height="48" alt="odysseus0" title="odysseus0"/></a> <a href="https://github.com/oswalpalash"><img src="https://avatars.githubusercontent.com/u/6431196?v=4&s=48" width="48" height="48" alt="oswalpalash" title="oswalpalash"/></a> <a href="https://github.com/pcty-nextgen-service-account"><img src="https://avatars.githubusercontent.com/u/112553441?v=4&s=48" width="48" height="48" alt="pcty-nextgen-service-account" title="pcty-nextgen-service-account"/></a> <a href="https://github.com/pi0"><img src="https://avatars.githubusercontent.com/u/5158436?v=4&s=48" width="48" height="48" alt="pi0" title="pi0"/></a> <a href="https://github.com/rmorse"><img src="https://avatars.githubusercontent.com/u/853547?v=4&s=48" width="48" height="48" alt="rmorse" title="rmorse"/></a> <a href="https://github.com/search?q=Roopak%20Nijhara"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Roopak Nijhara" title="Roopak Nijhara"/></a> <a href="https://github.com/Syhids"><img src="https://avatars.githubusercontent.com/u/671202?v=4&s=48" width="48" height="48" alt="Syhids" title="Syhids"/></a>
|
||||||
<a href="https://github.com/optimikelabs"><img src="https://avatars.githubusercontent.com/u/31423109?v=4&s=48" width="48" height="48" alt="optimikelabs" title="optimikelabs"/></a> <a href="https://github.com/p6l-richard"><img src="https://avatars.githubusercontent.com/u/18185649?v=4&s=48" width="48" height="48" alt="p6l-richard" title="p6l-richard"/></a> <a href="https://github.com/philipp-spiess"><img src="https://avatars.githubusercontent.com/u/458591?v=4&s=48" width="48" height="48" alt="philipp-spiess" title="philipp-spiess"/></a> <a href="https://github.com/search?q=Pocket%20Clawd"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Pocket Clawd" title="Pocket Clawd"/></a> <a href="https://github.com/robaxelsen"><img src="https://avatars.githubusercontent.com/u/13132899?v=4&s=48" width="48" height="48" alt="robaxelsen" title="robaxelsen"/></a> <a href="https://github.com/search?q=Sash%20Catanzarite"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Sash Catanzarite" title="Sash Catanzarite"/></a> <a href="https://github.com/Suksham-sharma"><img src="https://avatars.githubusercontent.com/u/94667656?v=4&s=48" width="48" height="48" alt="Suksham-sharma" title="Suksham-sharma"/></a> <a href="https://github.com/T5-AndyML"><img src="https://avatars.githubusercontent.com/u/22801233?v=4&s=48" width="48" height="48" alt="T5-AndyML" title="T5-AndyML"/></a> <a href="https://github.com/tewatia"><img src="https://avatars.githubusercontent.com/u/22875334?v=4&s=48" width="48" height="48" alt="tewatia" title="tewatia"/></a> <a href="https://github.com/travisp"><img src="https://avatars.githubusercontent.com/u/165698?v=4&s=48" width="48" height="48" alt="travisp" title="travisp"/></a>
|
<a href="https://github.com/search?q=Ubuntu"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Ubuntu" title="Ubuntu"/></a> <a href="https://github.com/search?q=xiaose"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="xiaose" title="xiaose"/></a> <a href="https://github.com/search?q=Aaron%20Konyer"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Aaron Konyer" title="Aaron Konyer"/></a> <a href="https://github.com/aaronveklabs"><img src="https://avatars.githubusercontent.com/u/225997828?v=4&s=48" width="48" height="48" alt="aaronveklabs" title="aaronveklabs"/></a> <a href="https://github.com/andreabadesso"><img src="https://avatars.githubusercontent.com/u/3586068?v=4&s=48" width="48" height="48" alt="andreabadesso" title="andreabadesso"/></a> <a href="https://github.com/search?q=Andrii"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Andrii" title="Andrii"/></a> <a href="https://github.com/cash-echo-bot"><img src="https://avatars.githubusercontent.com/u/252747386?v=4&s=48" width="48" height="48" alt="cash-echo-bot" title="cash-echo-bot"/></a> <a href="https://github.com/search?q=Clawd"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Clawd" title="Clawd"/></a> <a href="https://github.com/search?q=ClawdFx"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="ClawdFx" title="ClawdFx"/></a> <a href="https://github.com/danballance"><img src="https://avatars.githubusercontent.com/u/13839912?v=4&s=48" width="48" height="48" alt="danballance" title="danballance"/></a>
|
||||||
<a href="https://github.com/search?q=VAC"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="VAC" title="VAC"/></a> <a href="https://github.com/search?q=william%20arzt"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="william arzt" title="william arzt"/></a> <a href="https://github.com/zknicker"><img src="https://avatars.githubusercontent.com/u/1164085?v=4&s=48" width="48" height="48" alt="zknicker" title="zknicker"/></a> <a href="https://github.com/0oAstro"><img src="https://avatars.githubusercontent.com/u/79555780?v=4&s=48" width="48" height="48" alt="0oAstro" title="0oAstro"/></a> <a href="https://github.com/abhaymundhara"><img src="https://avatars.githubusercontent.com/u/62872231?v=4&s=48" width="48" height="48" alt="abhaymundhara" title="abhaymundhara"/></a> <a href="https://github.com/aduk059"><img src="https://avatars.githubusercontent.com/u/257603478?v=4&s=48" width="48" height="48" alt="aduk059" title="aduk059"/></a> <a href="https://github.com/search?q=alejandro%20maza"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="alejandro maza" title="alejandro maza"/></a> <a href="https://github.com/Alex-Alaniz"><img src="https://avatars.githubusercontent.com/u/88956822?v=4&s=48" width="48" height="48" alt="Alex-Alaniz" title="Alex-Alaniz"/></a> <a href="https://github.com/alexstyl"><img src="https://avatars.githubusercontent.com/u/1665273?v=4&s=48" width="48" height="48" alt="alexstyl" title="alexstyl"/></a> <a href="https://github.com/andrewting19"><img src="https://avatars.githubusercontent.com/u/10536704?v=4&s=48" width="48" height="48" alt="andrewting19" title="andrewting19"/></a>
|
<a href="https://github.com/EnzeD"><img src="https://avatars.githubusercontent.com/u/9866900?v=4&s=48" width="48" height="48" alt="EnzeD" title="EnzeD"/></a> <a href="https://github.com/erik-agens"><img src="https://avatars.githubusercontent.com/u/80908960?v=4&s=48" width="48" height="48" alt="erik-agens" title="erik-agens"/></a> <a href="https://github.com/Evizero"><img src="https://avatars.githubusercontent.com/u/10854026?v=4&s=48" width="48" height="48" alt="Evizero" title="Evizero"/></a> <a href="https://github.com/fcatuhe"><img src="https://avatars.githubusercontent.com/u/17382215?v=4&s=48" width="48" height="48" alt="fcatuhe" title="fcatuhe"/></a> <a href="https://github.com/itsjaydesu"><img src="https://avatars.githubusercontent.com/u/220390?v=4&s=48" width="48" height="48" alt="itsjaydesu" title="itsjaydesu"/></a> <a href="https://github.com/ivancasco"><img src="https://avatars.githubusercontent.com/u/2452858?v=4&s=48" width="48" height="48" alt="ivancasco" title="ivancasco"/></a> <a href="https://github.com/ivanrvpereira"><img src="https://avatars.githubusercontent.com/u/183991?v=4&s=48" width="48" height="48" alt="ivanrvpereira" title="ivanrvpereira"/></a> <a href="https://github.com/search?q=Jarvis"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Jarvis" title="Jarvis"/></a> <a href="https://github.com/jayhickey"><img src="https://avatars.githubusercontent.com/u/1676460?v=4&s=48" width="48" height="48" alt="jayhickey" title="jayhickey"/></a> <a href="https://github.com/jeffersonwarrior"><img src="https://avatars.githubusercontent.com/u/89030989?v=4&s=48" width="48" height="48" alt="jeffersonwarrior" title="jeffersonwarrior"/></a>
|
||||||
<a href="https://github.com/anpoirier"><img src="https://avatars.githubusercontent.com/u/1245729?v=4&s=48" width="48" height="48" alt="anpoirier" title="anpoirier"/></a> <a href="https://github.com/araa47"><img src="https://avatars.githubusercontent.com/u/22760261?v=4&s=48" width="48" height="48" alt="araa47" title="araa47"/></a> <a href="https://github.com/arthyn"><img src="https://avatars.githubusercontent.com/u/5466421?v=4&s=48" width="48" height="48" alt="arthyn" title="arthyn"/></a> <a href="https://github.com/Asleep123"><img src="https://avatars.githubusercontent.com/u/122379135?v=4&s=48" width="48" height="48" alt="Asleep123" title="Asleep123"/></a> <a href="https://github.com/bguidolim"><img src="https://avatars.githubusercontent.com/u/987360?v=4&s=48" width="48" height="48" alt="bguidolim" title="bguidolim"/></a> <a href="https://github.com/bolismauro"><img src="https://avatars.githubusercontent.com/u/771999?v=4&s=48" width="48" height="48" alt="bolismauro" title="bolismauro"/></a> <a href="https://github.com/chenyuan99"><img src="https://avatars.githubusercontent.com/u/25518100?v=4&s=48" width="48" height="48" alt="chenyuan99" title="chenyuan99"/></a> <a href="https://github.com/Chloe-VP"><img src="https://avatars.githubusercontent.com/u/257371598?v=4&s=48" width="48" height="48" alt="Chloe-VP" title="Chloe-VP"/></a> <a href="https://github.com/conhecendoia"><img src="https://avatars.githubusercontent.com/u/82890727?v=4&s=48" width="48" height="48" alt="conhecendoia" title="conhecendoia"/></a> <a href="https://github.com/dasilva333"><img src="https://avatars.githubusercontent.com/u/947827?v=4&s=48" width="48" height="48" alt="dasilva333" title="dasilva333"/></a>
|
<a href="https://github.com/search?q=jeffersonwarrior"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="jeffersonwarrior" title="jeffersonwarrior"/></a> <a href="https://github.com/jverdi"><img src="https://avatars.githubusercontent.com/u/345050?v=4&s=48" width="48" height="48" alt="jverdi" title="jverdi"/></a> <a href="https://github.com/longmaba"><img src="https://avatars.githubusercontent.com/u/9361500?v=4&s=48" width="48" height="48" alt="longmaba" title="longmaba"/></a> <a href="https://github.com/MarvinCui"><img src="https://avatars.githubusercontent.com/u/130876763?v=4&s=48" width="48" height="48" alt="MarvinCui" title="MarvinCui"/></a> <a href="https://github.com/mjrussell"><img src="https://avatars.githubusercontent.com/u/1641895?v=4&s=48" width="48" height="48" alt="mjrussell" title="mjrussell"/></a> <a href="https://github.com/odnxe"><img src="https://avatars.githubusercontent.com/u/403141?v=4&s=48" width="48" height="48" alt="odnxe" title="odnxe"/></a> <a href="https://github.com/optimikelabs"><img src="https://avatars.githubusercontent.com/u/31423109?v=4&s=48" width="48" height="48" alt="optimikelabs" title="optimikelabs"/></a> <a href="https://github.com/p6l-richard"><img src="https://avatars.githubusercontent.com/u/18185649?v=4&s=48" width="48" height="48" alt="p6l-richard" title="p6l-richard"/></a> <a href="https://github.com/philipp-spiess"><img src="https://avatars.githubusercontent.com/u/458591?v=4&s=48" width="48" height="48" alt="philipp-spiess" title="philipp-spiess"/></a> <a href="https://github.com/search?q=Pocket%20Clawd"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Pocket Clawd" title="Pocket Clawd"/></a>
|
||||||
<a href="https://github.com/David-Marsh-Photo"><img src="https://avatars.githubusercontent.com/u/228404527?v=4&s=48" width="48" height="48" alt="David-Marsh-Photo" title="David-Marsh-Photo"/></a> <a href="https://github.com/search?q=Developer"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Developer" title="Developer"/></a> <a href="https://github.com/search?q=Dimitrios%20Ploutarchos"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Dimitrios Ploutarchos" title="Dimitrios Ploutarchos"/></a> <a href="https://github.com/search?q=Drake%20Thomsen"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Drake Thomsen" title="Drake Thomsen"/></a> <a href="https://github.com/dylanneve1"><img src="https://avatars.githubusercontent.com/u/31746704?v=4&s=48" width="48" height="48" alt="dylanneve1" title="dylanneve1"/></a> <a href="https://github.com/search?q=Felix%20Krause"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Felix Krause" title="Felix Krause"/></a> <a href="https://github.com/foeken"><img src="https://avatars.githubusercontent.com/u/13864?v=4&s=48" width="48" height="48" alt="foeken" title="foeken"/></a> <a href="https://github.com/frankekn"><img src="https://avatars.githubusercontent.com/u/4488090?v=4&s=48" width="48" height="48" alt="frankekn" title="frankekn"/></a> <a href="https://github.com/search?q=ganghyun%20kim"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="ganghyun kim" title="ganghyun kim"/></a> <a href="https://github.com/grrowl"><img src="https://avatars.githubusercontent.com/u/907140?v=4&s=48" width="48" height="48" alt="grrowl" title="grrowl"/></a>
|
<a href="https://github.com/robaxelsen"><img src="https://avatars.githubusercontent.com/u/13132899?v=4&s=48" width="48" height="48" alt="robaxelsen" title="robaxelsen"/></a> <a href="https://github.com/search?q=Sash%20Catanzarite"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Sash Catanzarite" title="Sash Catanzarite"/></a> <a href="https://github.com/Suksham-sharma"><img src="https://avatars.githubusercontent.com/u/94667656?v=4&s=48" width="48" height="48" alt="Suksham-sharma" title="Suksham-sharma"/></a> <a href="https://github.com/T5-AndyML"><img src="https://avatars.githubusercontent.com/u/22801233?v=4&s=48" width="48" height="48" alt="T5-AndyML" title="T5-AndyML"/></a> <a href="https://github.com/tewatia"><img src="https://avatars.githubusercontent.com/u/22875334?v=4&s=48" width="48" height="48" alt="tewatia" title="tewatia"/></a> <a href="https://github.com/thejhinvirtuoso"><img src="https://avatars.githubusercontent.com/u/258521837?v=4&s=48" width="48" height="48" alt="thejhinvirtuoso" title="thejhinvirtuoso"/></a> <a href="https://github.com/travisp"><img src="https://avatars.githubusercontent.com/u/165698?v=4&s=48" width="48" height="48" alt="travisp" title="travisp"/></a> <a href="https://github.com/search?q=VAC"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="VAC" title="VAC"/></a> <a href="https://github.com/search?q=william%20arzt"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="william arzt" title="william arzt"/></a> <a href="https://github.com/zknicker"><img src="https://avatars.githubusercontent.com/u/1164085?v=4&s=48" width="48" height="48" alt="zknicker" title="zknicker"/></a>
|
||||||
<a href="https://github.com/gtsifrikas"><img src="https://avatars.githubusercontent.com/u/8904378?v=4&s=48" width="48" height="48" alt="gtsifrikas" title="gtsifrikas"/></a> <a href="https://github.com/HazAT"><img src="https://avatars.githubusercontent.com/u/363802?v=4&s=48" width="48" height="48" alt="HazAT" title="HazAT"/></a> <a href="https://github.com/hrdwdmrbl"><img src="https://avatars.githubusercontent.com/u/554881?v=4&s=48" width="48" height="48" alt="hrdwdmrbl" title="hrdwdmrbl"/></a> <a href="https://github.com/hugobarauna"><img src="https://avatars.githubusercontent.com/u/2719?v=4&s=48" width="48" height="48" alt="hugobarauna" title="hugobarauna"/></a> <a href="https://github.com/search?q=Jamie%20Openshaw"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Jamie Openshaw" title="Jamie Openshaw"/></a> <a href="https://github.com/search?q=Jane"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Jane" title="Jane"/></a> <a href="https://github.com/search?q=Jarvis%20Deploy"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Jarvis Deploy" title="Jarvis Deploy"/></a> <a href="https://github.com/search?q=Jefferson%20Nunn"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Jefferson Nunn" title="Jefferson Nunn"/></a> <a href="https://github.com/jogi47"><img src="https://avatars.githubusercontent.com/u/1710139?v=4&s=48" width="48" height="48" alt="jogi47" title="jogi47"/></a> <a href="https://github.com/kentaro"><img src="https://avatars.githubusercontent.com/u/3458?v=4&s=48" width="48" height="48" alt="kentaro" title="kentaro"/></a>
|
<a href="https://github.com/0oAstro"><img src="https://avatars.githubusercontent.com/u/79555780?v=4&s=48" width="48" height="48" alt="0oAstro" title="0oAstro"/></a> <a href="https://github.com/abhaymundhara"><img src="https://avatars.githubusercontent.com/u/62872231?v=4&s=48" width="48" height="48" alt="abhaymundhara" title="abhaymundhara"/></a> <a href="https://github.com/aduk059"><img src="https://avatars.githubusercontent.com/u/257603478?v=4&s=48" width="48" height="48" alt="aduk059" title="aduk059"/></a> <a href="https://github.com/aldoeliacim"><img src="https://avatars.githubusercontent.com/u/17973757?v=4&s=48" width="48" height="48" alt="aldoeliacim" title="aldoeliacim"/></a> <a href="https://github.com/search?q=alejandro%20maza"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="alejandro maza" title="alejandro maza"/></a> <a href="https://github.com/Alex-Alaniz"><img src="https://avatars.githubusercontent.com/u/88956822?v=4&s=48" width="48" height="48" alt="Alex-Alaniz" title="Alex-Alaniz"/></a> <a href="https://github.com/alexanderatallah"><img src="https://avatars.githubusercontent.com/u/1011391?v=4&s=48" width="48" height="48" alt="alexanderatallah" title="alexanderatallah"/></a> <a href="https://github.com/alexstyl"><img src="https://avatars.githubusercontent.com/u/1665273?v=4&s=48" width="48" height="48" alt="alexstyl" title="alexstyl"/></a> <a href="https://github.com/andrewting19"><img src="https://avatars.githubusercontent.com/u/10536704?v=4&s=48" width="48" height="48" alt="andrewting19" title="andrewting19"/></a> <a href="https://github.com/anpoirier"><img src="https://avatars.githubusercontent.com/u/1245729?v=4&s=48" width="48" height="48" alt="anpoirier" title="anpoirier"/></a>
|
||||||
<a href="https://github.com/search?q=Kevin%20Lin"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Kevin Lin" title="Kevin Lin"/></a> <a href="https://github.com/kira-ariaki"><img src="https://avatars.githubusercontent.com/u/257352493?v=4&s=48" width="48" height="48" alt="kira-ariaki" title="kira-ariaki"/></a> <a href="https://github.com/kitze"><img src="https://avatars.githubusercontent.com/u/1160594?v=4&s=48" width="48" height="48" alt="kitze" title="kitze"/></a> <a href="https://github.com/Kiwitwitter"><img src="https://avatars.githubusercontent.com/u/25277769?v=4&s=48" width="48" height="48" alt="Kiwitwitter" title="Kiwitwitter"/></a> <a href="https://github.com/levifig"><img src="https://avatars.githubusercontent.com/u/1605?v=4&s=48" width="48" height="48" alt="levifig" title="levifig"/></a> <a href="https://github.com/search?q=Lloyd"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Lloyd" title="Lloyd"/></a> <a href="https://github.com/longjos"><img src="https://avatars.githubusercontent.com/u/740160?v=4&s=48" width="48" height="48" alt="longjos" title="longjos"/></a> <a href="https://github.com/loukotal"><img src="https://avatars.githubusercontent.com/u/18210858?v=4&s=48" width="48" height="48" alt="loukotal" title="loukotal"/></a> <a href="https://github.com/louzhixian"><img src="https://avatars.githubusercontent.com/u/7994361?v=4&s=48" width="48" height="48" alt="louzhixian" title="louzhixian"/></a> <a href="https://github.com/martinpucik"><img src="https://avatars.githubusercontent.com/u/5503097?v=4&s=48" width="48" height="48" alt="martinpucik" title="martinpucik"/></a>
|
<a href="https://github.com/araa47"><img src="https://avatars.githubusercontent.com/u/22760261?v=4&s=48" width="48" height="48" alt="araa47" title="araa47"/></a> <a href="https://github.com/arthyn"><img src="https://avatars.githubusercontent.com/u/5466421?v=4&s=48" width="48" height="48" alt="arthyn" title="arthyn"/></a> <a href="https://github.com/Asleep123"><img src="https://avatars.githubusercontent.com/u/122379135?v=4&s=48" width="48" height="48" alt="Asleep123" title="Asleep123"/></a> <a href="https://github.com/search?q=Ayush%20Ojha"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Ayush Ojha" title="Ayush Ojha"/></a> <a href="https://github.com/Ayush10"><img src="https://avatars.githubusercontent.com/u/7945279?v=4&s=48" width="48" height="48" alt="Ayush10" title="Ayush10"/></a> <a href="https://github.com/bguidolim"><img src="https://avatars.githubusercontent.com/u/987360?v=4&s=48" width="48" height="48" alt="bguidolim" title="bguidolim"/></a> <a href="https://github.com/bolismauro"><img src="https://avatars.githubusercontent.com/u/771999?v=4&s=48" width="48" height="48" alt="bolismauro" title="bolismauro"/></a> <a href="https://github.com/championswimmer"><img src="https://avatars.githubusercontent.com/u/1327050?v=4&s=48" width="48" height="48" alt="championswimmer" title="championswimmer"/></a> <a href="https://github.com/chenyuan99"><img src="https://avatars.githubusercontent.com/u/25518100?v=4&s=48" width="48" height="48" alt="chenyuan99" title="chenyuan99"/></a> <a href="https://github.com/Chloe-VP"><img src="https://avatars.githubusercontent.com/u/257371598?v=4&s=48" width="48" height="48" alt="Chloe-VP" title="Chloe-VP"/></a>
|
||||||
<a href="https://github.com/search?q=Matt%20mini"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Matt mini" title="Matt mini"/></a> <a href="https://github.com/mertcicekci0"><img src="https://avatars.githubusercontent.com/u/179321902?v=4&s=48" width="48" height="48" alt="mertcicekci0" title="mertcicekci0"/></a> <a href="https://github.com/search?q=Miles"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Miles" title="Miles"/></a> <a href="https://github.com/mrdbstn"><img src="https://avatars.githubusercontent.com/u/58957632?v=4&s=48" width="48" height="48" alt="mrdbstn" title="mrdbstn"/></a> <a href="https://github.com/MSch"><img src="https://avatars.githubusercontent.com/u/7475?v=4&s=48" width="48" height="48" alt="MSch" title="MSch"/></a> <a href="https://github.com/search?q=Mustafa%20Tag%20Eldeen"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Mustafa Tag Eldeen" title="Mustafa Tag Eldeen"/></a> <a href="https://github.com/mylukin"><img src="https://avatars.githubusercontent.com/u/1021019?v=4&s=48" width="48" height="48" alt="mylukin" title="mylukin"/></a> <a href="https://github.com/nathanbosse"><img src="https://avatars.githubusercontent.com/u/4040669?v=4&s=48" width="48" height="48" alt="nathanbosse" title="nathanbosse"/></a> <a href="https://github.com/ndraiman"><img src="https://avatars.githubusercontent.com/u/12609607?v=4&s=48" width="48" height="48" alt="ndraiman" title="ndraiman"/></a> <a href="https://github.com/nexty5870"><img src="https://avatars.githubusercontent.com/u/3869659?v=4&s=48" width="48" height="48" alt="nexty5870" title="nexty5870"/></a>
|
<a href="https://github.com/search?q=Clawdbot%20Maintainers"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Clawdbot Maintainers" title="Clawdbot Maintainers"/></a> <a href="https://github.com/conhecendoia"><img src="https://avatars.githubusercontent.com/u/82890727?v=4&s=48" width="48" height="48" alt="conhecendoia" title="conhecendoia"/></a> <a href="https://github.com/dasilva333"><img src="https://avatars.githubusercontent.com/u/947827?v=4&s=48" width="48" height="48" alt="dasilva333" title="dasilva333"/></a> <a href="https://github.com/David-Marsh-Photo"><img src="https://avatars.githubusercontent.com/u/228404527?v=4&s=48" width="48" height="48" alt="David-Marsh-Photo" title="David-Marsh-Photo"/></a> <a href="https://github.com/search?q=Developer"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Developer" title="Developer"/></a> <a href="https://github.com/search?q=Dimitrios%20Ploutarchos"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Dimitrios Ploutarchos" title="Dimitrios Ploutarchos"/></a> <a href="https://github.com/search?q=Drake%20Thomsen"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Drake Thomsen" title="Drake Thomsen"/></a> <a href="https://github.com/dylanneve1"><img src="https://avatars.githubusercontent.com/u/31746704?v=4&s=48" width="48" height="48" alt="dylanneve1" title="dylanneve1"/></a> <a href="https://github.com/search?q=Felix%20Krause"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Felix Krause" title="Felix Krause"/></a> <a href="https://github.com/foeken"><img src="https://avatars.githubusercontent.com/u/13864?v=4&s=48" width="48" height="48" alt="foeken" title="foeken"/></a>
|
||||||
<a href="https://github.com/Noctivoro"><img src="https://avatars.githubusercontent.com/u/183974570?v=4&s=48" width="48" height="48" alt="Noctivoro" title="Noctivoro"/></a> <a href="https://github.com/ppamment"><img src="https://avatars.githubusercontent.com/u/2122919?v=4&s=48" width="48" height="48" alt="ppamment" title="ppamment"/></a> <a href="https://github.com/prathamdby"><img src="https://avatars.githubusercontent.com/u/134331217?v=4&s=48" width="48" height="48" alt="prathamdby" title="prathamdby"/></a> <a href="https://github.com/ptn1411"><img src="https://avatars.githubusercontent.com/u/57529765?v=4&s=48" width="48" height="48" alt="ptn1411" title="ptn1411"/></a> <a href="https://github.com/reeltimeapps"><img src="https://avatars.githubusercontent.com/u/637338?v=4&s=48" width="48" height="48" alt="reeltimeapps" title="reeltimeapps"/></a> <a href="https://github.com/RLTCmpe"><img src="https://avatars.githubusercontent.com/u/10762242?v=4&s=48" width="48" height="48" alt="RLTCmpe" title="RLTCmpe"/></a> <a href="https://github.com/search?q=Rolf%20Fredheim"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Rolf Fredheim" title="Rolf Fredheim"/></a> <a href="https://github.com/search?q=Rony%20Kelner"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Rony Kelner" title="Rony Kelner"/></a> <a href="https://github.com/search?q=Samrat%20Jha"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Samrat Jha" title="Samrat Jha"/></a> <a href="https://github.com/senoldogann"><img src="https://avatars.githubusercontent.com/u/45736551?v=4&s=48" width="48" height="48" alt="senoldogann" title="senoldogann"/></a>
|
<a href="https://github.com/frankekn"><img src="https://avatars.githubusercontent.com/u/4488090?v=4&s=48" width="48" height="48" alt="frankekn" title="frankekn"/></a> <a href="https://github.com/fredheir"><img src="https://avatars.githubusercontent.com/u/3304869?v=4&s=48" width="48" height="48" alt="fredheir" title="fredheir"/></a> <a href="https://github.com/search?q=ganghyun%20kim"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="ganghyun kim" title="ganghyun kim"/></a> <a href="https://github.com/grrowl"><img src="https://avatars.githubusercontent.com/u/907140?v=4&s=48" width="48" height="48" alt="grrowl" title="grrowl"/></a> <a href="https://github.com/gtsifrikas"><img src="https://avatars.githubusercontent.com/u/8904378?v=4&s=48" width="48" height="48" alt="gtsifrikas" title="gtsifrikas"/></a> <a href="https://github.com/HassanFleyah"><img src="https://avatars.githubusercontent.com/u/228002017?v=4&s=48" width="48" height="48" alt="HassanFleyah" title="HassanFleyah"/></a> <a href="https://github.com/HazAT"><img src="https://avatars.githubusercontent.com/u/363802?v=4&s=48" width="48" height="48" alt="HazAT" title="HazAT"/></a> <a href="https://github.com/hclsys"><img src="https://avatars.githubusercontent.com/u/7755017?v=4&s=48" width="48" height="48" alt="hclsys" title="hclsys"/></a> <a href="https://github.com/hrdwdmrbl"><img src="https://avatars.githubusercontent.com/u/554881?v=4&s=48" width="48" height="48" alt="hrdwdmrbl" title="hrdwdmrbl"/></a> <a href="https://github.com/hugobarauna"><img src="https://avatars.githubusercontent.com/u/2719?v=4&s=48" width="48" height="48" alt="hugobarauna" title="hugobarauna"/></a>
|
||||||
<a href="https://github.com/Seredeep"><img src="https://avatars.githubusercontent.com/u/22802816?v=4&s=48" width="48" height="48" alt="Seredeep" title="Seredeep"/></a> <a href="https://github.com/sergical"><img src="https://avatars.githubusercontent.com/u/3760543?v=4&s=48" width="48" height="48" alt="sergical" title="sergical"/></a> <a href="https://github.com/shiv19"><img src="https://avatars.githubusercontent.com/u/9407019?v=4&s=48" width="48" height="48" alt="shiv19" title="shiv19"/></a> <a href="https://github.com/shiyuanhai"><img src="https://avatars.githubusercontent.com/u/1187370?v=4&s=48" width="48" height="48" alt="shiyuanhai" title="shiyuanhai"/></a> <a href="https://github.com/siraht"><img src="https://avatars.githubusercontent.com/u/73152895?v=4&s=48" width="48" height="48" alt="siraht" title="siraht"/></a> <a href="https://github.com/snopoke"><img src="https://avatars.githubusercontent.com/u/249606?v=4&s=48" width="48" height="48" alt="snopoke" title="snopoke"/></a> <a href="https://github.com/spiceoogway"><img src="https://avatars.githubusercontent.com/u/105812383?v=4&s=48" width="48" height="48" alt="spiceoogway" title="spiceoogway"/></a> <a href="https://github.com/search?q=techboss"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="techboss" title="techboss"/></a> <a href="https://github.com/testingabc321"><img src="https://avatars.githubusercontent.com/u/8577388?v=4&s=48" width="48" height="48" alt="testingabc321" title="testingabc321"/></a> <a href="https://github.com/search?q=The%20Admiral"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="The Admiral" title="The Admiral"/></a>
|
<a href="https://github.com/iamEvanYT"><img src="https://avatars.githubusercontent.com/u/47493765?v=4&s=48" width="48" height="48" alt="iamEvanYT" title="iamEvanYT"/></a> <a href="https://github.com/search?q=Jamie%20Openshaw"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Jamie Openshaw" title="Jamie Openshaw"/></a> <a href="https://github.com/search?q=Jane"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Jane" title="Jane"/></a> <a href="https://github.com/search?q=Jarvis%20Deploy"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Jarvis Deploy" title="Jarvis Deploy"/></a> <a href="https://github.com/search?q=Jefferson%20Nunn"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Jefferson Nunn" title="Jefferson Nunn"/></a> <a href="https://github.com/jogi47"><img src="https://avatars.githubusercontent.com/u/1710139?v=4&s=48" width="48" height="48" alt="jogi47" title="jogi47"/></a> <a href="https://github.com/kentaro"><img src="https://avatars.githubusercontent.com/u/3458?v=4&s=48" width="48" height="48" alt="kentaro" title="kentaro"/></a> <a href="https://github.com/search?q=Kevin%20Lin"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Kevin Lin" title="Kevin Lin"/></a> <a href="https://github.com/kira-ariaki"><img src="https://avatars.githubusercontent.com/u/257352493?v=4&s=48" width="48" height="48" alt="kira-ariaki" title="kira-ariaki"/></a> <a href="https://github.com/kitze"><img src="https://avatars.githubusercontent.com/u/1160594?v=4&s=48" width="48" height="48" alt="kitze" title="kitze"/></a>
|
||||||
<a href="https://github.com/thesash"><img src="https://avatars.githubusercontent.com/u/1166151?v=4&s=48" width="48" height="48" alt="thesash" title="thesash"/></a> <a href="https://github.com/search?q=Ubuntu"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Ubuntu" title="Ubuntu"/></a> <a href="https://github.com/search?q=Vibe%20Kanban"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Vibe Kanban" title="Vibe Kanban"/></a> <a href="https://github.com/voidserf"><img src="https://avatars.githubusercontent.com/u/477673?v=4&s=48" width="48" height="48" alt="voidserf" title="voidserf"/></a> <a href="https://github.com/search?q=Vultr-Clawd%20Admin"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Vultr-Clawd Admin" title="Vultr-Clawd Admin"/></a> <a href="https://github.com/search?q=Wimmie"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Wimmie" title="Wimmie"/></a> <a href="https://github.com/search?q=wolfred"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="wolfred" title="wolfred"/></a> <a href="https://github.com/wstock"><img src="https://avatars.githubusercontent.com/u/1394687?v=4&s=48" width="48" height="48" alt="wstock" title="wstock"/></a> <a href="https://github.com/YangHuang2280"><img src="https://avatars.githubusercontent.com/u/201681634?v=4&s=48" width="48" height="48" alt="YangHuang2280" title="YangHuang2280"/></a> <a href="https://github.com/yazinsai"><img src="https://avatars.githubusercontent.com/u/1846034?v=4&s=48" width="48" height="48" alt="yazinsai" title="yazinsai"/></a>
|
<a href="https://github.com/Kiwitwitter"><img src="https://avatars.githubusercontent.com/u/25277769?v=4&s=48" width="48" height="48" alt="Kiwitwitter" title="Kiwitwitter"/></a> <a href="https://github.com/levifig"><img src="https://avatars.githubusercontent.com/u/1605?v=4&s=48" width="48" height="48" alt="levifig" title="levifig"/></a> <a href="https://github.com/search?q=Lloyd"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Lloyd" title="Lloyd"/></a> <a href="https://github.com/loganaden"><img src="https://avatars.githubusercontent.com/u/1688420?v=4&s=48" width="48" height="48" alt="loganaden" title="loganaden"/></a> <a href="https://github.com/longjos"><img src="https://avatars.githubusercontent.com/u/740160?v=4&s=48" width="48" height="48" alt="longjos" title="longjos"/></a> <a href="https://github.com/loukotal"><img src="https://avatars.githubusercontent.com/u/18210858?v=4&s=48" width="48" height="48" alt="loukotal" title="loukotal"/></a> <a href="https://github.com/louzhixian"><img src="https://avatars.githubusercontent.com/u/7994361?v=4&s=48" width="48" height="48" alt="louzhixian" title="louzhixian"/></a> <a href="https://github.com/martinpucik"><img src="https://avatars.githubusercontent.com/u/5503097?v=4&s=48" width="48" height="48" alt="martinpucik" title="martinpucik"/></a> <a href="https://github.com/search?q=Matt%20mini"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Matt mini" title="Matt mini"/></a> <a href="https://github.com/mertcicekci0"><img src="https://avatars.githubusercontent.com/u/179321902?v=4&s=48" width="48" height="48" alt="mertcicekci0" title="mertcicekci0"/></a>
|
||||||
<a href="https://github.com/YiWang24"><img src="https://avatars.githubusercontent.com/u/176262341?v=4&s=48" width="48" height="48" alt="YiWang24" title="YiWang24"/></a> <a href="https://github.com/search?q=ymat19"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="ymat19" title="ymat19"/></a> <a href="https://github.com/search?q=Zach%20Knickerbocker"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Zach Knickerbocker" title="Zach Knickerbocker"/></a> <a href="https://github.com/zackerthescar"><img src="https://avatars.githubusercontent.com/u/38077284?v=4&s=48" width="48" height="48" alt="zackerthescar" title="zackerthescar"/></a> <a href="https://github.com/0xJonHoldsCrypto"><img src="https://avatars.githubusercontent.com/u/81202085?v=4&s=48" width="48" height="48" alt="0xJonHoldsCrypto" title="0xJonHoldsCrypto"/></a> <a href="https://github.com/aaronn"><img src="https://avatars.githubusercontent.com/u/1653630?v=4&s=48" width="48" height="48" alt="aaronn" title="aaronn"/></a> <a href="https://github.com/Alphonse-arianee"><img src="https://avatars.githubusercontent.com/u/254457365?v=4&s=48" width="48" height="48" alt="Alphonse-arianee" title="Alphonse-arianee"/></a> <a href="https://github.com/atalovesyou"><img src="https://avatars.githubusercontent.com/u/3534502?v=4&s=48" width="48" height="48" alt="atalovesyou" title="atalovesyou"/></a> <a href="https://github.com/search?q=Azade"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Azade" title="Azade"/></a> <a href="https://github.com/carlulsoe"><img src="https://avatars.githubusercontent.com/u/34673973?v=4&s=48" width="48" height="48" alt="carlulsoe" title="carlulsoe"/></a>
|
<a href="https://github.com/search?q=Miles"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Miles" title="Miles"/></a> <a href="https://github.com/mrdbstn"><img src="https://avatars.githubusercontent.com/u/58957632?v=4&s=48" width="48" height="48" alt="mrdbstn" title="mrdbstn"/></a> <a href="https://github.com/MSch"><img src="https://avatars.githubusercontent.com/u/7475?v=4&s=48" width="48" height="48" alt="MSch" title="MSch"/></a> <a href="https://github.com/search?q=Mustafa%20Tag%20Eldeen"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Mustafa Tag Eldeen" title="Mustafa Tag Eldeen"/></a> <a href="https://github.com/mylukin"><img src="https://avatars.githubusercontent.com/u/1021019?v=4&s=48" width="48" height="48" alt="mylukin" title="mylukin"/></a> <a href="https://github.com/nathanbosse"><img src="https://avatars.githubusercontent.com/u/4040669?v=4&s=48" width="48" height="48" alt="nathanbosse" title="nathanbosse"/></a> <a href="https://github.com/ndraiman"><img src="https://avatars.githubusercontent.com/u/12609607?v=4&s=48" width="48" height="48" alt="ndraiman" title="ndraiman"/></a> <a href="https://github.com/nexty5870"><img src="https://avatars.githubusercontent.com/u/3869659?v=4&s=48" width="48" height="48" alt="nexty5870" title="nexty5870"/></a> <a href="https://github.com/Noctivoro"><img src="https://avatars.githubusercontent.com/u/183974570?v=4&s=48" width="48" height="48" alt="Noctivoro" title="Noctivoro"/></a> <a href="https://github.com/ozgur-polat"><img src="https://avatars.githubusercontent.com/u/26483942?v=4&s=48" width="48" height="48" alt="ozgur-polat" title="ozgur-polat"/></a>
|
||||||
<a href="https://github.com/search?q=ddyo"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="ddyo" title="ddyo"/></a> <a href="https://github.com/search?q=Erik"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Erik" title="Erik"/></a> <a href="https://github.com/latitudeki5223"><img src="https://avatars.githubusercontent.com/u/119656367?v=4&s=48" width="48" height="48" alt="latitudeki5223" title="latitudeki5223"/></a> <a href="https://github.com/search?q=Manuel%20Maly"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Manuel Maly" title="Manuel Maly"/></a> <a href="https://github.com/search?q=Mourad%20Boustani"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Mourad Boustani" title="Mourad Boustani"/></a> <a href="https://github.com/odrobnik"><img src="https://avatars.githubusercontent.com/u/333270?v=4&s=48" width="48" height="48" alt="odrobnik" title="odrobnik"/></a> <a href="https://github.com/pcty-nextgen-ios-builder"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="pcty-nextgen-ios-builder" title="pcty-nextgen-ios-builder"/></a> <a href="https://github.com/search?q=Quentin"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Quentin" title="Quentin"/></a> <a href="https://github.com/search?q=Randy%20Torres"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Randy Torres" title="Randy Torres"/></a> <a href="https://github.com/rhjoh"><img src="https://avatars.githubusercontent.com/u/105699450?v=4&s=48" width="48" height="48" alt="rhjoh" title="rhjoh"/></a>
|
<a href="https://github.com/ppamment"><img src="https://avatars.githubusercontent.com/u/2122919?v=4&s=48" width="48" height="48" alt="ppamment" title="ppamment"/></a> <a href="https://github.com/prathamdby"><img src="https://avatars.githubusercontent.com/u/134331217?v=4&s=48" width="48" height="48" alt="prathamdby" title="prathamdby"/></a> <a href="https://github.com/ptn1411"><img src="https://avatars.githubusercontent.com/u/57529765?v=4&s=48" width="48" height="48" alt="ptn1411" title="ptn1411"/></a> <a href="https://github.com/reeltimeapps"><img src="https://avatars.githubusercontent.com/u/637338?v=4&s=48" width="48" height="48" alt="reeltimeapps" title="reeltimeapps"/></a> <a href="https://github.com/RLTCmpe"><img src="https://avatars.githubusercontent.com/u/10762242?v=4&s=48" width="48" height="48" alt="RLTCmpe" title="RLTCmpe"/></a> <a href="https://github.com/search?q=Rony%20Kelner"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Rony Kelner" title="Rony Kelner"/></a> <a href="https://github.com/ryancnelson"><img src="https://avatars.githubusercontent.com/u/347171?v=4&s=48" width="48" height="48" alt="ryancnelson" title="ryancnelson"/></a> <a href="https://github.com/search?q=Samrat%20Jha"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Samrat Jha" title="Samrat Jha"/></a> <a href="https://github.com/senoldogann"><img src="https://avatars.githubusercontent.com/u/45736551?v=4&s=48" width="48" height="48" alt="senoldogann" title="senoldogann"/></a> <a href="https://github.com/Seredeep"><img src="https://avatars.githubusercontent.com/u/22802816?v=4&s=48" width="48" height="48" alt="Seredeep" title="Seredeep"/></a>
|
||||||
<a href="https://github.com/ronak-guliani"><img src="https://avatars.githubusercontent.com/u/23518228?v=4&s=48" width="48" height="48" alt="ronak-guliani" title="ronak-guliani"/></a> <a href="https://github.com/search?q=William%20Stock"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="William Stock" title="William Stock"/></a>
|
<a href="https://github.com/sergical"><img src="https://avatars.githubusercontent.com/u/3760543?v=4&s=48" width="48" height="48" alt="sergical" title="sergical"/></a> <a href="https://github.com/shiv19"><img src="https://avatars.githubusercontent.com/u/9407019?v=4&s=48" width="48" height="48" alt="shiv19" title="shiv19"/></a> <a href="https://github.com/shiyuanhai"><img src="https://avatars.githubusercontent.com/u/1187370?v=4&s=48" width="48" height="48" alt="shiyuanhai" title="shiyuanhai"/></a> <a href="https://github.com/siraht"><img src="https://avatars.githubusercontent.com/u/73152895?v=4&s=48" width="48" height="48" alt="siraht" title="siraht"/></a> <a href="https://github.com/snopoke"><img src="https://avatars.githubusercontent.com/u/249606?v=4&s=48" width="48" height="48" alt="snopoke" title="snopoke"/></a> <a href="https://github.com/search?q=techboss"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="techboss" title="techboss"/></a> <a href="https://github.com/testingabc321"><img src="https://avatars.githubusercontent.com/u/8577388?v=4&s=48" width="48" height="48" alt="testingabc321" title="testingabc321"/></a> <a href="https://github.com/search?q=The%20Admiral"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="The Admiral" title="The Admiral"/></a> <a href="https://github.com/thesash"><img src="https://avatars.githubusercontent.com/u/1166151?v=4&s=48" width="48" height="48" alt="thesash" title="thesash"/></a> <a href="https://github.com/search?q=Vibe%20Kanban"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Vibe Kanban" title="Vibe Kanban"/></a>
|
||||||
|
<a href="https://github.com/voidserf"><img src="https://avatars.githubusercontent.com/u/477673?v=4&s=48" width="48" height="48" alt="voidserf" title="voidserf"/></a> <a href="https://github.com/search?q=Vultr-Clawd%20Admin"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Vultr-Clawd Admin" title="Vultr-Clawd Admin"/></a> <a href="https://github.com/search?q=Wimmie"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Wimmie" title="Wimmie"/></a> <a href="https://github.com/search?q=wolfred"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="wolfred" title="wolfred"/></a> <a href="https://github.com/wstock"><img src="https://avatars.githubusercontent.com/u/1394687?v=4&s=48" width="48" height="48" alt="wstock" title="wstock"/></a> <a href="https://github.com/YangHuang2280"><img src="https://avatars.githubusercontent.com/u/201681634?v=4&s=48" width="48" height="48" alt="YangHuang2280" title="YangHuang2280"/></a> <a href="https://github.com/yazinsai"><img src="https://avatars.githubusercontent.com/u/1846034?v=4&s=48" width="48" height="48" alt="yazinsai" title="yazinsai"/></a> <a href="https://github.com/yevhen"><img src="https://avatars.githubusercontent.com/u/107726?v=4&s=48" width="48" height="48" alt="yevhen" title="yevhen"/></a> <a href="https://github.com/YiWang24"><img src="https://avatars.githubusercontent.com/u/176262341?v=4&s=48" width="48" height="48" alt="YiWang24" title="YiWang24"/></a> <a href="https://github.com/search?q=ymat19"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="ymat19" title="ymat19"/></a>
|
||||||
|
<a href="https://github.com/search?q=Zach%20Knickerbocker"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Zach Knickerbocker" title="Zach Knickerbocker"/></a> <a href="https://github.com/zackerthescar"><img src="https://avatars.githubusercontent.com/u/38077284?v=4&s=48" width="48" height="48" alt="zackerthescar" title="zackerthescar"/></a> <a href="https://github.com/0xJonHoldsCrypto"><img src="https://avatars.githubusercontent.com/u/81202085?v=4&s=48" width="48" height="48" alt="0xJonHoldsCrypto" title="0xJonHoldsCrypto"/></a> <a href="https://github.com/aaronn"><img src="https://avatars.githubusercontent.com/u/1653630?v=4&s=48" width="48" height="48" alt="aaronn" title="aaronn"/></a> <a href="https://github.com/Alphonse-arianee"><img src="https://avatars.githubusercontent.com/u/254457365?v=4&s=48" width="48" height="48" alt="Alphonse-arianee" title="Alphonse-arianee"/></a> <a href="https://github.com/atalovesyou"><img src="https://avatars.githubusercontent.com/u/3534502?v=4&s=48" width="48" height="48" alt="atalovesyou" title="atalovesyou"/></a> <a href="https://github.com/search?q=Azade"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Azade" title="Azade"/></a> <a href="https://github.com/carlulsoe"><img src="https://avatars.githubusercontent.com/u/34673973?v=4&s=48" width="48" height="48" alt="carlulsoe" title="carlulsoe"/></a> <a href="https://github.com/search?q=ddyo"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="ddyo" title="ddyo"/></a> <a href="https://github.com/search?q=Erik"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Erik" title="Erik"/></a>
|
||||||
|
<a href="https://github.com/latitudeki5223"><img src="https://avatars.githubusercontent.com/u/119656367?v=4&s=48" width="48" height="48" alt="latitudeki5223" title="latitudeki5223"/></a> <a href="https://github.com/search?q=Manuel%20Maly"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Manuel Maly" title="Manuel Maly"/></a> <a href="https://github.com/search?q=Mourad%20Boustani"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Mourad Boustani" title="Mourad Boustani"/></a> <a href="https://github.com/odrobnik"><img src="https://avatars.githubusercontent.com/u/333270?v=4&s=48" width="48" height="48" alt="odrobnik" title="odrobnik"/></a> <a href="https://github.com/pcty-nextgen-ios-builder"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="pcty-nextgen-ios-builder" title="pcty-nextgen-ios-builder"/></a> <a href="https://github.com/search?q=Quentin"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Quentin" title="Quentin"/></a> <a href="https://github.com/search?q=Randy%20Torres"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Randy Torres" title="Randy Torres"/></a> <a href="https://github.com/rhjoh"><img src="https://avatars.githubusercontent.com/u/105699450?v=4&s=48" width="48" height="48" alt="rhjoh" title="rhjoh"/></a> <a href="https://github.com/search?q=Rolf%20Fredheim"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="Rolf Fredheim" title="Rolf Fredheim"/></a> <a href="https://github.com/ronak-guliani"><img src="https://avatars.githubusercontent.com/u/23518228?v=4&s=48" width="48" height="48" alt="ronak-guliani" title="ronak-guliani"/></a>
|
||||||
|
<a href="https://github.com/search?q=William%20Stock"><img src="assets/avatar-placeholder.svg" width="48" height="48" alt="William Stock" title="William Stock"/></a>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ The best way to help the project right now is by sending PRs.
|
|||||||
|
|
||||||
- Public Internet Exposure
|
- Public Internet Exposure
|
||||||
- Using OpenClaw in ways that the docs recommend not to
|
- Using OpenClaw in ways that the docs recommend not to
|
||||||
|
- Prompt injection attacks
|
||||||
|
|
||||||
## Operational Guidance
|
## Operational Guidance
|
||||||
|
|
||||||
|
|||||||
208
appcast.xml
208
appcast.xml
@@ -2,6 +2,117 @@
|
|||||||
<rss xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle" version="2.0">
|
<rss xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle" version="2.0">
|
||||||
<channel>
|
<channel>
|
||||||
<title>OpenClaw</title>
|
<title>OpenClaw</title>
|
||||||
|
<item>
|
||||||
|
<title>2026.2.1</title>
|
||||||
|
<pubDate>Mon, 02 Feb 2026 03:53:03 -0800</pubDate>
|
||||||
|
<link>https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml</link>
|
||||||
|
<sparkle:version>8650</sparkle:version>
|
||||||
|
<sparkle:shortVersionString>2026.2.1</sparkle:shortVersionString>
|
||||||
|
<sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>
|
||||||
|
<description><![CDATA[<h2>OpenClaw 2026.2.1</h2>
|
||||||
|
<h3>Changes</h3>
|
||||||
|
<ul>
|
||||||
|
<li>Docs: onboarding/install/i18n/exec-approvals/Control UI/exe.dev/cacheRetention updates + misc nav/typos. (#3050, #3461, #4064, #4675, #4729, #4763, #5003, #5402, #5446, #5474, #5663, #5689, #5694, #5967, #6270, #6300, #6311, #6416, #6487, #6550, #6789)</li>
|
||||||
|
<li>Telegram: use shared pairing store. (#6127) Thanks @obviyus.</li>
|
||||||
|
<li>Agents: add OpenRouter app attribution headers. Thanks @alexanderatallah.</li>
|
||||||
|
<li>Agents: add system prompt safety guardrails. (#5445) Thanks @joshp123.</li>
|
||||||
|
<li>Agents: update pi-ai to 0.50.9 and rename cacheControlTtl -> cacheRetention (with back-compat mapping).</li>
|
||||||
|
<li>Agents: extend CreateAgentSessionOptions with systemPrompt/skills/contextFiles.</li>
|
||||||
|
<li>Agents: add tool policy conformance snapshot (no runtime behavior change). (#6011)</li>
|
||||||
|
<li>Auth: update MiniMax OAuth hint + portal auth note copy.</li>
|
||||||
|
<li>Discord: inherit thread parent bindings for routing. (#3892) Thanks @aerolalit.</li>
|
||||||
|
<li>Gateway: inject timestamps into agent and chat.send messages. (#3705) Thanks @conroywhitney, @CashWilliams.</li>
|
||||||
|
<li>Gateway: require TLS 1.3 minimum for TLS listeners. (#5970) Thanks @loganaden.</li>
|
||||||
|
<li>Web UI: refine chat layout + extend session active duration.</li>
|
||||||
|
<li>CI: add formal conformance + alias consistency checks. (#5723, #5807)</li>
|
||||||
|
</ul>
|
||||||
|
<h3>Fixes</h3>
|
||||||
|
<ul>
|
||||||
|
<li>Plugins: validate plugin/hook install paths and reject traversal-like names.</li>
|
||||||
|
<li>Telegram: add download timeouts for file fetches. (#6914) Thanks @hclsys.</li>
|
||||||
|
<li>Telegram: enforce thread specs for DM vs forum sends. (#6833) Thanks @obviyus.</li>
|
||||||
|
<li>Streaming: flush block streaming on paragraph boundaries for newline chunking. (#7014)</li>
|
||||||
|
<li>Streaming: stabilize partial streaming filters.</li>
|
||||||
|
<li>Auto-reply: avoid referencing workspace files in /new greeting prompt. (#5706) Thanks @bravostation.</li>
|
||||||
|
<li>Tools: align tool execute adapters/signatures (legacy + parameter order + arg normalization).</li>
|
||||||
|
<li>Tools: treat <code>"*"</code> tool allowlist entries as valid to avoid spurious unknown-entry warnings.</li>
|
||||||
|
<li>Skills: update session-logs paths from .clawdbot to .openclaw. (#4502)</li>
|
||||||
|
<li>Slack: harden media fetch limits and Slack file URL validation. (#6639) Thanks @davidiach.</li>
|
||||||
|
<li>Lint: satisfy curly rule after import sorting. (#6310)</li>
|
||||||
|
<li>Process: resolve Windows <code>spawn()</code> failures for npm-family CLIs by appending <code>.cmd</code> when needed. (#5815) Thanks @thejhinvirtuoso.</li>
|
||||||
|
<li>Discord: resolve PluralKit proxied senders for allowlists and labels. (#5838) Thanks @thewilloftheshadow.</li>
|
||||||
|
<li>Tlon: add timeout to SSE client fetch calls (CWE-400). (#5926)</li>
|
||||||
|
<li>Memory search: L2-normalize local embedding vectors to fix semantic search. (#5332)</li>
|
||||||
|
<li>Agents: align embedded runner + typings with pi-coding-agent API updates (pi 0.51.0).</li>
|
||||||
|
<li>Agents: ensure OpenRouter attribution headers apply in the embedded runner.</li>
|
||||||
|
<li>Agents: cap context window resolution for compaction safeguard. (#6187) Thanks @iamEvanYT.</li>
|
||||||
|
<li>System prompt: resolve overrides and hint using session_status for current date/time. (#1897, #1928, #2108, #3677)</li>
|
||||||
|
<li>Agents: fix Pi prompt template argument syntax. (#6543)</li>
|
||||||
|
<li>Subagents: fix announce failover race (always emit lifecycle end; timeout=0 means no-timeout). (#6621)</li>
|
||||||
|
<li>Teams: gate media auth retries.</li>
|
||||||
|
<li>Telegram: restore draft streaming partials. (#5543) Thanks @obviyus.</li>
|
||||||
|
<li>Onboarding: friendlier Windows onboarding message. (#6242) Thanks @shanselman.</li>
|
||||||
|
<li>TUI: prevent crash when searching with digits in the model selector.</li>
|
||||||
|
<li>Agents: wire before_tool_call plugin hook into tool execution. (#6570, #6660) Thanks @ryancnelson.</li>
|
||||||
|
<li>Browser: secure Chrome extension relay CDP sessions.</li>
|
||||||
|
<li>Docker: use container port for gateway command instead of host port. (#5110) Thanks @mise42.</li>
|
||||||
|
<li>fix(lobster): block arbitrary exec via lobsterPath/cwd injection (GHSA-4mhr-g7xj-cg8j). (#5335) Thanks @vignesh07.</li>
|
||||||
|
<li>Security: sanitize WhatsApp accountId to prevent path traversal. (#4610)</li>
|
||||||
|
<li>Security: restrict MEDIA path extraction to prevent LFI. (#4930)</li>
|
||||||
|
<li>Security: validate message-tool filePath/path against sandbox root. (#6398)</li>
|
||||||
|
<li>Security: block LD*/DYLD* env overrides for host exec. (#4896) Thanks @HassanFleyah.</li>
|
||||||
|
<li>Security: harden web tool content wrapping + file parsing safeguards. (#4058) Thanks @VACInc.</li>
|
||||||
|
<li>Security: enforce Twitch <code>allowFrom</code> allowlist gating (deny non-allowlisted senders). Thanks @MegaManSec.</li>
|
||||||
|
</ul>
|
||||||
|
<p><a href="https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md">View full changelog</a></p>
|
||||||
|
]]></description>
|
||||||
|
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.2.1/OpenClaw-2026.2.1.zip" length="22458919" type="application/octet-stream" sparkle:edSignature="kA/8VQlVdtYphcB1iuFrhWczwWKgkVZMfDfQ7T9WD405D8JKTv5CZ1n8lstIVkpk4xog3UhrfaaoTG8Bf8DMAQ=="/>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<title>2026.1.30</title>
|
||||||
|
<pubDate>Sat, 31 Jan 2026 14:29:57 +0100</pubDate>
|
||||||
|
<link>https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml</link>
|
||||||
|
<sparkle:version>8469</sparkle:version>
|
||||||
|
<sparkle:shortVersionString>2026.1.30</sparkle:shortVersionString>
|
||||||
|
<sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>
|
||||||
|
<description><![CDATA[<h2>OpenClaw 2026.1.30</h2>
|
||||||
|
<h3>Changes</h3>
|
||||||
|
<ul>
|
||||||
|
<li>CLI: add <code>completion</code> command (Zsh/Bash/PowerShell/Fish) and auto-setup during postinstall/onboarding.</li>
|
||||||
|
<li>CLI: add per-agent <code>models status</code> (<code>--agent</code> filter). (#4780) Thanks @jlowin.</li>
|
||||||
|
<li>Agents: add Kimi K2.5 to the synthetic model catalog. (#4407) Thanks @manikv12.</li>
|
||||||
|
<li>Auth: switch Kimi Coding to built-in provider; normalize OAuth profile email.</li>
|
||||||
|
<li>Auth: add MiniMax OAuth plugin + onboarding option. (#4521) Thanks @Maosghoul.</li>
|
||||||
|
<li>Agents: update pi SDK/API usage and dependencies.</li>
|
||||||
|
<li>Web UI: refresh sessions after chat commands and improve session display names.</li>
|
||||||
|
<li>Build: move TypeScript builds to <code>tsdown</code> + <code>tsgo</code> (faster builds, CI typechecks), update tsconfig target, and clean up lint rules.</li>
|
||||||
|
<li>Build: align npm tar override and bin metadata so the <code>openclaw</code> CLI entrypoint is preserved in npm publishes.</li>
|
||||||
|
<li>Docs: add pi/pi-dev docs and update OpenClaw branding + install links.</li>
|
||||||
|
</ul>
|
||||||
|
<h3>Fixes</h3>
|
||||||
|
<ul>
|
||||||
|
<li>Security: restrict local path extraction in media parser to prevent LFI. (#4880)</li>
|
||||||
|
<li>Gateway: prevent token defaults from becoming the literal "undefined". (#4873) Thanks @Hisleren.</li>
|
||||||
|
<li>Control UI: fix assets resolution for npm global installs. (#4909) Thanks @YuriNachos.</li>
|
||||||
|
<li>macOS: avoid stderr pipe backpressure in gateway discovery. (#3304) Thanks @abhijeet117.</li>
|
||||||
|
<li>Telegram: normalize account token lookup for non-normalized IDs. (#5055) Thanks @jasonsschin.</li>
|
||||||
|
<li>Telegram: preserve delivery thread fallback and fix threadId handling in delivery context.</li>
|
||||||
|
<li>Telegram: fix HTML nesting for overlapping styles/links. (#4578) Thanks @ThanhNguyxn.</li>
|
||||||
|
<li>Telegram: accept numeric messageId/chatId in react actions. (#4533) Thanks @Ayush10.</li>
|
||||||
|
<li>Telegram: honor per-account proxy dispatcher via undici fetch. (#4456) Thanks @spiceoogway.</li>
|
||||||
|
<li>Telegram: scope skill commands to bound agent per bot. (#4360) Thanks @robhparker.</li>
|
||||||
|
<li>BlueBubbles: debounce by messageId to preserve attachments in text+image messages. (#4984)</li>
|
||||||
|
<li>Routing: prefer requesterOrigin over stale session entries for sub-agent announce delivery. (#4957)</li>
|
||||||
|
<li>Extensions: restore embedded extension discovery typings.</li>
|
||||||
|
<li>CLI: fix <code>tui:dev</code> port resolution.</li>
|
||||||
|
<li>LINE: fix status command TypeError. (#4651)</li>
|
||||||
|
<li>OAuth: skip expired-token warnings when refresh tokens are still valid. (#4593)</li>
|
||||||
|
<li>Build: skip redundant UI install step in Dockerfile. (#4584) Thanks @obviyus.</li>
|
||||||
|
</ul>
|
||||||
|
<p><a href="https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md">View full changelog</a></p>
|
||||||
|
]]></description>
|
||||||
|
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.1.30/OpenClaw-2026.1.30.zip" length="22458594" type="application/octet-stream" sparkle:edSignature="77/GuEcruKGgu2CJyMq+OVwzaJ2v1VzRQC9NmOirKO3uH5Nn5HaoouwrOHnOanrzlD4OvPW0FS5GH2E4Ntu4CQ=="/>
|
||||||
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<title>2026.1.29</title>
|
<title>2026.1.29</title>
|
||||||
<pubDate>Fri, 30 Jan 2026 06:24:15 +0100</pubDate>
|
<pubDate>Fri, 30 Jan 2026 06:24:15 +0100</pubDate>
|
||||||
@@ -134,100 +245,5 @@ Status: stable.
|
|||||||
]]></description>
|
]]></description>
|
||||||
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.1.29/OpenClaw-2026.1.29.zip" length="22458204" type="application/octet-stream" sparkle:edSignature="HqHwZHQyG/CEfBuQnQ/RffJQPKpSbCVrho9C6rgt93S5ek4AH6hUhB3BBKY8sbX1IVFATKK5QZZNE0YPAf7eBw=="/>
|
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.1.29/OpenClaw-2026.1.29.zip" length="22458204" type="application/octet-stream" sparkle:edSignature="HqHwZHQyG/CEfBuQnQ/RffJQPKpSbCVrho9C6rgt93S5ek4AH6hUhB3BBKY8sbX1IVFATKK5QZZNE0YPAf7eBw=="/>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
|
||||||
<title>2026.1.24-1</title>
|
|
||||||
<pubDate>Sun, 25 Jan 2026 14:05:25 +0000</pubDate>
|
|
||||||
<link>https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml</link>
|
|
||||||
<sparkle:version>7952</sparkle:version>
|
|
||||||
<sparkle:shortVersionString>2026.1.24-1</sparkle:shortVersionString>
|
|
||||||
<sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>
|
|
||||||
<description><![CDATA[<h2>OpenClaw 2026.1.24-1</h2>
|
|
||||||
<h3>Fixes</h3>
|
|
||||||
<ul>
|
|
||||||
<li>Packaging: include dist/shared output in npm tarball (fixes missing reasoning-tags import on install).</li>
|
|
||||||
</ul>
|
|
||||||
<p><a href="https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md">View full changelog</a></p>
|
|
||||||
]]></description>
|
|
||||||
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.1.24-1/OpenClaw-2026.1.24-1.zip" length="12396699" type="application/octet-stream" sparkle:edSignature="VaEdWIgEJBrZLIp2UmigoQ6vaq4P/jNFXpHYXvXHD5MsATS0CqBl6ugyyxRq+/GbpUqmdgdlht4dTUVbLRw6BA=="/>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<title>2026.1.24</title>
|
|
||||||
<pubDate>Sun, 25 Jan 2026 13:31:05 +0000</pubDate>
|
|
||||||
<link>https://raw.githubusercontent.com/openclaw/openclaw/main/appcast.xml</link>
|
|
||||||
<sparkle:version>7944</sparkle:version>
|
|
||||||
<sparkle:shortVersionString>2026.1.24</sparkle:shortVersionString>
|
|
||||||
<sparkle:minimumSystemVersion>15.0</sparkle:minimumSystemVersion>
|
|
||||||
<description><![CDATA[<h2>OpenClaw 2026.1.24</h2>
|
|
||||||
<h3>Highlights</h3>
|
|
||||||
<ul>
|
|
||||||
<li>Providers: Ollama discovery + docs; Venice guide upgrades + cross-links. (#1606) Thanks @abhaymundhara. https://docs.openclaw.ai/providers/ollama https://docs.openclaw.ai/providers/venice</li>
|
|
||||||
<li>Channels: LINE plugin (Messaging API) with rich replies + quick replies. (#1630) Thanks @plum-dawg.</li>
|
|
||||||
<li>TTS: Edge fallback (keyless) + <code>/tts</code> auto modes. (#1668, #1667) Thanks @steipete, @sebslight. https://docs.openclaw.ai/tts</li>
|
|
||||||
<li>Exec approvals: approve in-chat via <code>/approve</code> across all channels (including plugins). (#1621) Thanks @czekaj. https://docs.openclaw.ai/tools/exec-approvals https://docs.openclaw.ai/tools/slash-commands</li>
|
|
||||||
<li>Telegram: DM topics as separate sessions + outbound link preview toggle. (#1597, #1700) Thanks @rohannagpal, @zerone0x. https://docs.openclaw.ai/channels/telegram</li>
|
|
||||||
</ul>
|
|
||||||
<h3>Changes</h3>
|
|
||||||
<ul>
|
|
||||||
<li>Channels: add LINE plugin (Messaging API) with rich replies, quick replies, and plugin HTTP registry. (#1630) Thanks @plum-dawg.</li>
|
|
||||||
<li>TTS: add Edge TTS provider fallback, defaulting to keyless Edge with MP3 retry on format failures. (#1668) Thanks @steipete. https://docs.openclaw.ai/tts</li>
|
|
||||||
<li>TTS: add auto mode enum (off/always/inbound/tagged) with per-session <code>/tts</code> override. (#1667) Thanks @sebslight. https://docs.openclaw.ai/tts</li>
|
|
||||||
<li>Telegram: treat DM topics as separate sessions and keep DM history limits stable with thread suffixes. (#1597) Thanks @rohannagpal.</li>
|
|
||||||
<li>Telegram: add <code>channels.telegram.linkPreview</code> to toggle outbound link previews. (#1700) Thanks @zerone0x. https://docs.openclaw.ai/channels/telegram</li>
|
|
||||||
<li>Web search: add Brave freshness filter parameter for time-scoped results. (#1688) Thanks @JonUleis. https://docs.openclaw.ai/tools/web</li>
|
|
||||||
<li>UI: refresh Control UI dashboard design system (typography, colors, spacing). (#1786) Thanks @mousberg.</li>
|
|
||||||
<li>Exec approvals: forward approval prompts to chat with <code>/approve</code> for all channels (including plugins). (#1621) Thanks @czekaj. https://docs.openclaw.ai/tools/exec-approvals https://docs.openclaw.ai/tools/slash-commands</li>
|
|
||||||
<li>Gateway: expose config.patch in the gateway tool with safe partial updates + restart sentinel. (#1653) Thanks @Glucksberg.</li>
|
|
||||||
<li>Diagnostics: add diagnostic flags for targeted debug logs (config + env override). https://docs.openclaw.ai/diagnostics/flags</li>
|
|
||||||
<li>Docs: expand FAQ (migration, scheduling, concurrency, model recommendations, OpenAI subscription auth, Pi sizing, hackable install, docs SSL workaround).</li>
|
|
||||||
<li>Docs: add verbose installer troubleshooting guidance.</li>
|
|
||||||
<li>Docs: add macOS VM guide with local/hosted options + VPS/nodes guidance. (#1693) Thanks @f-trycua.</li>
|
|
||||||
<li>Docs: add Bedrock EC2 instance role setup + IAM steps. (#1625) Thanks @sergical. https://docs.openclaw.ai/bedrock</li>
|
|
||||||
<li>Docs: update Fly.io guide notes.</li>
|
|
||||||
<li>Dev: add prek pre-commit hooks + dependabot config for weekly updates. (#1720) Thanks @dguido.</li>
|
|
||||||
</ul>
|
|
||||||
<h3>Fixes</h3>
|
|
||||||
<ul>
|
|
||||||
<li>Web UI: fix config/debug layout overflow, scrolling, and code block sizing. (#1715) Thanks @saipreetham589.</li>
|
|
||||||
<li>Web UI: show Stop button during active runs, swap back to New session when idle. (#1664) Thanks @ndbroadbent.</li>
|
|
||||||
<li>Web UI: clear stale disconnect banners on reconnect; allow form saves with unsupported schema paths but block missing schema. (#1707) Thanks @Glucksberg.</li>
|
|
||||||
<li>Web UI: hide internal <code>message_id</code> hints in chat bubbles.</li>
|
|
||||||
<li>Gateway: allow Control UI token-only auth to skip device pairing even when device identity is present (<code>gateway.controlUi.allowInsecureAuth</code>). (#1679) Thanks @steipete.</li>
|
|
||||||
<li>Matrix: decrypt E2EE media attachments with preflight size guard. (#1744) Thanks @araa47.</li>
|
|
||||||
<li>BlueBubbles: route phone-number targets to DMs, avoid leaking routing IDs, and auto-create missing DMs (Private API required). (#1751) Thanks @tyler6204. https://docs.openclaw.ai/channels/bluebubbles</li>
|
|
||||||
<li>BlueBubbles: keep part-index GUIDs in reply tags when short IDs are missing.</li>
|
|
||||||
<li>Signal: repair reaction sends (group/UUID targets + CLI author flags). (#1651) Thanks @vilkasdev.</li>
|
|
||||||
<li>Signal: add configurable signal-cli startup timeout + external daemon mode docs. (#1677) https://docs.openclaw.ai/channels/signal</li>
|
|
||||||
<li>Telegram: set fetch duplex="half" for uploads on Node 22 to avoid sendPhoto failures. (#1684) Thanks @commdata2338.</li>
|
|
||||||
<li>Telegram: use wrapped fetch for long-polling on Node to normalize AbortSignal handling. (#1639)</li>
|
|
||||||
<li>Telegram: honor per-account proxy for outbound API calls. (#1774) Thanks @radek-paclt.</li>
|
|
||||||
<li>Telegram: fall back to text when voice notes are blocked by privacy settings. (#1725) Thanks @foeken.</li>
|
|
||||||
<li>Voice Call: return stream TwiML for outbound conversation calls on initial Twilio webhook. (#1634)</li>
|
|
||||||
<li>Voice Call: serialize Twilio TTS playback and cancel on barge-in to prevent overlap. (#1713) Thanks @dguido.</li>
|
|
||||||
<li>Google Chat: tighten email allowlist matching, typing cleanup, media caps, and onboarding/docs/tests. (#1635) Thanks @iHildy.</li>
|
|
||||||
<li>Google Chat: normalize space targets without double <code>spaces/</code> prefix.</li>
|
|
||||||
<li>Agents: auto-compact on context overflow prompt errors before failing. (#1627) Thanks @rodrigouroz.</li>
|
|
||||||
<li>Agents: use the active auth profile for auto-compaction recovery.</li>
|
|
||||||
<li>Media understanding: skip image understanding when the primary model already supports vision. (#1747) Thanks @tyler6204.</li>
|
|
||||||
<li>Models: default missing custom provider fields so minimal configs are accepted.</li>
|
|
||||||
<li>Messaging: keep newline chunking safe for fenced markdown blocks across channels.</li>
|
|
||||||
<li>TUI: reload history after gateway reconnect to restore session state. (#1663)</li>
|
|
||||||
<li>Heartbeat: normalize target identifiers for consistent routing.</li>
|
|
||||||
<li>Exec: keep approvals for elevated ask unless full mode. (#1616) Thanks @ivancasco.</li>
|
|
||||||
<li>Exec: treat Windows platform labels as Windows for node shell selection. (#1760) Thanks @ymat19.</li>
|
|
||||||
<li>Gateway: include inline config env vars in service install environments. (#1735) Thanks @Seredeep.</li>
|
|
||||||
<li>Gateway: skip Tailscale DNS probing when tailscale.mode is off. (#1671)</li>
|
|
||||||
<li>Gateway: reduce log noise for late invokes + remote node probes; debounce skills refresh. (#1607) Thanks @petter-b.</li>
|
|
||||||
<li>Gateway: clarify Control UI/WebChat auth error hints for missing tokens. (#1690)</li>
|
|
||||||
<li>Gateway: listen on IPv6 loopback when bound to 127.0.0.1 so localhost webhooks work.</li>
|
|
||||||
<li>Gateway: store lock files in the temp directory to avoid stale locks on persistent volumes. (#1676)</li>
|
|
||||||
<li>macOS: default direct-transport <code>ws://</code> URLs to port 18789; document <code>gateway.remote.transport</code>. (#1603) Thanks @ngutman.</li>
|
|
||||||
<li>Tests: cap Vitest workers on CI macOS to reduce timeouts. (#1597) Thanks @rohannagpal.</li>
|
|
||||||
<li>Tests: avoid fake-timer dependency in embedded runner stream mock to reduce CI flakes. (#1597) Thanks @rohannagpal.</li>
|
|
||||||
<li>Tests: increase embedded runner ordering test timeout to reduce CI flakes. (#1597) Thanks @rohannagpal.</li>
|
|
||||||
</ul>
|
|
||||||
<p><a href="https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md">View full changelog</a></p>
|
|
||||||
]]></description>
|
|
||||||
<enclosure url="https://github.com/openclaw/openclaw/releases/download/v2026.1.24/OpenClaw-2026.1.24.zip" length="12396700" type="application/octet-stream" sparkle:edSignature="u+XzKD3YwV8s79gIr7LK4OtDCcmp/b+cjNC6SHav3/1CVJegh02SsBKatrampox32XGx8P2+8c/+fHV+qpkHCA=="/>
|
|
||||||
</item>
|
|
||||||
</channel>
|
</channel>
|
||||||
</rss>
|
</rss>
|
||||||
@@ -21,8 +21,8 @@ android {
|
|||||||
applicationId = "ai.openclaw.android"
|
applicationId = "ai.openclaw.android"
|
||||||
minSdk = 31
|
minSdk = 31
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionCode = 202601290
|
versionCode = 202602020
|
||||||
versionName = "2026.1.29"
|
versionName = "2026.2.2"
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
|||||||
@@ -19,9 +19,9 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2026.1.29</string>
|
<string>2026.2.2</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>20260129</string>
|
<string>20260202</string>
|
||||||
<key>NSAppTransportSecurity</key>
|
<key>NSAppTransportSecurity</key>
|
||||||
<dict>
|
<dict>
|
||||||
<key>NSAllowsArbitraryLoadsInWebContent</key>
|
<key>NSAllowsArbitraryLoadsInWebContent</key>
|
||||||
|
|||||||
@@ -17,8 +17,8 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>BNDL</string>
|
<string>BNDL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2026.1.29</string>
|
<string>2026.2.2</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>20260129</string>
|
<string>20260202</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -81,8 +81,8 @@ targets:
|
|||||||
properties:
|
properties:
|
||||||
CFBundleDisplayName: OpenClaw
|
CFBundleDisplayName: OpenClaw
|
||||||
CFBundleIconName: AppIcon
|
CFBundleIconName: AppIcon
|
||||||
CFBundleShortVersionString: "2026.1.27-beta.1"
|
CFBundleShortVersionString: "2026.2.2"
|
||||||
CFBundleVersion: "20260126"
|
CFBundleVersion: "20260202"
|
||||||
UILaunchScreen: {}
|
UILaunchScreen: {}
|
||||||
UIApplicationSceneManifest:
|
UIApplicationSceneManifest:
|
||||||
UIApplicationSupportsMultipleScenes: false
|
UIApplicationSupportsMultipleScenes: false
|
||||||
@@ -130,5 +130,5 @@ targets:
|
|||||||
path: Tests/Info.plist
|
path: Tests/Info.plist
|
||||||
properties:
|
properties:
|
||||||
CFBundleDisplayName: OpenClawTests
|
CFBundleDisplayName: OpenClawTests
|
||||||
CFBundleShortVersionString: "2026.1.27-beta.1"
|
CFBundleShortVersionString: "2026.2.2"
|
||||||
CFBundleVersion: "20260126"
|
CFBundleVersion: "20260202"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"originHash" : "c86f22da7772193c6f161fc9db81747cc00c8b8c96b45f9479de1e65c2c4b17e",
|
"originHash" : "1c9c9d251b760ed3234ecff741a88eb4bf42315ad6f50ac7392b187cf226c16c",
|
||||||
"pins" : [
|
"pins" : [
|
||||||
{
|
{
|
||||||
"identity" : "axorcist",
|
"identity" : "axorcist",
|
||||||
@@ -24,7 +24,7 @@
|
|||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/steipete/ElevenLabsKit",
|
"location" : "https://github.com/steipete/ElevenLabsKit",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "7e3c948d8340abe3977014f3de020edf221e9269",
|
"revision" : "c8679fbd37416a8780fe43be88a497ff16209e2d",
|
||||||
"version" : "0.1.0"
|
"version" : "0.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -15,9 +15,9 @@
|
|||||||
<key>CFBundlePackageType</key>
|
<key>CFBundlePackageType</key>
|
||||||
<string>APPL</string>
|
<string>APPL</string>
|
||||||
<key>CFBundleShortVersionString</key>
|
<key>CFBundleShortVersionString</key>
|
||||||
<string>2026.1.29</string>
|
<string>2026.2.2</string>
|
||||||
<key>CFBundleVersion</key>
|
<key>CFBundleVersion</key>
|
||||||
<string>202601290</string>
|
<string>202602020</string>
|
||||||
<key>CFBundleIconFile</key>
|
<key>CFBundleIconFile</key>
|
||||||
<string>OpenClaw</string>
|
<string>OpenClaw</string>
|
||||||
<key>CFBundleURLTypes</key>
|
<key>CFBundleURLTypes</key>
|
||||||
|
|||||||
@@ -222,9 +222,9 @@ enum WideAreaGatewayDiscovery {
|
|||||||
process.executableURL = URL(fileURLWithPath: path)
|
process.executableURL = URL(fileURLWithPath: path)
|
||||||
process.arguments = args
|
process.arguments = args
|
||||||
let outPipe = Pipe()
|
let outPipe = Pipe()
|
||||||
let errPipe = Pipe()
|
|
||||||
process.standardOutput = outPipe
|
process.standardOutput = outPipe
|
||||||
process.standardError = errPipe
|
// Avoid stderr pipe backpressure; we don't consume it.
|
||||||
|
process.standardError = FileHandle.nullDevice
|
||||||
|
|
||||||
do {
|
do {
|
||||||
try process.run()
|
try process.run()
|
||||||
|
|||||||
@@ -589,20 +589,24 @@ public struct AgentIdentityResult: Codable, Sendable {
|
|||||||
public let agentid: String
|
public let agentid: String
|
||||||
public let name: String?
|
public let name: String?
|
||||||
public let avatar: String?
|
public let avatar: String?
|
||||||
|
public let emoji: String?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
agentid: String,
|
agentid: String,
|
||||||
name: String?,
|
name: String?,
|
||||||
avatar: String?
|
avatar: String?,
|
||||||
|
emoji: String?
|
||||||
) {
|
) {
|
||||||
self.agentid = agentid
|
self.agentid = agentid
|
||||||
self.name = name
|
self.name = name
|
||||||
self.avatar = avatar
|
self.avatar = avatar
|
||||||
|
self.emoji = emoji
|
||||||
}
|
}
|
||||||
private enum CodingKeys: String, CodingKey {
|
private enum CodingKeys: String, CodingKey {
|
||||||
case agentid = "agentId"
|
case agentid = "agentId"
|
||||||
case name
|
case name
|
||||||
case avatar
|
case avatar
|
||||||
|
case emoji
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1556,6 +1560,157 @@ public struct AgentSummary: Codable, Sendable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public struct AgentsFileEntry: Codable, Sendable {
|
||||||
|
public let name: String
|
||||||
|
public let path: String
|
||||||
|
public let missing: Bool
|
||||||
|
public let size: Int?
|
||||||
|
public let updatedatms: Int?
|
||||||
|
public let content: String?
|
||||||
|
|
||||||
|
public init(
|
||||||
|
name: String,
|
||||||
|
path: String,
|
||||||
|
missing: Bool,
|
||||||
|
size: Int?,
|
||||||
|
updatedatms: Int?,
|
||||||
|
content: String?
|
||||||
|
) {
|
||||||
|
self.name = name
|
||||||
|
self.path = path
|
||||||
|
self.missing = missing
|
||||||
|
self.size = size
|
||||||
|
self.updatedatms = updatedatms
|
||||||
|
self.content = content
|
||||||
|
}
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case name
|
||||||
|
case path
|
||||||
|
case missing
|
||||||
|
case size
|
||||||
|
case updatedatms = "updatedAtMs"
|
||||||
|
case content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct AgentsFilesListParams: Codable, Sendable {
|
||||||
|
public let agentid: String
|
||||||
|
|
||||||
|
public init(
|
||||||
|
agentid: String
|
||||||
|
) {
|
||||||
|
self.agentid = agentid
|
||||||
|
}
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case agentid = "agentId"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct AgentsFilesListResult: Codable, Sendable {
|
||||||
|
public let agentid: String
|
||||||
|
public let workspace: String
|
||||||
|
public let files: [AgentsFileEntry]
|
||||||
|
|
||||||
|
public init(
|
||||||
|
agentid: String,
|
||||||
|
workspace: String,
|
||||||
|
files: [AgentsFileEntry]
|
||||||
|
) {
|
||||||
|
self.agentid = agentid
|
||||||
|
self.workspace = workspace
|
||||||
|
self.files = files
|
||||||
|
}
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case agentid = "agentId"
|
||||||
|
case workspace
|
||||||
|
case files
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct AgentsFilesGetParams: Codable, Sendable {
|
||||||
|
public let agentid: String
|
||||||
|
public let name: String
|
||||||
|
|
||||||
|
public init(
|
||||||
|
agentid: String,
|
||||||
|
name: String
|
||||||
|
) {
|
||||||
|
self.agentid = agentid
|
||||||
|
self.name = name
|
||||||
|
}
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case agentid = "agentId"
|
||||||
|
case name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct AgentsFilesGetResult: Codable, Sendable {
|
||||||
|
public let agentid: String
|
||||||
|
public let workspace: String
|
||||||
|
public let file: AgentsFileEntry
|
||||||
|
|
||||||
|
public init(
|
||||||
|
agentid: String,
|
||||||
|
workspace: String,
|
||||||
|
file: AgentsFileEntry
|
||||||
|
) {
|
||||||
|
self.agentid = agentid
|
||||||
|
self.workspace = workspace
|
||||||
|
self.file = file
|
||||||
|
}
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case agentid = "agentId"
|
||||||
|
case workspace
|
||||||
|
case file
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct AgentsFilesSetParams: Codable, Sendable {
|
||||||
|
public let agentid: String
|
||||||
|
public let name: String
|
||||||
|
public let content: String
|
||||||
|
|
||||||
|
public init(
|
||||||
|
agentid: String,
|
||||||
|
name: String,
|
||||||
|
content: String
|
||||||
|
) {
|
||||||
|
self.agentid = agentid
|
||||||
|
self.name = name
|
||||||
|
self.content = content
|
||||||
|
}
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case agentid = "agentId"
|
||||||
|
case name
|
||||||
|
case content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct AgentsFilesSetResult: Codable, Sendable {
|
||||||
|
public let ok: Bool
|
||||||
|
public let agentid: String
|
||||||
|
public let workspace: String
|
||||||
|
public let file: AgentsFileEntry
|
||||||
|
|
||||||
|
public init(
|
||||||
|
ok: Bool,
|
||||||
|
agentid: String,
|
||||||
|
workspace: String,
|
||||||
|
file: AgentsFileEntry
|
||||||
|
) {
|
||||||
|
self.ok = ok
|
||||||
|
self.agentid = agentid
|
||||||
|
self.workspace = workspace
|
||||||
|
self.file = file
|
||||||
|
}
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case ok
|
||||||
|
case agentid = "agentId"
|
||||||
|
case workspace
|
||||||
|
case file
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public struct AgentsListParams: Codable, Sendable {
|
public struct AgentsListParams: Codable, Sendable {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1630,6 +1785,16 @@ public struct ModelsListResult: Codable, Sendable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public struct SkillsStatusParams: Codable, Sendable {
|
public struct SkillsStatusParams: Codable, Sendable {
|
||||||
|
public let agentid: String?
|
||||||
|
|
||||||
|
public init(
|
||||||
|
agentid: String?
|
||||||
|
) {
|
||||||
|
self.agentid = agentid
|
||||||
|
}
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case agentid = "agentId"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct SkillsBinsParams: Codable, Sendable {
|
public struct SkillsBinsParams: Codable, Sendable {
|
||||||
|
|||||||
@@ -416,7 +416,9 @@ public actor GatewayChannelActor {
|
|||||||
guard let self else { return }
|
guard let self else { return }
|
||||||
await self.watchTicks()
|
await self.watchTicks()
|
||||||
}
|
}
|
||||||
await self.pushHandler?(.snapshot(ok))
|
if let pushHandler = self.pushHandler {
|
||||||
|
Task { await pushHandler(.snapshot(ok)) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func listen() {
|
private func listen() {
|
||||||
|
|||||||
@@ -11,10 +11,12 @@ private struct NodeInvokeRequestPayload: Codable, Sendable {
|
|||||||
var idempotencyKey: String?
|
var idempotencyKey: String?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public actor GatewayNodeSession {
|
public actor GatewayNodeSession {
|
||||||
private let logger = Logger(subsystem: "ai.openclaw", category: "node.gateway")
|
private let logger = Logger(subsystem: "ai.openclaw", category: "node.gateway")
|
||||||
private let decoder = JSONDecoder()
|
private let decoder = JSONDecoder()
|
||||||
private let encoder = JSONEncoder()
|
private let encoder = JSONEncoder()
|
||||||
|
private static let defaultInvokeTimeoutMs = 30_000
|
||||||
private var channel: GatewayChannelActor?
|
private var channel: GatewayChannelActor?
|
||||||
private var activeURL: URL?
|
private var activeURL: URL?
|
||||||
private var activeToken: String?
|
private var activeToken: String?
|
||||||
@@ -23,34 +25,78 @@ public actor GatewayNodeSession {
|
|||||||
private var onConnected: (@Sendable () async -> Void)?
|
private var onConnected: (@Sendable () async -> Void)?
|
||||||
private var onDisconnected: (@Sendable (String) async -> Void)?
|
private var onDisconnected: (@Sendable (String) async -> Void)?
|
||||||
private var onInvoke: (@Sendable (BridgeInvokeRequest) async -> BridgeInvokeResponse)?
|
private var onInvoke: (@Sendable (BridgeInvokeRequest) async -> BridgeInvokeResponse)?
|
||||||
|
private var hasNotifiedConnected = false
|
||||||
|
private var snapshotReceived = false
|
||||||
|
private var snapshotWaiters: [CheckedContinuation<Bool, Never>] = []
|
||||||
|
|
||||||
static func invokeWithTimeout(
|
static func invokeWithTimeout(
|
||||||
request: BridgeInvokeRequest,
|
request: BridgeInvokeRequest,
|
||||||
timeoutMs: Int?,
|
timeoutMs: Int?,
|
||||||
onInvoke: @escaping @Sendable (BridgeInvokeRequest) async -> BridgeInvokeResponse
|
onInvoke: @escaping @Sendable (BridgeInvokeRequest) async -> BridgeInvokeResponse
|
||||||
) async -> BridgeInvokeResponse {
|
) async -> BridgeInvokeResponse {
|
||||||
let timeout = max(0, timeoutMs ?? 0)
|
let timeoutLogger = Logger(subsystem: "ai.openclaw", category: "node.gateway")
|
||||||
|
let timeout: Int = {
|
||||||
|
if let timeoutMs { return max(0, timeoutMs) }
|
||||||
|
return Self.defaultInvokeTimeoutMs
|
||||||
|
}()
|
||||||
guard timeout > 0 else {
|
guard timeout > 0 else {
|
||||||
return await onInvoke(request)
|
return await onInvoke(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
return await withTaskGroup(of: BridgeInvokeResponse.self) { group in
|
// Use an explicit latch so timeouts win even if onInvoke blocks (e.g., permission prompts).
|
||||||
group.addTask { await onInvoke(request) }
|
final class InvokeLatch: @unchecked Sendable {
|
||||||
group.addTask {
|
private let lock = NSLock()
|
||||||
|
private var continuation: CheckedContinuation<BridgeInvokeResponse, Never>?
|
||||||
|
private var resumed = false
|
||||||
|
|
||||||
|
func setContinuation(_ continuation: CheckedContinuation<BridgeInvokeResponse, Never>) {
|
||||||
|
self.lock.lock()
|
||||||
|
defer { self.lock.unlock() }
|
||||||
|
self.continuation = continuation
|
||||||
|
}
|
||||||
|
|
||||||
|
func resume(_ response: BridgeInvokeResponse) {
|
||||||
|
let cont: CheckedContinuation<BridgeInvokeResponse, Never>?
|
||||||
|
self.lock.lock()
|
||||||
|
if self.resumed {
|
||||||
|
self.lock.unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.resumed = true
|
||||||
|
cont = self.continuation
|
||||||
|
self.continuation = nil
|
||||||
|
self.lock.unlock()
|
||||||
|
cont?.resume(returning: response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let latch = InvokeLatch()
|
||||||
|
var onInvokeTask: Task<Void, Never>?
|
||||||
|
var timeoutTask: Task<Void, Never>?
|
||||||
|
defer {
|
||||||
|
onInvokeTask?.cancel()
|
||||||
|
timeoutTask?.cancel()
|
||||||
|
}
|
||||||
|
let response = await withCheckedContinuation { (cont: CheckedContinuation<BridgeInvokeResponse, Never>) in
|
||||||
|
latch.setContinuation(cont)
|
||||||
|
onInvokeTask = Task.detached {
|
||||||
|
let result = await onInvoke(request)
|
||||||
|
latch.resume(result)
|
||||||
|
}
|
||||||
|
timeoutTask = Task.detached {
|
||||||
try? await Task.sleep(nanoseconds: UInt64(timeout) * 1_000_000)
|
try? await Task.sleep(nanoseconds: UInt64(timeout) * 1_000_000)
|
||||||
return BridgeInvokeResponse(
|
timeoutLogger.info("node invoke timeout fired id=\(request.id, privacy: .public)")
|
||||||
|
latch.resume(BridgeInvokeResponse(
|
||||||
id: request.id,
|
id: request.id,
|
||||||
ok: false,
|
ok: false,
|
||||||
error: OpenClawNodeError(
|
error: OpenClawNodeError(
|
||||||
code: .unavailable,
|
code: .unavailable,
|
||||||
message: "node invoke timed out")
|
message: "node invoke timed out")
|
||||||
)
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
let first = await group.next()!
|
|
||||||
group.cancelAll()
|
|
||||||
return first
|
|
||||||
}
|
}
|
||||||
|
timeoutLogger.info("node invoke race resolved id=\(request.id, privacy: .public) ok=\(response.ok, privacy: .public)")
|
||||||
|
return response
|
||||||
}
|
}
|
||||||
private var serverEventSubscribers: [UUID: AsyncStream<EventFrame>.Continuation] = [:]
|
private var serverEventSubscribers: [UUID: AsyncStream<EventFrame>.Continuation] = [:]
|
||||||
private var canvasHostUrl: String?
|
private var canvasHostUrl: String?
|
||||||
@@ -78,6 +124,7 @@ public actor GatewayNodeSession {
|
|||||||
self.onInvoke = onInvoke
|
self.onInvoke = onInvoke
|
||||||
|
|
||||||
if shouldReconnect {
|
if shouldReconnect {
|
||||||
|
self.resetConnectionState()
|
||||||
if let existing = self.channel {
|
if let existing = self.channel {
|
||||||
await existing.shutdown()
|
await existing.shutdown()
|
||||||
}
|
}
|
||||||
@@ -107,7 +154,8 @@ public actor GatewayNodeSession {
|
|||||||
|
|
||||||
do {
|
do {
|
||||||
try await channel.connect()
|
try await channel.connect()
|
||||||
await onConnected()
|
_ = await self.waitForSnapshot(timeoutMs: 500)
|
||||||
|
await self.notifyConnectedIfNeeded()
|
||||||
} catch {
|
} catch {
|
||||||
await onDisconnected(error.localizedDescription)
|
await onDisconnected(error.localizedDescription)
|
||||||
throw error
|
throw error
|
||||||
@@ -120,6 +168,7 @@ public actor GatewayNodeSession {
|
|||||||
self.activeURL = nil
|
self.activeURL = nil
|
||||||
self.activeToken = nil
|
self.activeToken = nil
|
||||||
self.activePassword = nil
|
self.activePassword = nil
|
||||||
|
self.resetConnectionState()
|
||||||
}
|
}
|
||||||
|
|
||||||
public func currentCanvasHostUrl() -> String? {
|
public func currentCanvasHostUrl() -> String? {
|
||||||
@@ -179,7 +228,8 @@ public actor GatewayNodeSession {
|
|||||||
case let .snapshot(ok):
|
case let .snapshot(ok):
|
||||||
let raw = ok.canvashosturl?.trimmingCharacters(in: .whitespacesAndNewlines)
|
let raw = ok.canvashosturl?.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
self.canvasHostUrl = (raw?.isEmpty == false) ? raw : nil
|
self.canvasHostUrl = (raw?.isEmpty == false) ? raw : nil
|
||||||
await self.onConnected?()
|
self.markSnapshotReceived()
|
||||||
|
await self.notifyConnectedIfNeeded()
|
||||||
case let .event(evt):
|
case let .event(evt):
|
||||||
await self.handleEvent(evt)
|
await self.handleEvent(evt)
|
||||||
default:
|
default:
|
||||||
@@ -187,28 +237,98 @@ public actor GatewayNodeSession {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func resetConnectionState() {
|
||||||
|
self.hasNotifiedConnected = false
|
||||||
|
self.snapshotReceived = false
|
||||||
|
if !self.snapshotWaiters.isEmpty {
|
||||||
|
let waiters = self.snapshotWaiters
|
||||||
|
self.snapshotWaiters.removeAll()
|
||||||
|
for waiter in waiters {
|
||||||
|
waiter.resume(returning: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func markSnapshotReceived() {
|
||||||
|
self.snapshotReceived = true
|
||||||
|
if !self.snapshotWaiters.isEmpty {
|
||||||
|
let waiters = self.snapshotWaiters
|
||||||
|
self.snapshotWaiters.removeAll()
|
||||||
|
for waiter in waiters {
|
||||||
|
waiter.resume(returning: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func waitForSnapshot(timeoutMs: Int) async -> Bool {
|
||||||
|
if self.snapshotReceived { return true }
|
||||||
|
let clamped = max(0, timeoutMs)
|
||||||
|
return await withCheckedContinuation { cont in
|
||||||
|
self.snapshotWaiters.append(cont)
|
||||||
|
Task { [weak self] in
|
||||||
|
guard let self else { return }
|
||||||
|
try? await Task.sleep(nanoseconds: UInt64(clamped) * 1_000_000)
|
||||||
|
await self.timeoutSnapshotWaiters()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func timeoutSnapshotWaiters() {
|
||||||
|
guard !self.snapshotReceived else { return }
|
||||||
|
if !self.snapshotWaiters.isEmpty {
|
||||||
|
let waiters = self.snapshotWaiters
|
||||||
|
self.snapshotWaiters.removeAll()
|
||||||
|
for waiter in waiters {
|
||||||
|
waiter.resume(returning: false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func notifyConnectedIfNeeded() async {
|
||||||
|
guard !self.hasNotifiedConnected else { return }
|
||||||
|
self.hasNotifiedConnected = true
|
||||||
|
await self.onConnected?()
|
||||||
|
}
|
||||||
|
|
||||||
private func handleEvent(_ evt: EventFrame) async {
|
private func handleEvent(_ evt: EventFrame) async {
|
||||||
self.broadcastServerEvent(evt)
|
self.broadcastServerEvent(evt)
|
||||||
guard evt.event == "node.invoke.request" else { return }
|
guard evt.event == "node.invoke.request" else { return }
|
||||||
|
self.logger.info("node invoke request received")
|
||||||
guard let payload = evt.payload else { return }
|
guard let payload = evt.payload else { return }
|
||||||
do {
|
do {
|
||||||
let data = try self.encoder.encode(payload)
|
let request = try self.decodeInvokeRequest(from: payload)
|
||||||
let request = try self.decoder.decode(NodeInvokeRequestPayload.self, from: data)
|
let timeoutLabel = request.timeoutMs.map(String.init) ?? "none"
|
||||||
|
self.logger.info("node invoke request decoded id=\(request.id, privacy: .public) command=\(request.command, privacy: .public) timeoutMs=\(timeoutLabel, privacy: .public)")
|
||||||
guard let onInvoke else { return }
|
guard let onInvoke else { return }
|
||||||
let req = BridgeInvokeRequest(id: request.id, command: request.command, paramsJSON: request.paramsJSON)
|
let req = BridgeInvokeRequest(id: request.id, command: request.command, paramsJSON: request.paramsJSON)
|
||||||
|
self.logger.info("node invoke executing id=\(request.id, privacy: .public)")
|
||||||
let response = await Self.invokeWithTimeout(
|
let response = await Self.invokeWithTimeout(
|
||||||
request: req,
|
request: req,
|
||||||
timeoutMs: request.timeoutMs,
|
timeoutMs: request.timeoutMs,
|
||||||
onInvoke: onInvoke
|
onInvoke: onInvoke
|
||||||
)
|
)
|
||||||
|
self.logger.info("node invoke completed id=\(request.id, privacy: .public) ok=\(response.ok, privacy: .public)")
|
||||||
await self.sendInvokeResult(request: request, response: response)
|
await self.sendInvokeResult(request: request, response: response)
|
||||||
} catch {
|
} catch {
|
||||||
self.logger.error("node invoke decode failed: \(error.localizedDescription, privacy: .public)")
|
self.logger.error("node invoke decode failed: \(error.localizedDescription, privacy: .public)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func decodeInvokeRequest(from payload: OpenClawProtocol.AnyCodable) throws -> NodeInvokeRequestPayload {
|
||||||
|
do {
|
||||||
|
let data = try self.encoder.encode(payload)
|
||||||
|
return try self.decoder.decode(NodeInvokeRequestPayload.self, from: data)
|
||||||
|
} catch {
|
||||||
|
if let raw = payload.value as? String, let data = raw.data(using: .utf8) {
|
||||||
|
return try self.decoder.decode(NodeInvokeRequestPayload.self, from: data)
|
||||||
|
}
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func sendInvokeResult(request: NodeInvokeRequestPayload, response: BridgeInvokeResponse) async {
|
private func sendInvokeResult(request: NodeInvokeRequestPayload, response: BridgeInvokeResponse) async {
|
||||||
guard let channel = self.channel else { return }
|
guard let channel = self.channel else { return }
|
||||||
|
self.logger.info("node invoke result sending id=\(request.id, privacy: .public) ok=\(response.ok, privacy: .public)")
|
||||||
var params: [String: AnyCodable] = [
|
var params: [String: AnyCodable] = [
|
||||||
"id": AnyCodable(request.id),
|
"id": AnyCodable(request.id),
|
||||||
"nodeId": AnyCodable(request.nodeId),
|
"nodeId": AnyCodable(request.nodeId),
|
||||||
@@ -226,7 +346,7 @@ public actor GatewayNodeSession {
|
|||||||
do {
|
do {
|
||||||
try await channel.send(method: "node.invoke.result", params: params)
|
try await channel.send(method: "node.invoke.result", params: params)
|
||||||
} catch {
|
} catch {
|
||||||
self.logger.error("node invoke result failed: \(error.localizedDescription, privacy: .public)")
|
self.logger.error("node invoke result failed id=\(request.id, privacy: .public) error=\(error.localizedDescription, privacy: .public)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -589,20 +589,24 @@ public struct AgentIdentityResult: Codable, Sendable {
|
|||||||
public let agentid: String
|
public let agentid: String
|
||||||
public let name: String?
|
public let name: String?
|
||||||
public let avatar: String?
|
public let avatar: String?
|
||||||
|
public let emoji: String?
|
||||||
|
|
||||||
public init(
|
public init(
|
||||||
agentid: String,
|
agentid: String,
|
||||||
name: String?,
|
name: String?,
|
||||||
avatar: String?
|
avatar: String?,
|
||||||
|
emoji: String?
|
||||||
) {
|
) {
|
||||||
self.agentid = agentid
|
self.agentid = agentid
|
||||||
self.name = name
|
self.name = name
|
||||||
self.avatar = avatar
|
self.avatar = avatar
|
||||||
|
self.emoji = emoji
|
||||||
}
|
}
|
||||||
private enum CodingKeys: String, CodingKey {
|
private enum CodingKeys: String, CodingKey {
|
||||||
case agentid = "agentId"
|
case agentid = "agentId"
|
||||||
case name
|
case name
|
||||||
case avatar
|
case avatar
|
||||||
|
case emoji
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1556,6 +1560,157 @@ public struct AgentSummary: Codable, Sendable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public struct AgentsFileEntry: Codable, Sendable {
|
||||||
|
public let name: String
|
||||||
|
public let path: String
|
||||||
|
public let missing: Bool
|
||||||
|
public let size: Int?
|
||||||
|
public let updatedatms: Int?
|
||||||
|
public let content: String?
|
||||||
|
|
||||||
|
public init(
|
||||||
|
name: String,
|
||||||
|
path: String,
|
||||||
|
missing: Bool,
|
||||||
|
size: Int?,
|
||||||
|
updatedatms: Int?,
|
||||||
|
content: String?
|
||||||
|
) {
|
||||||
|
self.name = name
|
||||||
|
self.path = path
|
||||||
|
self.missing = missing
|
||||||
|
self.size = size
|
||||||
|
self.updatedatms = updatedatms
|
||||||
|
self.content = content
|
||||||
|
}
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case name
|
||||||
|
case path
|
||||||
|
case missing
|
||||||
|
case size
|
||||||
|
case updatedatms = "updatedAtMs"
|
||||||
|
case content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct AgentsFilesListParams: Codable, Sendable {
|
||||||
|
public let agentid: String
|
||||||
|
|
||||||
|
public init(
|
||||||
|
agentid: String
|
||||||
|
) {
|
||||||
|
self.agentid = agentid
|
||||||
|
}
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case agentid = "agentId"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct AgentsFilesListResult: Codable, Sendable {
|
||||||
|
public let agentid: String
|
||||||
|
public let workspace: String
|
||||||
|
public let files: [AgentsFileEntry]
|
||||||
|
|
||||||
|
public init(
|
||||||
|
agentid: String,
|
||||||
|
workspace: String,
|
||||||
|
files: [AgentsFileEntry]
|
||||||
|
) {
|
||||||
|
self.agentid = agentid
|
||||||
|
self.workspace = workspace
|
||||||
|
self.files = files
|
||||||
|
}
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case agentid = "agentId"
|
||||||
|
case workspace
|
||||||
|
case files
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct AgentsFilesGetParams: Codable, Sendable {
|
||||||
|
public let agentid: String
|
||||||
|
public let name: String
|
||||||
|
|
||||||
|
public init(
|
||||||
|
agentid: String,
|
||||||
|
name: String
|
||||||
|
) {
|
||||||
|
self.agentid = agentid
|
||||||
|
self.name = name
|
||||||
|
}
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case agentid = "agentId"
|
||||||
|
case name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct AgentsFilesGetResult: Codable, Sendable {
|
||||||
|
public let agentid: String
|
||||||
|
public let workspace: String
|
||||||
|
public let file: AgentsFileEntry
|
||||||
|
|
||||||
|
public init(
|
||||||
|
agentid: String,
|
||||||
|
workspace: String,
|
||||||
|
file: AgentsFileEntry
|
||||||
|
) {
|
||||||
|
self.agentid = agentid
|
||||||
|
self.workspace = workspace
|
||||||
|
self.file = file
|
||||||
|
}
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case agentid = "agentId"
|
||||||
|
case workspace
|
||||||
|
case file
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct AgentsFilesSetParams: Codable, Sendable {
|
||||||
|
public let agentid: String
|
||||||
|
public let name: String
|
||||||
|
public let content: String
|
||||||
|
|
||||||
|
public init(
|
||||||
|
agentid: String,
|
||||||
|
name: String,
|
||||||
|
content: String
|
||||||
|
) {
|
||||||
|
self.agentid = agentid
|
||||||
|
self.name = name
|
||||||
|
self.content = content
|
||||||
|
}
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case agentid = "agentId"
|
||||||
|
case name
|
||||||
|
case content
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct AgentsFilesSetResult: Codable, Sendable {
|
||||||
|
public let ok: Bool
|
||||||
|
public let agentid: String
|
||||||
|
public let workspace: String
|
||||||
|
public let file: AgentsFileEntry
|
||||||
|
|
||||||
|
public init(
|
||||||
|
ok: Bool,
|
||||||
|
agentid: String,
|
||||||
|
workspace: String,
|
||||||
|
file: AgentsFileEntry
|
||||||
|
) {
|
||||||
|
self.ok = ok
|
||||||
|
self.agentid = agentid
|
||||||
|
self.workspace = workspace
|
||||||
|
self.file = file
|
||||||
|
}
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case ok
|
||||||
|
case agentid = "agentId"
|
||||||
|
case workspace
|
||||||
|
case file
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public struct AgentsListParams: Codable, Sendable {
|
public struct AgentsListParams: Codable, Sendable {
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1630,6 +1785,16 @@ public struct ModelsListResult: Codable, Sendable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public struct SkillsStatusParams: Codable, Sendable {
|
public struct SkillsStatusParams: Codable, Sendable {
|
||||||
|
public let agentid: String?
|
||||||
|
|
||||||
|
public init(
|
||||||
|
agentid: String?
|
||||||
|
) {
|
||||||
|
self.agentid = agentid
|
||||||
|
}
|
||||||
|
private enum CodingKeys: String, CodingKey {
|
||||||
|
case agentid = "agentId"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct SkillsBinsParams: Codable, Sendable {
|
public struct SkillsBinsParams: Codable, Sendable {
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ if (modalElement && Array.isArray(modalElement.styles)) {
|
|||||||
modalElement.styles = [...modalElement.styles, modalStyles];
|
modalElement.styles = [...modalElement.styles, modalStyles];
|
||||||
}
|
}
|
||||||
|
|
||||||
const empty = Object.freeze({});
|
|
||||||
const emptyClasses = () => ({});
|
const emptyClasses = () => ({});
|
||||||
const textHintStyles = () => ({ h1: {}, h2: {}, h3: {}, h4: {}, h5: {}, body: {}, caption: {} });
|
const textHintStyles = () => ({ h1: {}, h2: {}, h3: {}, h4: {}, h5: {}, body: {}, caption: {} });
|
||||||
|
|
||||||
@@ -160,7 +159,7 @@ class OpenClawA2UIHost extends LitElement {
|
|||||||
};
|
};
|
||||||
|
|
||||||
#processor = v0_8.Data.createSignalA2uiMessageProcessor();
|
#processor = v0_8.Data.createSignalA2uiMessageProcessor();
|
||||||
#themeProvider = new ContextProvider(this, {
|
themeProvider = new ContextProvider(this, {
|
||||||
context: themeContext,
|
context: themeContext,
|
||||||
initialValue: openclawTheme,
|
initialValue: openclawTheme,
|
||||||
});
|
});
|
||||||
@@ -318,8 +317,8 @@ class OpenClawA2UIHost extends LitElement {
|
|||||||
|
|
||||||
#handleActionStatus(evt) {
|
#handleActionStatus(evt) {
|
||||||
const detail = evt?.detail ?? null;
|
const detail = evt?.detail ?? null;
|
||||||
if (!detail || typeof detail.id !== "string") return;
|
if (!detail || typeof detail.id !== "string") {return;}
|
||||||
if (!this.pendingAction || this.pendingAction.id !== detail.id) return;
|
if (!this.pendingAction || this.pendingAction.id !== detail.id) {return;}
|
||||||
|
|
||||||
if (detail.ok) {
|
if (detail.ok) {
|
||||||
this.pendingAction = { ...this.pendingAction, phase: "sent", sentAt: Date.now() };
|
this.pendingAction = { ...this.pendingAction, phase: "sent", sentAt: Date.now() };
|
||||||
@@ -362,7 +361,7 @@ class OpenClawA2UIHost extends LitElement {
|
|||||||
for (const item of ctxItems) {
|
for (const item of ctxItems) {
|
||||||
const key = item?.key;
|
const key = item?.key;
|
||||||
const value = item?.value ?? null;
|
const value = item?.value ?? null;
|
||||||
if (!key || !value) continue;
|
if (!key || !value) {continue;}
|
||||||
|
|
||||||
if (typeof value.path === "string") {
|
if (typeof value.path === "string") {
|
||||||
const resolved = sourceNode
|
const resolved = sourceNode
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ services:
|
|||||||
"--bind",
|
"--bind",
|
||||||
"${OPENCLAW_GATEWAY_BIND:-lan}",
|
"${OPENCLAW_GATEWAY_BIND:-lan}",
|
||||||
"--port",
|
"--port",
|
||||||
"${OPENCLAW_GATEWAY_PORT:-18789}"
|
"18789",
|
||||||
]
|
]
|
||||||
|
|
||||||
openclaw-cli:
|
openclaw-cli:
|
||||||
@@ -32,6 +32,7 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
HOME: /home/node
|
HOME: /home/node
|
||||||
TERM: xterm-256color
|
TERM: xterm-256color
|
||||||
|
OPENCLAW_GATEWAY_TOKEN: ${OPENCLAW_GATEWAY_TOKEN}
|
||||||
BROWSER: echo
|
BROWSER: echo
|
||||||
CLAUDE_AI_SESSION_KEY: ${CLAUDE_AI_SESSION_KEY}
|
CLAUDE_AI_SESSION_KEY: ${CLAUDE_AI_SESSION_KEY}
|
||||||
CLAUDE_WEB_SESSION_KEY: ${CLAUDE_WEB_SESSION_KEY}
|
CLAUDE_WEB_SESSION_KEY: ${CLAUDE_WEB_SESSION_KEY}
|
||||||
|
|||||||
@@ -191,12 +191,12 @@ docker compose "${COMPOSE_ARGS[@]}" run --rm openclaw-cli onboard --no-install-d
|
|||||||
echo ""
|
echo ""
|
||||||
echo "==> Provider setup (optional)"
|
echo "==> Provider setup (optional)"
|
||||||
echo "WhatsApp (QR):"
|
echo "WhatsApp (QR):"
|
||||||
echo " ${COMPOSE_HINT} run --rm openclaw-cli providers login"
|
echo " ${COMPOSE_HINT} run --rm openclaw-cli channels login"
|
||||||
echo "Telegram (bot token):"
|
echo "Telegram (bot token):"
|
||||||
echo " ${COMPOSE_HINT} run --rm openclaw-cli providers add --provider telegram --token <token>"
|
echo " ${COMPOSE_HINT} run --rm openclaw-cli channels add --channel telegram --token <token>"
|
||||||
echo "Discord (bot token):"
|
echo "Discord (bot token):"
|
||||||
echo " ${COMPOSE_HINT} run --rm openclaw-cli providers add --provider discord --token <token>"
|
echo " ${COMPOSE_HINT} run --rm openclaw-cli channels add --channel discord --token <token>"
|
||||||
echo "Docs: https://docs.openclaw.ai/providers"
|
echo "Docs: https://docs.openclaw.ai/channels"
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "==> Starting gateway"
|
echo "==> Starting gateway"
|
||||||
|
|||||||
15
docs.acp.md
15
docs.acp.md
@@ -84,9 +84,12 @@ To target a specific Gateway or agent:
|
|||||||
"command": "openclaw",
|
"command": "openclaw",
|
||||||
"args": [
|
"args": [
|
||||||
"acp",
|
"acp",
|
||||||
"--url", "wss://gateway-host:18789",
|
"--url",
|
||||||
"--token", "<token>",
|
"wss://gateway-host:18789",
|
||||||
"--session", "agent:design:main"
|
"--token",
|
||||||
|
"<token>",
|
||||||
|
"--session",
|
||||||
|
"agent:design:main"
|
||||||
],
|
],
|
||||||
"env": {}
|
"env": {}
|
||||||
}
|
}
|
||||||
@@ -112,7 +115,7 @@ By default each ACP session is mapped to a dedicated Gateway session key:
|
|||||||
|
|
||||||
You can override or reuse sessions in two ways:
|
You can override or reuse sessions in two ways:
|
||||||
|
|
||||||
1) CLI defaults
|
1. CLI defaults
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
openclaw acp --session agent:main:main
|
openclaw acp --session agent:main:main
|
||||||
@@ -120,7 +123,7 @@ openclaw acp --session-label "support inbox"
|
|||||||
openclaw acp --reset-session
|
openclaw acp --reset-session
|
||||||
```
|
```
|
||||||
|
|
||||||
2) ACP metadata per session
|
2. ACP metadata per session
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@@ -185,7 +188,7 @@ updates. Terminal Gateway states map to ACP `done` with stop reasons:
|
|||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
- Unit: `src/acp/session.test.ts` covers run id lifecycle.
|
- Unit: `src/acp/session.test.ts` covers run id lifecycle.
|
||||||
- Full gate: `pnpm lint && pnpm build && pnpm test && pnpm docs:build`.
|
- Full gate: `pnpm build && pnpm check && pnpm test && pnpm docs:build`.
|
||||||
|
|
||||||
## Related Docs
|
## Related Docs
|
||||||
|
|
||||||
|
|||||||
31
docs/.i18n/README.md
Normal file
31
docs/.i18n/README.md
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# OpenClaw docs i18n assets
|
||||||
|
|
||||||
|
This folder stores **generated** and **config** files for documentation translations.
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
- `glossary.<lang>.json` — preferred term mappings (used in prompt guidance).
|
||||||
|
- `<lang>.tm.jsonl` — translation memory (cache) keyed by workflow + model + text hash.
|
||||||
|
|
||||||
|
## Glossary format
|
||||||
|
|
||||||
|
`glossary.<lang>.json` is an array of entries:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"source": "troubleshooting",
|
||||||
|
"target": "故障排除",
|
||||||
|
"ignore_case": true,
|
||||||
|
"whole_word": false
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Fields:
|
||||||
|
|
||||||
|
- `source`: English (or source) phrase to prefer.
|
||||||
|
- `target`: preferred translation output.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Glossary entries are passed to the model as **prompt guidance** (no deterministic rewrites).
|
||||||
|
- The translation memory is updated by `scripts/docs-i18n`.
|
||||||
190
docs/.i18n/glossary.zh-CN.json
Normal file
190
docs/.i18n/glossary.zh-CN.json
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"source": "OpenClaw",
|
||||||
|
"target": "OpenClaw"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "Gateway",
|
||||||
|
"target": "Gateway 网关"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "Pi",
|
||||||
|
"target": "Pi"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "Skills",
|
||||||
|
"target": "Skills"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "Skills config",
|
||||||
|
"target": "Skills 配置"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "Skills Config",
|
||||||
|
"target": "Skills 配置"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "local loopback",
|
||||||
|
"target": "local loopback"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "Tailscale",
|
||||||
|
"target": "Tailscale"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "Getting Started",
|
||||||
|
"target": "入门指南"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "Getting started",
|
||||||
|
"target": "入门指南"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "DMs",
|
||||||
|
"target": "私信"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "DM",
|
||||||
|
"target": "私信"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "sandbox",
|
||||||
|
"target": "沙箱"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "Sandbox",
|
||||||
|
"target": "沙箱"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "sandboxing",
|
||||||
|
"target": "沙箱隔离"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "Sandboxing",
|
||||||
|
"target": "沙箱隔离"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "sandboxed",
|
||||||
|
"target": "沙箱隔离"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "Sandboxed",
|
||||||
|
"target": "沙箱隔离"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "Sandboxing note",
|
||||||
|
"target": "沙箱注意事项"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "Companion apps",
|
||||||
|
"target": "配套应用"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "expected keys",
|
||||||
|
"target": "预期键名"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "block streaming",
|
||||||
|
"target": "分块流式传输"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "Block streaming",
|
||||||
|
"target": "分块流式传输"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "Discovery + transports",
|
||||||
|
"target": "设备发现 + 传输协议"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "Discovery",
|
||||||
|
"target": "设备发现"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "Network model",
|
||||||
|
"target": "网络模型"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "for full details",
|
||||||
|
"target": "了解详情"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "First 60 seconds",
|
||||||
|
"target": "最初的六十秒"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "Auth: where it lives (important)",
|
||||||
|
"target": "凭证:存储位置(重要)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "agent",
|
||||||
|
"target": "智能体"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "channel",
|
||||||
|
"target": "渠道"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "session",
|
||||||
|
"target": "会话"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "provider",
|
||||||
|
"target": "提供商"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "model",
|
||||||
|
"target": "模型"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "tool",
|
||||||
|
"target": "工具"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "CLI",
|
||||||
|
"target": "CLI"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "install sanity",
|
||||||
|
"target": "安装完整性检查"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "get unstuck",
|
||||||
|
"target": "解决问题"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "troubleshooting",
|
||||||
|
"target": "故障排除"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "FAQ",
|
||||||
|
"target": "常见问题"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "onboarding",
|
||||||
|
"target": "新手引导"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "Onboarding",
|
||||||
|
"target": "新手引导"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "wizard",
|
||||||
|
"target": "向导"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "environment variables",
|
||||||
|
"target": "环境变量"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "environment variable",
|
||||||
|
"target": "环境变量"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "env vars",
|
||||||
|
"target": "环境变量"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "env var",
|
||||||
|
"target": "环境变量"
|
||||||
|
}
|
||||||
|
]
|
||||||
1329
docs/.i18n/zh-CN.tm.jsonl
Normal file
1329
docs/.i18n/zh-CN.tm.jsonl
Normal file
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,9 @@ summary: "Monitor OAuth expiry for model providers"
|
|||||||
read_when:
|
read_when:
|
||||||
- Setting up auth expiry monitoring or alerts
|
- Setting up auth expiry monitoring or alerts
|
||||||
- Automating Claude Code / Codex OAuth refresh checks
|
- Automating Claude Code / Codex OAuth refresh checks
|
||||||
|
title: "Auth Monitoring"
|
||||||
---
|
---
|
||||||
|
|
||||||
# Auth monitoring
|
# Auth monitoring
|
||||||
|
|
||||||
OpenClaw exposes OAuth expiry health via `openclaw models status`. Use that for
|
OpenClaw exposes OAuth expiry health via `openclaw models status`. Use that for
|
||||||
@@ -16,6 +18,7 @@ openclaw models status --check
|
|||||||
```
|
```
|
||||||
|
|
||||||
Exit codes:
|
Exit codes:
|
||||||
|
|
||||||
- `0`: OK
|
- `0`: OK
|
||||||
- `1`: expired or missing credentials
|
- `1`: expired or missing credentials
|
||||||
- `2`: expiring soon (within 24h)
|
- `2`: expiring soon (within 24h)
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ read_when:
|
|||||||
- Scheduling background jobs or wakeups
|
- Scheduling background jobs or wakeups
|
||||||
- Wiring automation that should run with or alongside heartbeats
|
- Wiring automation that should run with or alongside heartbeats
|
||||||
- Deciding between heartbeat and cron for scheduled tasks
|
- Deciding between heartbeat and cron for scheduled tasks
|
||||||
|
title: "Cron Jobs"
|
||||||
---
|
---
|
||||||
|
|
||||||
# Cron jobs (Gateway scheduler)
|
# Cron jobs (Gateway scheduler)
|
||||||
|
|
||||||
> **Cron vs Heartbeat?** See [Cron vs Heartbeat](/automation/cron-vs-heartbeat) for guidance on when to use each.
|
> **Cron vs Heartbeat?** See [Cron vs Heartbeat](/automation/cron-vs-heartbeat) for guidance on when to use each.
|
||||||
@@ -12,10 +14,11 @@ read_when:
|
|||||||
Cron is the Gateway’s built-in scheduler. It persists jobs, wakes the agent at
|
Cron is the Gateway’s built-in scheduler. It persists jobs, wakes the agent at
|
||||||
the right time, and can optionally deliver output back to a chat.
|
the right time, and can optionally deliver output back to a chat.
|
||||||
|
|
||||||
If you want *“run this every morning”* or *“poke the agent in 20 minutes”*,
|
If you want _“run this every morning”_ or _“poke the agent in 20 minutes”_,
|
||||||
cron is the mechanism.
|
cron is the mechanism.
|
||||||
|
|
||||||
## TL;DR
|
## TL;DR
|
||||||
|
|
||||||
- Cron runs **inside the Gateway** (not inside the model).
|
- Cron runs **inside the Gateway** (not inside the model).
|
||||||
- Jobs persist under `~/.openclaw/cron/` so restarts don’t lose schedules.
|
- Jobs persist under `~/.openclaw/cron/` so restarts don’t lose schedules.
|
||||||
- Two execution styles:
|
- Two execution styles:
|
||||||
@@ -23,19 +26,63 @@ cron is the mechanism.
|
|||||||
- **Isolated**: run a dedicated agent turn in `cron:<jobId>`, optionally deliver output.
|
- **Isolated**: run a dedicated agent turn in `cron:<jobId>`, optionally deliver output.
|
||||||
- Wakeups are first-class: a job can request “wake now” vs “next heartbeat”.
|
- Wakeups are first-class: a job can request “wake now” vs “next heartbeat”.
|
||||||
|
|
||||||
|
## Quick start (actionable)
|
||||||
|
|
||||||
|
Create a one-shot reminder, verify it exists, and run it immediately:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openclaw cron add \
|
||||||
|
--name "Reminder" \
|
||||||
|
--at "2026-02-01T16:00:00Z" \
|
||||||
|
--session main \
|
||||||
|
--system-event "Reminder: check the cron docs draft" \
|
||||||
|
--wake now \
|
||||||
|
--delete-after-run
|
||||||
|
|
||||||
|
openclaw cron list
|
||||||
|
openclaw cron run <job-id> --force
|
||||||
|
openclaw cron runs --id <job-id>
|
||||||
|
```
|
||||||
|
|
||||||
|
Schedule a recurring isolated job with delivery:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
openclaw cron add \
|
||||||
|
--name "Morning brief" \
|
||||||
|
--cron "0 7 * * *" \
|
||||||
|
--tz "America/Los_Angeles" \
|
||||||
|
--session isolated \
|
||||||
|
--message "Summarize overnight updates." \
|
||||||
|
--deliver \
|
||||||
|
--channel slack \
|
||||||
|
--to "channel:C1234567890"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tool-call equivalents (Gateway cron tool)
|
||||||
|
|
||||||
|
For the canonical JSON shapes and examples, see [JSON schema for tool calls](/automation/cron-jobs#json-schema-for-tool-calls).
|
||||||
|
|
||||||
|
## Where cron jobs are stored
|
||||||
|
|
||||||
|
Cron jobs are persisted on the Gateway host at `~/.openclaw/cron/jobs.json` by default.
|
||||||
|
The Gateway loads the file into memory and writes it back on changes, so manual edits
|
||||||
|
are only safe when the Gateway is stopped. Prefer `openclaw cron add/edit` or the cron
|
||||||
|
tool call API for changes.
|
||||||
|
|
||||||
## Beginner-friendly overview
|
## Beginner-friendly overview
|
||||||
|
|
||||||
Think of a cron job as: **when** to run + **what** to do.
|
Think of a cron job as: **when** to run + **what** to do.
|
||||||
|
|
||||||
1) **Choose a schedule**
|
1. **Choose a schedule**
|
||||||
- One-shot reminder → `schedule.kind = "at"` (CLI: `--at`)
|
- One-shot reminder → `schedule.kind = "at"` (CLI: `--at`)
|
||||||
- Repeating job → `schedule.kind = "every"` or `schedule.kind = "cron"`
|
- Repeating job → `schedule.kind = "every"` or `schedule.kind = "cron"`
|
||||||
- If your ISO timestamp omits a timezone, it is treated as **UTC**.
|
- If your ISO timestamp omits a timezone, it is treated as **UTC**.
|
||||||
|
|
||||||
2) **Choose where it runs**
|
2. **Choose where it runs**
|
||||||
- `sessionTarget: "main"` → run during the next heartbeat with main context.
|
- `sessionTarget: "main"` → run during the next heartbeat with main context.
|
||||||
- `sessionTarget: "isolated"` → run a dedicated agent turn in `cron:<jobId>`.
|
- `sessionTarget: "isolated"` → run a dedicated agent turn in `cron:<jobId>`.
|
||||||
|
|
||||||
3) **Choose the payload**
|
3. **Choose the payload**
|
||||||
- Main session → `payload.kind = "systemEvent"`
|
- Main session → `payload.kind = "systemEvent"`
|
||||||
- Isolated session → `payload.kind = "agentTurn"`
|
- Isolated session → `payload.kind = "agentTurn"`
|
||||||
|
|
||||||
@@ -44,7 +91,9 @@ Optional: `deleteAfterRun: true` removes successful one-shot jobs from the store
|
|||||||
## Concepts
|
## Concepts
|
||||||
|
|
||||||
### Jobs
|
### Jobs
|
||||||
|
|
||||||
A cron job is a stored record with:
|
A cron job is a stored record with:
|
||||||
|
|
||||||
- a **schedule** (when it should run),
|
- a **schedule** (when it should run),
|
||||||
- a **payload** (what it should do),
|
- a **payload** (what it should do),
|
||||||
- optional **delivery** (where output should be sent).
|
- optional **delivery** (where output should be sent).
|
||||||
@@ -56,7 +105,9 @@ In agent tool calls, `jobId` is canonical; legacy `id` is accepted for compatibi
|
|||||||
Jobs can optionally auto-delete after a successful one-shot run via `deleteAfterRun: true`.
|
Jobs can optionally auto-delete after a successful one-shot run via `deleteAfterRun: true`.
|
||||||
|
|
||||||
### Schedules
|
### Schedules
|
||||||
|
|
||||||
Cron supports three schedule kinds:
|
Cron supports three schedule kinds:
|
||||||
|
|
||||||
- `at`: one-shot timestamp (ms since epoch). Gateway accepts ISO 8601 and coerces to UTC.
|
- `at`: one-shot timestamp (ms since epoch). Gateway accepts ISO 8601 and coerces to UTC.
|
||||||
- `every`: fixed interval (ms).
|
- `every`: fixed interval (ms).
|
||||||
- `cron`: 5-field cron expression with optional IANA timezone.
|
- `cron`: 5-field cron expression with optional IANA timezone.
|
||||||
@@ -67,6 +118,7 @@ local timezone is used.
|
|||||||
### Main vs isolated execution
|
### Main vs isolated execution
|
||||||
|
|
||||||
#### Main session jobs (system events)
|
#### Main session jobs (system events)
|
||||||
|
|
||||||
Main jobs enqueue a system event and optionally wake the heartbeat runner.
|
Main jobs enqueue a system event and optionally wake the heartbeat runner.
|
||||||
They must use `payload.kind = "systemEvent"`.
|
They must use `payload.kind = "systemEvent"`.
|
||||||
|
|
||||||
@@ -77,9 +129,11 @@ This is the best fit when you want the normal heartbeat prompt + main-session co
|
|||||||
See [Heartbeat](/gateway/heartbeat).
|
See [Heartbeat](/gateway/heartbeat).
|
||||||
|
|
||||||
#### Isolated jobs (dedicated cron sessions)
|
#### Isolated jobs (dedicated cron sessions)
|
||||||
|
|
||||||
Isolated jobs run a dedicated agent turn in session `cron:<jobId>`.
|
Isolated jobs run a dedicated agent turn in session `cron:<jobId>`.
|
||||||
|
|
||||||
Key behaviors:
|
Key behaviors:
|
||||||
|
|
||||||
- Prompt is prefixed with `[cron:<jobId> <job name>]` for traceability.
|
- Prompt is prefixed with `[cron:<jobId> <job name>]` for traceability.
|
||||||
- Each run starts a **fresh session id** (no prior conversation carry-over).
|
- Each run starts a **fresh session id** (no prior conversation carry-over).
|
||||||
- A summary is posted to the main session (prefix `Cron`, configurable).
|
- A summary is posted to the main session (prefix `Cron`, configurable).
|
||||||
@@ -90,11 +144,14 @@ Use isolated jobs for noisy, frequent, or "background chores" that shouldn't spa
|
|||||||
your main chat history.
|
your main chat history.
|
||||||
|
|
||||||
### Payload shapes (what runs)
|
### Payload shapes (what runs)
|
||||||
|
|
||||||
Two payload kinds are supported:
|
Two payload kinds are supported:
|
||||||
|
|
||||||
- `systemEvent`: main-session only, routed through the heartbeat prompt.
|
- `systemEvent`: main-session only, routed through the heartbeat prompt.
|
||||||
- `agentTurn`: isolated-session only, runs a dedicated agent turn.
|
- `agentTurn`: isolated-session only, runs a dedicated agent turn.
|
||||||
|
|
||||||
Common `agentTurn` fields:
|
Common `agentTurn` fields:
|
||||||
|
|
||||||
- `message`: required text prompt.
|
- `message`: required text prompt.
|
||||||
- `model` / `thinking`: optional overrides (see below).
|
- `model` / `thinking`: optional overrides (see below).
|
||||||
- `timeoutSeconds`: optional timeout override.
|
- `timeoutSeconds`: optional timeout override.
|
||||||
@@ -104,12 +161,15 @@ Common `agentTurn` fields:
|
|||||||
- `bestEffortDeliver`: avoid failing the job if delivery fails.
|
- `bestEffortDeliver`: avoid failing the job if delivery fails.
|
||||||
|
|
||||||
Isolation options (only for `session=isolated`):
|
Isolation options (only for `session=isolated`):
|
||||||
|
|
||||||
- `postToMainPrefix` (CLI: `--post-prefix`): prefix for the system event in main.
|
- `postToMainPrefix` (CLI: `--post-prefix`): prefix for the system event in main.
|
||||||
- `postToMainMode`: `summary` (default) or `full`.
|
- `postToMainMode`: `summary` (default) or `full`.
|
||||||
- `postToMainMaxChars`: max chars when `postToMainMode=full` (default 8000).
|
- `postToMainMaxChars`: max chars when `postToMainMode=full` (default 8000).
|
||||||
|
|
||||||
### Model and thinking overrides
|
### Model and thinking overrides
|
||||||
|
|
||||||
Isolated jobs (`agentTurn`) can override the model and thinking level:
|
Isolated jobs (`agentTurn`) can override the model and thinking level:
|
||||||
|
|
||||||
- `model`: Provider/model string (e.g., `anthropic/claude-sonnet-4-20250514`) or alias (e.g., `opus`)
|
- `model`: Provider/model string (e.g., `anthropic/claude-sonnet-4-20250514`) or alias (e.g., `opus`)
|
||||||
- `thinking`: Thinking level (`off`, `minimal`, `low`, `medium`, `high`, `xhigh`; GPT-5.2 + Codex models only)
|
- `thinking`: Thinking level (`off`, `minimal`, `low`, `medium`, `high`, `xhigh`; GPT-5.2 + Codex models only)
|
||||||
|
|
||||||
@@ -118,12 +178,15 @@ session model. We recommend model overrides only for isolated jobs to avoid
|
|||||||
unexpected context shifts.
|
unexpected context shifts.
|
||||||
|
|
||||||
Resolution priority:
|
Resolution priority:
|
||||||
|
|
||||||
1. Job payload override (highest)
|
1. Job payload override (highest)
|
||||||
2. Hook-specific defaults (e.g., `hooks.gmail.model`)
|
2. Hook-specific defaults (e.g., `hooks.gmail.model`)
|
||||||
3. Agent config default
|
3. Agent config default
|
||||||
|
|
||||||
### Delivery (channel + target)
|
### Delivery (channel + target)
|
||||||
|
|
||||||
Isolated jobs can deliver output to a channel. The job payload can specify:
|
Isolated jobs can deliver output to a channel. The job payload can specify:
|
||||||
|
|
||||||
- `channel`: `whatsapp` / `telegram` / `discord` / `slack` / `mattermost` (plugin) / `signal` / `imessage` / `last`
|
- `channel`: `whatsapp` / `telegram` / `discord` / `slack` / `mattermost` (plugin) / `signal` / `imessage` / `last`
|
||||||
- `to`: channel-specific recipient target
|
- `to`: channel-specific recipient target
|
||||||
|
|
||||||
@@ -131,15 +194,18 @@ If `channel` or `to` is omitted, cron can fall back to the main session’s “l
|
|||||||
(the last place the agent replied).
|
(the last place the agent replied).
|
||||||
|
|
||||||
Delivery notes:
|
Delivery notes:
|
||||||
|
|
||||||
- If `to` is set, cron auto-delivers the agent’s final output even if `deliver` is omitted.
|
- If `to` is set, cron auto-delivers the agent’s final output even if `deliver` is omitted.
|
||||||
- Use `deliver: true` when you want last-route delivery without an explicit `to`.
|
- Use `deliver: true` when you want last-route delivery without an explicit `to`.
|
||||||
- Use `deliver: false` to keep output internal even if a `to` is present.
|
- Use `deliver: false` to keep output internal even if a `to` is present.
|
||||||
|
|
||||||
Target format reminders:
|
Target format reminders:
|
||||||
|
|
||||||
- Slack/Discord/Mattermost (plugin) targets should use explicit prefixes (e.g. `channel:<id>`, `user:<id>`) to avoid ambiguity.
|
- Slack/Discord/Mattermost (plugin) targets should use explicit prefixes (e.g. `channel:<id>`, `user:<id>`) to avoid ambiguity.
|
||||||
- Telegram topics should use the `:topic:` form (see below).
|
- Telegram topics should use the `:topic:` form (see below).
|
||||||
|
|
||||||
#### Telegram delivery targets (topics / forum threads)
|
#### Telegram delivery targets (topics / forum threads)
|
||||||
|
|
||||||
Telegram supports forum topics via `message_thread_id`. For cron delivery, you can encode
|
Telegram supports forum topics via `message_thread_id`. For cron delivery, you can encode
|
||||||
the topic/thread into the `to` field:
|
the topic/thread into the `to` field:
|
||||||
|
|
||||||
@@ -148,9 +214,87 @@ the topic/thread into the `to` field:
|
|||||||
- `-1001234567890:123` (shorthand: numeric suffix)
|
- `-1001234567890:123` (shorthand: numeric suffix)
|
||||||
|
|
||||||
Prefixed targets like `telegram:...` / `telegram:group:...` are also accepted:
|
Prefixed targets like `telegram:...` / `telegram:group:...` are also accepted:
|
||||||
|
|
||||||
- `telegram:group:-1001234567890:topic:123`
|
- `telegram:group:-1001234567890:topic:123`
|
||||||
|
|
||||||
|
## JSON schema for tool calls
|
||||||
|
|
||||||
|
Use these shapes when calling Gateway `cron.*` tools directly (agent tool calls or RPC).
|
||||||
|
CLI flags accept human durations like `20m`, but tool calls use epoch milliseconds for
|
||||||
|
`atMs` and `everyMs` (ISO timestamps are accepted for `at` times).
|
||||||
|
|
||||||
|
### cron.add params
|
||||||
|
|
||||||
|
One-shot, main session job (system event):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "Reminder",
|
||||||
|
"schedule": { "kind": "at", "atMs": 1738262400000 },
|
||||||
|
"sessionTarget": "main",
|
||||||
|
"wakeMode": "now",
|
||||||
|
"payload": { "kind": "systemEvent", "text": "Reminder text" },
|
||||||
|
"deleteAfterRun": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Recurring, isolated job with delivery:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "Morning brief",
|
||||||
|
"schedule": { "kind": "cron", "expr": "0 7 * * *", "tz": "America/Los_Angeles" },
|
||||||
|
"sessionTarget": "isolated",
|
||||||
|
"wakeMode": "next-heartbeat",
|
||||||
|
"payload": {
|
||||||
|
"kind": "agentTurn",
|
||||||
|
"message": "Summarize overnight updates.",
|
||||||
|
"deliver": true,
|
||||||
|
"channel": "slack",
|
||||||
|
"to": "channel:C1234567890",
|
||||||
|
"bestEffortDeliver": true
|
||||||
|
},
|
||||||
|
"isolation": { "postToMainPrefix": "Cron", "postToMainMode": "summary" }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
- `schedule.kind`: `at` (`atMs`), `every` (`everyMs`), or `cron` (`expr`, optional `tz`).
|
||||||
|
- `atMs` and `everyMs` are epoch milliseconds.
|
||||||
|
- `sessionTarget` must be `"main"` or `"isolated"` and must match `payload.kind`.
|
||||||
|
- Optional fields: `agentId`, `description`, `enabled`, `deleteAfterRun`, `isolation`.
|
||||||
|
- `wakeMode` defaults to `"next-heartbeat"` when omitted.
|
||||||
|
|
||||||
|
### cron.update params
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"jobId": "job-123",
|
||||||
|
"patch": {
|
||||||
|
"enabled": false,
|
||||||
|
"schedule": { "kind": "every", "everyMs": 3600000 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
- `jobId` is canonical; `id` is accepted for compatibility.
|
||||||
|
- Use `agentId: null` in the patch to clear an agent binding.
|
||||||
|
|
||||||
|
### cron.run and cron.remove params
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "jobId": "job-123", "mode": "force" }
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "jobId": "job-123" }
|
||||||
|
```
|
||||||
|
|
||||||
## Storage & history
|
## Storage & history
|
||||||
|
|
||||||
- Job store: `~/.openclaw/cron/jobs.json` (Gateway-managed JSON).
|
- Job store: `~/.openclaw/cron/jobs.json` (Gateway-managed JSON).
|
||||||
- Run history: `~/.openclaw/cron/runs/<jobId>.jsonl` (JSONL, auto-pruned).
|
- Run history: `~/.openclaw/cron/runs/<jobId>.jsonl` (JSONL, auto-pruned).
|
||||||
- Override store path: `cron.store` in config.
|
- Override store path: `cron.store` in config.
|
||||||
@@ -162,18 +306,20 @@ Prefixed targets like `telegram:...` / `telegram:group:...` are also accepted:
|
|||||||
cron: {
|
cron: {
|
||||||
enabled: true, // default true
|
enabled: true, // default true
|
||||||
store: "~/.openclaw/cron/jobs.json",
|
store: "~/.openclaw/cron/jobs.json",
|
||||||
maxConcurrentRuns: 1 // default 1
|
maxConcurrentRuns: 1, // default 1
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Disable cron entirely:
|
Disable cron entirely:
|
||||||
|
|
||||||
- `cron.enabled: false` (config)
|
- `cron.enabled: false` (config)
|
||||||
- `OPENCLAW_SKIP_CRON=1` (env)
|
- `OPENCLAW_SKIP_CRON=1` (env)
|
||||||
|
|
||||||
## CLI quickstart
|
## CLI quickstart
|
||||||
|
|
||||||
One-shot reminder (UTC ISO, auto-delete after success):
|
One-shot reminder (UTC ISO, auto-delete after success):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
openclaw cron add \
|
openclaw cron add \
|
||||||
--name "Send reminder" \
|
--name "Send reminder" \
|
||||||
@@ -185,6 +331,7 @@ openclaw cron add \
|
|||||||
```
|
```
|
||||||
|
|
||||||
One-shot reminder (main session, wake immediately):
|
One-shot reminder (main session, wake immediately):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
openclaw cron add \
|
openclaw cron add \
|
||||||
--name "Calendar check" \
|
--name "Calendar check" \
|
||||||
@@ -195,6 +342,7 @@ openclaw cron add \
|
|||||||
```
|
```
|
||||||
|
|
||||||
Recurring isolated job (deliver to WhatsApp):
|
Recurring isolated job (deliver to WhatsApp):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
openclaw cron add \
|
openclaw cron add \
|
||||||
--name "Morning status" \
|
--name "Morning status" \
|
||||||
@@ -208,6 +356,7 @@ openclaw cron add \
|
|||||||
```
|
```
|
||||||
|
|
||||||
Recurring isolated job (deliver to a Telegram topic):
|
Recurring isolated job (deliver to a Telegram topic):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
openclaw cron add \
|
openclaw cron add \
|
||||||
--name "Nightly summary (topic)" \
|
--name "Nightly summary (topic)" \
|
||||||
@@ -221,6 +370,7 @@ openclaw cron add \
|
|||||||
```
|
```
|
||||||
|
|
||||||
Isolated job with model and thinking override:
|
Isolated job with model and thinking override:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
openclaw cron add \
|
openclaw cron add \
|
||||||
--name "Deep analysis" \
|
--name "Deep analysis" \
|
||||||
@@ -233,8 +383,10 @@ openclaw cron add \
|
|||||||
--deliver \
|
--deliver \
|
||||||
--channel whatsapp \
|
--channel whatsapp \
|
||||||
--to "+15551234567"
|
--to "+15551234567"
|
||||||
|
```
|
||||||
|
|
||||||
Agent selection (multi-agent setups):
|
Agent selection (multi-agent setups):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Pin a job to agent "ops" (falls back to default if that agent is missing)
|
# Pin a job to agent "ops" (falls back to default if that agent is missing)
|
||||||
openclaw cron add --name "Ops sweep" --cron "0 6 * * *" --session isolated --message "Check ops queue" --agent ops
|
openclaw cron add --name "Ops sweep" --cron "0 6 * * *" --session isolated --message "Check ops queue" --agent ops
|
||||||
@@ -243,14 +395,15 @@ openclaw cron add --name "Ops sweep" --cron "0 6 * * *" --session isolated --mes
|
|||||||
openclaw cron edit <jobId> --agent ops
|
openclaw cron edit <jobId> --agent ops
|
||||||
openclaw cron edit <jobId> --clear-agent
|
openclaw cron edit <jobId> --clear-agent
|
||||||
```
|
```
|
||||||
```
|
|
||||||
|
|
||||||
Manual run (debug):
|
Manual run (debug):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
openclaw cron run <jobId> --force
|
openclaw cron run <jobId> --force
|
||||||
```
|
```
|
||||||
|
|
||||||
Edit an existing job (patch fields):
|
Edit an existing job (patch fields):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
openclaw cron edit <jobId> \
|
openclaw cron edit <jobId> \
|
||||||
--message "Updated prompt" \
|
--message "Updated prompt" \
|
||||||
@@ -259,28 +412,33 @@ openclaw cron edit <jobId> \
|
|||||||
```
|
```
|
||||||
|
|
||||||
Run history:
|
Run history:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
openclaw cron runs --id <jobId> --limit 50
|
openclaw cron runs --id <jobId> --limit 50
|
||||||
```
|
```
|
||||||
|
|
||||||
Immediate system event without creating a job:
|
Immediate system event without creating a job:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
openclaw system event --mode now --text "Next heartbeat: check battery."
|
openclaw system event --mode now --text "Next heartbeat: check battery."
|
||||||
```
|
```
|
||||||
|
|
||||||
## Gateway API surface
|
## Gateway API surface
|
||||||
|
|
||||||
- `cron.list`, `cron.status`, `cron.add`, `cron.update`, `cron.remove`
|
- `cron.list`, `cron.status`, `cron.add`, `cron.update`, `cron.remove`
|
||||||
- `cron.run` (force or due), `cron.runs`
|
- `cron.run` (force or due), `cron.runs`
|
||||||
For immediate system events without a job, use [`openclaw system event`](/cli/system).
|
For immediate system events without a job, use [`openclaw system event`](/cli/system).
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
### “Nothing runs”
|
### “Nothing runs”
|
||||||
|
|
||||||
- Check cron is enabled: `cron.enabled` and `OPENCLAW_SKIP_CRON`.
|
- Check cron is enabled: `cron.enabled` and `OPENCLAW_SKIP_CRON`.
|
||||||
- Check the Gateway is running continuously (cron runs inside the Gateway process).
|
- Check the Gateway is running continuously (cron runs inside the Gateway process).
|
||||||
- For `cron` schedules: confirm timezone (`--tz`) vs the host timezone.
|
- For `cron` schedules: confirm timezone (`--tz`) vs the host timezone.
|
||||||
|
|
||||||
### Telegram delivers to the wrong place
|
### Telegram delivers to the wrong place
|
||||||
|
|
||||||
- For forum topics, use `-100…:topic:<id>` so it’s explicit and unambiguous.
|
- For forum topics, use `-100…:topic:<id>` so it’s explicit and unambiguous.
|
||||||
- If you see `telegram:...` prefixes in logs or stored “last route” targets, that’s normal;
|
- If you see `telegram:...` prefixes in logs or stored “last route” targets, that’s normal;
|
||||||
cron delivery accepts them and still parses topic IDs correctly.
|
cron delivery accepts them and still parses topic IDs correctly.
|
||||||
|
|||||||
@@ -4,21 +4,23 @@ read_when:
|
|||||||
- Deciding how to schedule recurring tasks
|
- Deciding how to schedule recurring tasks
|
||||||
- Setting up background monitoring or notifications
|
- Setting up background monitoring or notifications
|
||||||
- Optimizing token usage for periodic checks
|
- Optimizing token usage for periodic checks
|
||||||
|
title: "Cron vs Heartbeat"
|
||||||
---
|
---
|
||||||
|
|
||||||
# Cron vs Heartbeat: When to Use Each
|
# Cron vs Heartbeat: When to Use Each
|
||||||
|
|
||||||
Both heartbeats and cron jobs let you run tasks on a schedule. This guide helps you choose the right mechanism for your use case.
|
Both heartbeats and cron jobs let you run tasks on a schedule. This guide helps you choose the right mechanism for your use case.
|
||||||
|
|
||||||
## Quick Decision Guide
|
## Quick Decision Guide
|
||||||
|
|
||||||
| Use Case | Recommended | Why |
|
| Use Case | Recommended | Why |
|
||||||
|----------|-------------|-----|
|
| ------------------------------------ | ------------------- | ---------------------------------------- |
|
||||||
| Check inbox every 30 min | Heartbeat | Batches with other checks, context-aware |
|
| Check inbox every 30 min | Heartbeat | Batches with other checks, context-aware |
|
||||||
| Send daily report at 9am sharp | Cron (isolated) | Exact timing needed |
|
| Send daily report at 9am sharp | Cron (isolated) | Exact timing needed |
|
||||||
| Monitor calendar for upcoming events | Heartbeat | Natural fit for periodic awareness |
|
| Monitor calendar for upcoming events | Heartbeat | Natural fit for periodic awareness |
|
||||||
| Run weekly deep analysis | Cron (isolated) | Standalone task, can use different model |
|
| Run weekly deep analysis | Cron (isolated) | Standalone task, can use different model |
|
||||||
| Remind me in 20 minutes | Cron (main, `--at`) | One-shot with precise timing |
|
| Remind me in 20 minutes | Cron (main, `--at`) | One-shot with precise timing |
|
||||||
| Background project health check | Heartbeat | Piggybacks on existing cycle |
|
| Background project health check | Heartbeat | Piggybacks on existing cycle |
|
||||||
|
|
||||||
## Heartbeat: Periodic Awareness
|
## Heartbeat: Periodic Awareness
|
||||||
|
|
||||||
@@ -59,12 +61,12 @@ The agent reads this on each heartbeat and handles all items in one turn.
|
|||||||
agents: {
|
agents: {
|
||||||
defaults: {
|
defaults: {
|
||||||
heartbeat: {
|
heartbeat: {
|
||||||
every: "30m", // interval
|
every: "30m", // interval
|
||||||
target: "last", // where to deliver alerts
|
target: "last", // where to deliver alerts
|
||||||
activeHours: { start: "08:00", end: "22:00" } // optional
|
activeHours: { start: "08:00", end: "22:00" }, // optional
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -157,8 +159,10 @@ The most efficient setup uses **both**:
|
|||||||
### Example: Efficient automation setup
|
### Example: Efficient automation setup
|
||||||
|
|
||||||
**HEARTBEAT.md** (checked every 30 min):
|
**HEARTBEAT.md** (checked every 30 min):
|
||||||
|
|
||||||
```md
|
```md
|
||||||
# Heartbeat checklist
|
# Heartbeat checklist
|
||||||
|
|
||||||
- Scan inbox for urgent emails
|
- Scan inbox for urgent emails
|
||||||
- Check calendar for events in next 2h
|
- Check calendar for events in next 2h
|
||||||
- Review any pending tasks
|
- Review any pending tasks
|
||||||
@@ -166,6 +170,7 @@ The most efficient setup uses **both**:
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Cron jobs** (precise timing):
|
**Cron jobs** (precise timing):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Daily morning briefing at 7am
|
# Daily morning briefing at 7am
|
||||||
openclaw cron add --name "Morning brief" --cron "0 7 * * *" --session isolated --message "..." --deliver
|
openclaw cron add --name "Morning brief" --cron "0 7 * * *" --session isolated --message "..." --deliver
|
||||||
@@ -177,7 +182,6 @@ openclaw cron add --name "Weekly review" --cron "0 9 * * 1" --session isolated -
|
|||||||
openclaw cron add --name "Call back" --at "2h" --session main --system-event "Call back the client" --wake now
|
openclaw cron add --name "Call back" --at "2h" --session main --system-event "Call back the client" --wake now
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Lobster: Deterministic workflows with approvals
|
## Lobster: Deterministic workflows with approvals
|
||||||
|
|
||||||
Lobster is the workflow runtime for **multi-step tool pipelines** that need deterministic execution and explicit approvals.
|
Lobster is the workflow runtime for **multi-step tool pipelines** that need deterministic execution and explicit approvals.
|
||||||
@@ -191,8 +195,8 @@ Use it when the task is more than a single agent turn, and you want a resumable
|
|||||||
|
|
||||||
### How it pairs with heartbeat and cron
|
### How it pairs with heartbeat and cron
|
||||||
|
|
||||||
- **Heartbeat/cron** decide *when* a run happens.
|
- **Heartbeat/cron** decide _when_ a run happens.
|
||||||
- **Lobster** defines *what steps* happen once the run starts.
|
- **Lobster** defines _what steps_ happen once the run starts.
|
||||||
|
|
||||||
For scheduled workflows, use cron or heartbeat to trigger an agent turn that calls Lobster.
|
For scheduled workflows, use cron or heartbeat to trigger an agent turn that calls Lobster.
|
||||||
For ad-hoc workflows, call Lobster directly.
|
For ad-hoc workflows, call Lobster directly.
|
||||||
@@ -210,17 +214,18 @@ See [Lobster](/tools/lobster) for full usage and examples.
|
|||||||
|
|
||||||
Both heartbeat and cron can interact with the main session, but differently:
|
Both heartbeat and cron can interact with the main session, but differently:
|
||||||
|
|
||||||
| | Heartbeat | Cron (main) | Cron (isolated) |
|
| | Heartbeat | Cron (main) | Cron (isolated) |
|
||||||
|---|---|---|---|
|
| ------- | ------------------------------- | ------------------------ | ---------------------- |
|
||||||
| Session | Main | Main (via system event) | `cron:<jobId>` |
|
| Session | Main | Main (via system event) | `cron:<jobId>` |
|
||||||
| History | Shared | Shared | Fresh each run |
|
| History | Shared | Shared | Fresh each run |
|
||||||
| Context | Full | Full | None (starts clean) |
|
| Context | Full | Full | None (starts clean) |
|
||||||
| Model | Main session model | Main session model | Can override |
|
| Model | Main session model | Main session model | Can override |
|
||||||
| Output | Delivered if not `HEARTBEAT_OK` | Heartbeat prompt + event | Summary posted to main |
|
| Output | Delivered if not `HEARTBEAT_OK` | Heartbeat prompt + event | Summary posted to main |
|
||||||
|
|
||||||
### When to use main session cron
|
### When to use main session cron
|
||||||
|
|
||||||
Use `--session main` with `--system-event` when you want:
|
Use `--session main` with `--system-event` when you want:
|
||||||
|
|
||||||
- The reminder/event to appear in main session context
|
- The reminder/event to appear in main session context
|
||||||
- The agent to handle it during the next heartbeat with full context
|
- The agent to handle it during the next heartbeat with full context
|
||||||
- No separate isolated run
|
- No separate isolated run
|
||||||
@@ -237,6 +242,7 @@ openclaw cron add \
|
|||||||
### When to use isolated cron
|
### When to use isolated cron
|
||||||
|
|
||||||
Use `--session isolated` when you want:
|
Use `--session isolated` when you want:
|
||||||
|
|
||||||
- A clean slate without prior context
|
- A clean slate without prior context
|
||||||
- Different model or thinking settings
|
- Different model or thinking settings
|
||||||
- Output delivered directly to a channel (summary still posts to main by default)
|
- Output delivered directly to a channel (summary still posts to main by default)
|
||||||
@@ -255,13 +261,14 @@ openclaw cron add \
|
|||||||
|
|
||||||
## Cost Considerations
|
## Cost Considerations
|
||||||
|
|
||||||
| Mechanism | Cost Profile |
|
| Mechanism | Cost Profile |
|
||||||
|-----------|--------------|
|
| --------------- | ------------------------------------------------------- |
|
||||||
| Heartbeat | One turn every N minutes; scales with HEARTBEAT.md size |
|
| Heartbeat | One turn every N minutes; scales with HEARTBEAT.md size |
|
||||||
| Cron (main) | Adds event to next heartbeat (no isolated turn) |
|
| Cron (main) | Adds event to next heartbeat (no isolated turn) |
|
||||||
| Cron (isolated) | Full agent turn per job; can use cheaper model |
|
| Cron (isolated) | Full agent turn per job; can use cheaper model |
|
||||||
|
|
||||||
**Tips**:
|
**Tips**:
|
||||||
|
|
||||||
- Keep `HEARTBEAT.md` small to minimize token overhead.
|
- Keep `HEARTBEAT.md` small to minimize token overhead.
|
||||||
- Batch similar checks into heartbeat instead of multiple cron jobs.
|
- Batch similar checks into heartbeat instead of multiple cron jobs.
|
||||||
- Use `target: "none"` on heartbeat if you only want internal processing.
|
- Use `target: "none"` on heartbeat if you only want internal processing.
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ summary: "Gmail Pub/Sub push wired into OpenClaw webhooks via gogcli"
|
|||||||
read_when:
|
read_when:
|
||||||
- Wiring Gmail inbox triggers to OpenClaw
|
- Wiring Gmail inbox triggers to OpenClaw
|
||||||
- Setting up Pub/Sub push for agent wake
|
- Setting up Pub/Sub push for agent wake
|
||||||
|
title: "Gmail PubSub"
|
||||||
---
|
---
|
||||||
|
|
||||||
# Gmail Pub/Sub -> OpenClaw
|
# Gmail Pub/Sub -> OpenClaw
|
||||||
@@ -26,8 +27,8 @@ Example hook config (enable Gmail preset mapping):
|
|||||||
enabled: true,
|
enabled: true,
|
||||||
token: "OPENCLAW_HOOK_TOKEN",
|
token: "OPENCLAW_HOOK_TOKEN",
|
||||||
path: "/hooks",
|
path: "/hooks",
|
||||||
presets: ["gmail"]
|
presets: ["gmail"],
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -47,15 +48,14 @@ that sets `deliver` + optional `channel`/`to`:
|
|||||||
wakeMode: "now",
|
wakeMode: "now",
|
||||||
name: "Gmail",
|
name: "Gmail",
|
||||||
sessionKey: "hook:gmail:{{messages[0].id}}",
|
sessionKey: "hook:gmail:{{messages[0].id}}",
|
||||||
messageTemplate:
|
messageTemplate: "New email from {{messages[0].from}}\nSubject: {{messages[0].subject}}\n{{messages[0].snippet}}\n{{messages[0].body}}",
|
||||||
"New email from {{messages[0].from}}\nSubject: {{messages[0].subject}}\n{{messages[0].snippet}}\n{{messages[0].body}}",
|
|
||||||
model: "openai/gpt-5.2-mini",
|
model: "openai/gpt-5.2-mini",
|
||||||
deliver: true,
|
deliver: true,
|
||||||
channel: "last"
|
channel: "last",
|
||||||
// to: "+15551234567"
|
// to: "+15551234567"
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -73,13 +73,14 @@ To set a default model and thinking level specifically for Gmail hooks, add
|
|||||||
hooks: {
|
hooks: {
|
||||||
gmail: {
|
gmail: {
|
||||||
model: "openrouter/meta-llama/llama-3.3-70b-instruct:free",
|
model: "openrouter/meta-llama/llama-3.3-70b-instruct:free",
|
||||||
thinking: "off"
|
thinking: "off",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
- Per-hook `model`/`thinking` in the mapping still overrides these defaults.
|
- Per-hook `model`/`thinking` in the mapping still overrides these defaults.
|
||||||
- Fallback order: `hooks.gmail.model` → `agents.defaults.model.fallbacks` → primary (auth/rate-limit/timeouts).
|
- Fallback order: `hooks.gmail.model` → `agents.defaults.model.fallbacks` → primary (auth/rate-limit/timeouts).
|
||||||
- If `agents.defaults.models` is set, the Gmail model must be in the allowlist.
|
- If `agents.defaults.models` is set, the Gmail model must be in the allowlist.
|
||||||
@@ -99,6 +100,7 @@ openclaw webhooks gmail setup \
|
|||||||
```
|
```
|
||||||
|
|
||||||
Defaults:
|
Defaults:
|
||||||
|
|
||||||
- Uses Tailscale Funnel for the public push endpoint.
|
- Uses Tailscale Funnel for the public push endpoint.
|
||||||
- Writes `hooks.gmail` config for `openclaw webhooks gmail run`.
|
- Writes `hooks.gmail` config for `openclaw webhooks gmail run`.
|
||||||
- Enables the Gmail hook preset (`hooks.presets: ["gmail"]`).
|
- Enables the Gmail hook preset (`hooks.presets: ["gmail"]`).
|
||||||
@@ -117,6 +119,7 @@ Platform note: on macOS the wizard installs `gcloud`, `gogcli`, and `tailscale`
|
|||||||
via Homebrew; on Linux install them manually first.
|
via Homebrew; on Linux install them manually first.
|
||||||
|
|
||||||
Gateway auto-start (recommended):
|
Gateway auto-start (recommended):
|
||||||
|
|
||||||
- When `hooks.enabled=true` and `hooks.gmail.account` is set, the Gateway starts
|
- When `hooks.enabled=true` and `hooks.gmail.account` is set, the Gateway starts
|
||||||
`gog gmail watch serve` on boot and auto-renews the watch.
|
`gog gmail watch serve` on boot and auto-renews the watch.
|
||||||
- Set `OPENCLAW_SKIP_GMAIL_WATCHER=1` to opt out (useful if you run the daemon yourself).
|
- Set `OPENCLAW_SKIP_GMAIL_WATCHER=1` to opt out (useful if you run the daemon yourself).
|
||||||
@@ -131,7 +134,7 @@ openclaw webhooks gmail run
|
|||||||
|
|
||||||
## One-time setup
|
## One-time setup
|
||||||
|
|
||||||
1) Select the GCP project **that owns the OAuth client** used by `gog`.
|
1. Select the GCP project **that owns the OAuth client** used by `gog`.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
gcloud auth login
|
gcloud auth login
|
||||||
@@ -140,19 +143,19 @@ gcloud config set project <project-id>
|
|||||||
|
|
||||||
Note: Gmail watch requires the Pub/Sub topic to live in the same project as the OAuth client.
|
Note: Gmail watch requires the Pub/Sub topic to live in the same project as the OAuth client.
|
||||||
|
|
||||||
2) Enable APIs:
|
2. Enable APIs:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
gcloud services enable gmail.googleapis.com pubsub.googleapis.com
|
gcloud services enable gmail.googleapis.com pubsub.googleapis.com
|
||||||
```
|
```
|
||||||
|
|
||||||
3) Create a topic:
|
3. Create a topic:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
gcloud pubsub topics create gog-gmail-watch
|
gcloud pubsub topics create gog-gmail-watch
|
||||||
```
|
```
|
||||||
|
|
||||||
4) Allow Gmail push to publish:
|
4. Allow Gmail push to publish:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
gcloud pubsub topics add-iam-policy-binding gog-gmail-watch \
|
gcloud pubsub topics add-iam-policy-binding gog-gmail-watch \
|
||||||
@@ -189,6 +192,7 @@ gog gmail watch serve \
|
|||||||
```
|
```
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
- `--token` protects the push endpoint (`x-gog-token` or `?token=`).
|
- `--token` protects the push endpoint (`x-gog-token` or `?token=`).
|
||||||
- `--hook-url` points to OpenClaw `/hooks/gmail` (mapped; isolated run + summary to main).
|
- `--hook-url` points to OpenClaw `/hooks/gmail` (mapped; isolated run + summary to main).
|
||||||
- `--include-body` and `--max-bytes` control the body snippet sent to OpenClaw.
|
- `--include-body` and `--max-bytes` control the body snippet sent to OpenClaw.
|
||||||
|
|||||||
@@ -3,11 +3,13 @@ summary: "Poll sending via gateway + CLI"
|
|||||||
read_when:
|
read_when:
|
||||||
- Adding or modifying poll support
|
- Adding or modifying poll support
|
||||||
- Debugging poll sends from the CLI or gateway
|
- Debugging poll sends from the CLI or gateway
|
||||||
|
title: "Polls"
|
||||||
---
|
---
|
||||||
|
|
||||||
# Polls
|
# Polls
|
||||||
|
|
||||||
|
|
||||||
## Supported channels
|
## Supported channels
|
||||||
|
|
||||||
- WhatsApp (web channel)
|
- WhatsApp (web channel)
|
||||||
- Discord
|
- Discord
|
||||||
- MS Teams (Adaptive Cards)
|
- MS Teams (Adaptive Cards)
|
||||||
@@ -33,6 +35,7 @@ openclaw message poll --channel msteams --target conversation:19:abc@thread.tacv
|
|||||||
```
|
```
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|
||||||
- `--channel`: `whatsapp` (default), `discord`, or `msteams`
|
- `--channel`: `whatsapp` (default), `discord`, or `msteams`
|
||||||
- `--poll-multi`: allow selecting multiple options
|
- `--poll-multi`: allow selecting multiple options
|
||||||
- `--poll-duration-hours`: Discord-only (defaults to 24 when omitted)
|
- `--poll-duration-hours`: Discord-only (defaults to 24 when omitted)
|
||||||
@@ -42,6 +45,7 @@ Options:
|
|||||||
Method: `poll`
|
Method: `poll`
|
||||||
|
|
||||||
Params:
|
Params:
|
||||||
|
|
||||||
- `to` (string, required)
|
- `to` (string, required)
|
||||||
- `question` (string, required)
|
- `question` (string, required)
|
||||||
- `options` (string[], required)
|
- `options` (string[], required)
|
||||||
@@ -51,11 +55,13 @@ Params:
|
|||||||
- `idempotencyKey` (string, required)
|
- `idempotencyKey` (string, required)
|
||||||
|
|
||||||
## Channel differences
|
## Channel differences
|
||||||
|
|
||||||
- WhatsApp: 2-12 options, `maxSelections` must be within option count, ignores `durationHours`.
|
- WhatsApp: 2-12 options, `maxSelections` must be within option count, ignores `durationHours`.
|
||||||
- Discord: 2-10 options, `durationHours` clamped to 1-768 hours (default 24). `maxSelections > 1` enables multi-select; Discord does not support a strict selection count.
|
- Discord: 2-10 options, `durationHours` clamped to 1-768 hours (default 24). `maxSelections > 1` enables multi-select; Discord does not support a strict selection count.
|
||||||
- MS Teams: Adaptive Card polls (OpenClaw-managed). No native poll API; `durationHours` is ignored.
|
- MS Teams: Adaptive Card polls (OpenClaw-managed). No native poll API; `durationHours` is ignored.
|
||||||
|
|
||||||
## Agent tool (Message)
|
## Agent tool (Message)
|
||||||
|
|
||||||
Use the `message` tool with `poll` action (`to`, `pollQuestion`, `pollOption`, optional `pollMulti`, `pollDurationHours`, `channel`).
|
Use the `message` tool with `poll` action (`to`, `pollQuestion`, `pollOption`, optional `pollMulti`, `pollDurationHours`, `channel`).
|
||||||
|
|
||||||
Note: Discord has no “pick exactly N” mode; `pollMulti` maps to multi-select.
|
Note: Discord has no “pick exactly N” mode; `pollMulti` maps to multi-select.
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ summary: "Webhook ingress for wake and isolated agent runs"
|
|||||||
read_when:
|
read_when:
|
||||||
- Adding or changing webhook endpoints
|
- Adding or changing webhook endpoints
|
||||||
- Wiring external systems into OpenClaw
|
- Wiring external systems into OpenClaw
|
||||||
|
title: "Webhooks"
|
||||||
---
|
---
|
||||||
|
|
||||||
# Webhooks
|
# Webhooks
|
||||||
@@ -16,18 +17,20 @@ Gateway can expose a small HTTP webhook endpoint for external triggers.
|
|||||||
hooks: {
|
hooks: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
token: "shared-secret",
|
token: "shared-secret",
|
||||||
path: "/hooks"
|
path: "/hooks",
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
- `hooks.token` is required when `hooks.enabled=true`.
|
- `hooks.token` is required when `hooks.enabled=true`.
|
||||||
- `hooks.path` defaults to `/hooks`.
|
- `hooks.path` defaults to `/hooks`.
|
||||||
|
|
||||||
## Auth
|
## Auth
|
||||||
|
|
||||||
Every request must include the hook token. Prefer headers:
|
Every request must include the hook token. Prefer headers:
|
||||||
|
|
||||||
- `Authorization: Bearer <token>` (recommended)
|
- `Authorization: Bearer <token>` (recommended)
|
||||||
- `x-openclaw-token: <token>`
|
- `x-openclaw-token: <token>`
|
||||||
- `?token=<token>` (deprecated; logs a warning and will be removed in a future major release)
|
- `?token=<token>` (deprecated; logs a warning and will be removed in a future major release)
|
||||||
@@ -37,6 +40,7 @@ Every request must include the hook token. Prefer headers:
|
|||||||
### `POST /hooks/wake`
|
### `POST /hooks/wake`
|
||||||
|
|
||||||
Payload:
|
Payload:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{ "text": "System line", "mode": "now" }
|
{ "text": "System line", "mode": "now" }
|
||||||
```
|
```
|
||||||
@@ -45,12 +49,14 @@ Payload:
|
|||||||
- `mode` optional (`now` | `next-heartbeat`): Whether to trigger an immediate heartbeat (default `now`) or wait for the next periodic check.
|
- `mode` optional (`now` | `next-heartbeat`): Whether to trigger an immediate heartbeat (default `now`) or wait for the next periodic check.
|
||||||
|
|
||||||
Effect:
|
Effect:
|
||||||
|
|
||||||
- Enqueues a system event for the **main** session
|
- Enqueues a system event for the **main** session
|
||||||
- If `mode=now`, triggers an immediate heartbeat
|
- If `mode=now`, triggers an immediate heartbeat
|
||||||
|
|
||||||
### `POST /hooks/agent`
|
### `POST /hooks/agent`
|
||||||
|
|
||||||
Payload:
|
Payload:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"message": "Run this",
|
"message": "Run this",
|
||||||
@@ -78,6 +84,7 @@ Payload:
|
|||||||
- `timeoutSeconds` optional (number): Maximum duration for the agent run in seconds.
|
- `timeoutSeconds` optional (number): Maximum duration for the agent run in seconds.
|
||||||
|
|
||||||
Effect:
|
Effect:
|
||||||
|
|
||||||
- Runs an **isolated** agent turn (own session key)
|
- Runs an **isolated** agent turn (own session key)
|
||||||
- Always posts a summary into the **main** session
|
- Always posts a summary into the **main** session
|
||||||
- If `wakeMode=now`, triggers an immediate heartbeat
|
- If `wakeMode=now`, triggers an immediate heartbeat
|
||||||
@@ -89,6 +96,7 @@ turn arbitrary payloads into `wake` or `agent` actions, with optional templates
|
|||||||
code transforms.
|
code transforms.
|
||||||
|
|
||||||
Mapping options (summary):
|
Mapping options (summary):
|
||||||
|
|
||||||
- `hooks.presets: ["gmail"]` enables the built-in Gmail mapping.
|
- `hooks.presets: ["gmail"]` enables the built-in Gmail mapping.
|
||||||
- `hooks.mappings` lets you define `match`, `action`, and templates in config.
|
- `hooks.mappings` lets you define `match`, `action`, and templates in config.
|
||||||
- `hooks.transformsDir` + `transform.module` loads a JS/TS module for custom logic.
|
- `hooks.transformsDir` + `transform.module` loads a JS/TS module for custom logic.
|
||||||
@@ -99,7 +107,7 @@ Mapping options (summary):
|
|||||||
- `allowUnsafeExternalContent: true` disables the external content safety wrapper for that hook
|
- `allowUnsafeExternalContent: true` disables the external content safety wrapper for that hook
|
||||||
(dangerous; only for trusted internal sources).
|
(dangerous; only for trusted internal sources).
|
||||||
- `openclaw webhooks gmail setup` writes `hooks.gmail` config for `openclaw webhooks gmail run`.
|
- `openclaw webhooks gmail setup` writes `hooks.gmail` config for `openclaw webhooks gmail run`.
|
||||||
See [Gmail Pub/Sub](/automation/gmail-pubsub) for the full Gmail watch flow.
|
See [Gmail Pub/Sub](/automation/gmail-pubsub) for the full Gmail watch flow.
|
||||||
|
|
||||||
## Responses
|
## Responses
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ summary: "Use Amazon Bedrock (Converse API) models with OpenClaw"
|
|||||||
read_when:
|
read_when:
|
||||||
- You want to use Amazon Bedrock models with OpenClaw
|
- You want to use Amazon Bedrock models with OpenClaw
|
||||||
- You need AWS credential/region setup for model calls
|
- You need AWS credential/region setup for model calls
|
||||||
|
title: "Amazon Bedrock"
|
||||||
---
|
---
|
||||||
|
|
||||||
# Amazon Bedrock
|
# Amazon Bedrock
|
||||||
|
|
||||||
OpenClaw can use **Amazon Bedrock** models via pi‑ai’s **Bedrock Converse**
|
OpenClaw can use **Amazon Bedrock** models via pi‑ai’s **Bedrock Converse**
|
||||||
@@ -34,13 +36,14 @@ Config options live under `models.bedrockDiscovery`:
|
|||||||
providerFilter: ["anthropic", "amazon"],
|
providerFilter: ["anthropic", "amazon"],
|
||||||
refreshInterval: 3600,
|
refreshInterval: 3600,
|
||||||
defaultContextWindow: 32000,
|
defaultContextWindow: 32000,
|
||||||
defaultMaxTokens: 4096
|
defaultMaxTokens: 4096,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
- `enabled` defaults to `true` when AWS credentials are present.
|
- `enabled` defaults to `true` when AWS credentials are present.
|
||||||
- `region` defaults to `AWS_REGION` or `AWS_DEFAULT_REGION`, then `us-east-1`.
|
- `region` defaults to `AWS_REGION` or `AWS_DEFAULT_REGION`, then `us-east-1`.
|
||||||
- `providerFilter` matches Bedrock provider names (for example `anthropic`).
|
- `providerFilter` matches Bedrock provider names (for example `anthropic`).
|
||||||
@@ -50,7 +53,7 @@ Notes:
|
|||||||
|
|
||||||
## Setup (manual)
|
## Setup (manual)
|
||||||
|
|
||||||
1) Ensure AWS credentials are available on the **gateway host**:
|
1. Ensure AWS credentials are available on the **gateway host**:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
export AWS_ACCESS_KEY_ID="AKIA..."
|
export AWS_ACCESS_KEY_ID="AKIA..."
|
||||||
@@ -63,7 +66,7 @@ export AWS_PROFILE="your-profile"
|
|||||||
export AWS_BEARER_TOKEN_BEDROCK="..."
|
export AWS_BEARER_TOKEN_BEDROCK="..."
|
||||||
```
|
```
|
||||||
|
|
||||||
2) Add a Bedrock provider and model to your config (no `apiKey` required):
|
2. Add a Bedrock provider and model to your config (no `apiKey` required):
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
@@ -81,17 +84,17 @@ export AWS_BEARER_TOKEN_BEDROCK="..."
|
|||||||
input: ["text", "image"],
|
input: ["text", "image"],
|
||||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||||
contextWindow: 200000,
|
contextWindow: 200000,
|
||||||
maxTokens: 8192
|
maxTokens: 8192,
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
agents: {
|
agents: {
|
||||||
defaults: {
|
defaults: {
|
||||||
model: { primary: "amazon-bedrock/anthropic.claude-opus-4-5-20251101-v1:0" }
|
model: { primary: "amazon-bedrock/anthropic.claude-opus-4-5-20251101-v1:0" },
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -112,6 +115,7 @@ export AWS_REGION=us-east-1
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Required IAM permissions** for the EC2 instance role:
|
**Required IAM permissions** for the EC2 instance role:
|
||||||
|
|
||||||
- `bedrock:InvokeModel`
|
- `bedrock:InvokeModel`
|
||||||
- `bedrock:InvokeModelWithResponseStream`
|
- `bedrock:InvokeModelWithResponseStream`
|
||||||
- `bedrock:ListFoundationModels` (for automatic discovery)
|
- `bedrock:ListFoundationModels` (for automatic discovery)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ summary: "Brave Search API setup for web_search"
|
|||||||
read_when:
|
read_when:
|
||||||
- You want to use Brave Search for web_search
|
- You want to use Brave Search for web_search
|
||||||
- You need a BRAVE_API_KEY or plan details
|
- You need a BRAVE_API_KEY or plan details
|
||||||
|
title: "Brave Search"
|
||||||
---
|
---
|
||||||
|
|
||||||
# Brave Search API
|
# Brave Search API
|
||||||
@@ -11,9 +12,9 @@ OpenClaw uses Brave Search as the default provider for `web_search`.
|
|||||||
|
|
||||||
## Get an API key
|
## Get an API key
|
||||||
|
|
||||||
1) Create a Brave Search API account at https://brave.com/search/api/
|
1. Create a Brave Search API account at https://brave.com/search/api/
|
||||||
2) In the dashboard, choose the **Data for Search** plan and generate an API key.
|
2. In the dashboard, choose the **Data for Search** plan and generate an API key.
|
||||||
3) Store the key in config (recommended) or set `BRAVE_API_KEY` in the Gateway environment.
|
3. Store the key in config (recommended) or set `BRAVE_API_KEY` in the Gateway environment.
|
||||||
|
|
||||||
## Config example
|
## Config example
|
||||||
|
|
||||||
@@ -25,10 +26,10 @@ OpenClaw uses Brave Search as the default provider for `web_search`.
|
|||||||
provider: "brave",
|
provider: "brave",
|
||||||
apiKey: "BRAVE_API_KEY_HERE",
|
apiKey: "BRAVE_API_KEY_HERE",
|
||||||
maxResults: 5,
|
maxResults: 5,
|
||||||
timeoutSeconds: 30
|
timeoutSeconds: 30,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ read_when:
|
|||||||
- Configuring broadcast groups
|
- Configuring broadcast groups
|
||||||
- Debugging multi-agent replies in WhatsApp
|
- Debugging multi-agent replies in WhatsApp
|
||||||
status: experimental
|
status: experimental
|
||||||
|
title: "Broadcast Groups"
|
||||||
---
|
---
|
||||||
|
|
||||||
# Broadcast Groups
|
# Broadcast Groups
|
||||||
@@ -22,7 +23,9 @@ Broadcast groups are evaluated after channel allowlists and group activation rul
|
|||||||
## Use Cases
|
## Use Cases
|
||||||
|
|
||||||
### 1. Specialized Agent Teams
|
### 1. Specialized Agent Teams
|
||||||
|
|
||||||
Deploy multiple agents with atomic, focused responsibilities:
|
Deploy multiple agents with atomic, focused responsibilities:
|
||||||
|
|
||||||
```
|
```
|
||||||
Group: "Development Team"
|
Group: "Development Team"
|
||||||
Agents:
|
Agents:
|
||||||
@@ -35,6 +38,7 @@ Agents:
|
|||||||
Each agent processes the same message and provides its specialized perspective.
|
Each agent processes the same message and provides its specialized perspective.
|
||||||
|
|
||||||
### 2. Multi-Language Support
|
### 2. Multi-Language Support
|
||||||
|
|
||||||
```
|
```
|
||||||
Group: "International Support"
|
Group: "International Support"
|
||||||
Agents:
|
Agents:
|
||||||
@@ -44,6 +48,7 @@ Agents:
|
|||||||
```
|
```
|
||||||
|
|
||||||
### 3. Quality Assurance Workflows
|
### 3. Quality Assurance Workflows
|
||||||
|
|
||||||
```
|
```
|
||||||
Group: "Customer Support"
|
Group: "Customer Support"
|
||||||
Agents:
|
Agents:
|
||||||
@@ -52,6 +57,7 @@ Agents:
|
|||||||
```
|
```
|
||||||
|
|
||||||
### 4. Task Automation
|
### 4. Task Automation
|
||||||
|
|
||||||
```
|
```
|
||||||
Group: "Project Management"
|
Group: "Project Management"
|
||||||
Agents:
|
Agents:
|
||||||
@@ -65,6 +71,7 @@ Agents:
|
|||||||
### Basic Setup
|
### Basic Setup
|
||||||
|
|
||||||
Add a top-level `broadcast` section (next to `bindings`). Keys are WhatsApp peer ids:
|
Add a top-level `broadcast` section (next to `bindings`). Keys are WhatsApp peer ids:
|
||||||
|
|
||||||
- group chats: group JID (e.g. `120363403215116621@g.us`)
|
- group chats: group JID (e.g. `120363403215116621@g.us`)
|
||||||
- DMs: E.164 phone number (e.g. `+15551234567`)
|
- DMs: E.164 phone number (e.g. `+15551234567`)
|
||||||
|
|
||||||
@@ -83,7 +90,9 @@ Add a top-level `broadcast` section (next to `bindings`). Keys are WhatsApp peer
|
|||||||
Control how agents process messages:
|
Control how agents process messages:
|
||||||
|
|
||||||
#### Parallel (Default)
|
#### Parallel (Default)
|
||||||
|
|
||||||
All agents process simultaneously:
|
All agents process simultaneously:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"broadcast": {
|
"broadcast": {
|
||||||
@@ -94,7 +103,9 @@ All agents process simultaneously:
|
|||||||
```
|
```
|
||||||
|
|
||||||
#### Sequential
|
#### Sequential
|
||||||
|
|
||||||
Agents process in order (one waits for previous to finish):
|
Agents process in order (one waits for previous to finish):
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"broadcast": {
|
"broadcast": {
|
||||||
@@ -152,7 +163,7 @@ Agents process in order (one waits for previous to finish):
|
|||||||
4. **If not in broadcast list**:
|
4. **If not in broadcast list**:
|
||||||
- Normal routing applies (first matching binding)
|
- Normal routing applies (first matching binding)
|
||||||
|
|
||||||
Note: broadcast groups do not bypass channel allowlists or group activation rules (mentions/commands/etc). They only change *which agents run* when a message is eligible for processing.
|
Note: broadcast groups do not bypass channel allowlists or group activation rules (mentions/commands/etc). They only change _which agents run_ when a message is eligible for processing.
|
||||||
|
|
||||||
### Session Isolation
|
### Session Isolation
|
||||||
|
|
||||||
@@ -166,6 +177,7 @@ Each agent in a broadcast group maintains completely separate:
|
|||||||
- **Group context buffer** (recent group messages used for context) is shared per peer, so all broadcast agents see the same context when triggered
|
- **Group context buffer** (recent group messages used for context) is shared per peer, so all broadcast agents see the same context when triggered
|
||||||
|
|
||||||
This allows each agent to have:
|
This allows each agent to have:
|
||||||
|
|
||||||
- Different personalities
|
- Different personalities
|
||||||
- Different tool access (e.g., read-only vs. read-write)
|
- Different tool access (e.g., read-only vs. read-write)
|
||||||
- Different models (e.g., opus vs. sonnet)
|
- Different models (e.g., opus vs. sonnet)
|
||||||
@@ -176,6 +188,7 @@ This allows each agent to have:
|
|||||||
In group `120363403215116621@g.us` with agents `["alfred", "baerbel"]`:
|
In group `120363403215116621@g.us` with agents `["alfred", "baerbel"]`:
|
||||||
|
|
||||||
**Alfred's context:**
|
**Alfred's context:**
|
||||||
|
|
||||||
```
|
```
|
||||||
Session: agent:alfred:whatsapp:group:120363403215116621@g.us
|
Session: agent:alfred:whatsapp:group:120363403215116621@g.us
|
||||||
History: [user message, alfred's previous responses]
|
History: [user message, alfred's previous responses]
|
||||||
@@ -184,8 +197,9 @@ Tools: read, write, exec
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Bärbel's context:**
|
**Bärbel's context:**
|
||||||
|
|
||||||
```
|
```
|
||||||
Session: agent:baerbel:whatsapp:group:120363403215116621@g.us
|
Session: agent:baerbel:whatsapp:group:120363403215116621@g.us
|
||||||
History: [user message, baerbel's previous responses]
|
History: [user message, baerbel's previous responses]
|
||||||
Workspace: /Users/pascal/openclaw-baerbel/
|
Workspace: /Users/pascal/openclaw-baerbel/
|
||||||
Tools: read only
|
Tools: read only
|
||||||
@@ -230,10 +244,10 @@ Give agents only the tools they need:
|
|||||||
{
|
{
|
||||||
"agents": {
|
"agents": {
|
||||||
"reviewer": {
|
"reviewer": {
|
||||||
"tools": { "allow": ["read", "exec"] } // Read-only
|
"tools": { "allow": ["read", "exec"] } // Read-only
|
||||||
},
|
},
|
||||||
"fixer": {
|
"fixer": {
|
||||||
"tools": { "allow": ["read", "write", "edit", "exec"] } // Read-write
|
"tools": { "allow": ["read", "write", "edit", "exec"] } // Read-write
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -242,6 +256,7 @@ Give agents only the tools they need:
|
|||||||
### 4. Monitor Performance
|
### 4. Monitor Performance
|
||||||
|
|
||||||
With many agents, consider:
|
With many agents, consider:
|
||||||
|
|
||||||
- Using `"strategy": "parallel"` (default) for speed
|
- Using `"strategy": "parallel"` (default) for speed
|
||||||
- Limiting broadcast groups to 5-10 agents
|
- Limiting broadcast groups to 5-10 agents
|
||||||
- Using faster models for simpler agents
|
- Using faster models for simpler agents
|
||||||
@@ -260,6 +275,7 @@ Result: Agent A and C respond, Agent B logs error
|
|||||||
### Providers
|
### Providers
|
||||||
|
|
||||||
Broadcast groups currently work with:
|
Broadcast groups currently work with:
|
||||||
|
|
||||||
- ✅ WhatsApp (implemented)
|
- ✅ WhatsApp (implemented)
|
||||||
- 🚧 Telegram (planned)
|
- 🚧 Telegram (planned)
|
||||||
- 🚧 Discord (planned)
|
- 🚧 Discord (planned)
|
||||||
@@ -272,7 +288,10 @@ Broadcast groups work alongside existing routing:
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"bindings": [
|
"bindings": [
|
||||||
{ "match": { "channel": "whatsapp", "peer": { "kind": "group", "id": "GROUP_A" } }, "agentId": "alfred" }
|
{
|
||||||
|
"match": { "channel": "whatsapp", "peer": { "kind": "group", "id": "GROUP_A" } },
|
||||||
|
"agentId": "alfred"
|
||||||
|
}
|
||||||
],
|
],
|
||||||
"broadcast": {
|
"broadcast": {
|
||||||
"GROUP_B": ["agent1", "agent2"]
|
"GROUP_B": ["agent1", "agent2"]
|
||||||
@@ -290,11 +309,13 @@ Broadcast groups work alongside existing routing:
|
|||||||
### Agents Not Responding
|
### Agents Not Responding
|
||||||
|
|
||||||
**Check:**
|
**Check:**
|
||||||
|
|
||||||
1. Agent IDs exist in `agents.list`
|
1. Agent IDs exist in `agents.list`
|
||||||
2. Peer ID format is correct (e.g., `120363403215116621@g.us`)
|
2. Peer ID format is correct (e.g., `120363403215116621@g.us`)
|
||||||
3. Agents are not in deny lists
|
3. Agents are not in deny lists
|
||||||
|
|
||||||
**Debug:**
|
**Debug:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
tail -f ~/.openclaw/logs/gateway.log | grep broadcast
|
tail -f ~/.openclaw/logs/gateway.log | grep broadcast
|
||||||
```
|
```
|
||||||
@@ -308,6 +329,7 @@ tail -f ~/.openclaw/logs/gateway.log | grep broadcast
|
|||||||
### Performance Issues
|
### Performance Issues
|
||||||
|
|
||||||
**If slow with many agents:**
|
**If slow with many agents:**
|
||||||
|
|
||||||
- Reduce number of agents per group
|
- Reduce number of agents per group
|
||||||
- Use lighter models (sonnet instead of opus)
|
- Use lighter models (sonnet instead of opus)
|
||||||
- Check sandbox startup time
|
- Check sandbox startup time
|
||||||
@@ -329,9 +351,21 @@ tail -f ~/.openclaw/logs/gateway.log | grep broadcast
|
|||||||
},
|
},
|
||||||
"agents": {
|
"agents": {
|
||||||
"list": [
|
"list": [
|
||||||
{ "id": "code-formatter", "workspace": "~/agents/formatter", "tools": { "allow": ["read", "write"] } },
|
{
|
||||||
{ "id": "security-scanner", "workspace": "~/agents/security", "tools": { "allow": ["read", "exec"] } },
|
"id": "code-formatter",
|
||||||
{ "id": "test-coverage", "workspace": "~/agents/testing", "tools": { "allow": ["read", "exec"] } },
|
"workspace": "~/agents/formatter",
|
||||||
|
"tools": { "allow": ["read", "write"] }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "security-scanner",
|
||||||
|
"workspace": "~/agents/security",
|
||||||
|
"tools": { "allow": ["read", "exec"] }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "test-coverage",
|
||||||
|
"workspace": "~/agents/testing",
|
||||||
|
"tools": { "allow": ["read", "exec"] }
|
||||||
|
},
|
||||||
{ "id": "docs-checker", "workspace": "~/agents/docs", "tools": { "allow": ["read"] } }
|
{ "id": "docs-checker", "workspace": "~/agents/docs", "tools": { "allow": ["read"] } }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -340,6 +374,7 @@ tail -f ~/.openclaw/logs/gateway.log | grep broadcast
|
|||||||
|
|
||||||
**User sends:** Code snippet
|
**User sends:** Code snippet
|
||||||
**Responses:**
|
**Responses:**
|
||||||
|
|
||||||
- code-formatter: "Fixed indentation and added type hints"
|
- code-formatter: "Fixed indentation and added type hints"
|
||||||
- security-scanner: "⚠️ SQL injection vulnerability in line 12"
|
- security-scanner: "⚠️ SQL injection vulnerability in line 12"
|
||||||
- test-coverage: "Coverage is 45%, missing tests for error cases"
|
- test-coverage: "Coverage is 45%, missing tests for error cases"
|
||||||
@@ -381,7 +416,6 @@ interface OpenClawConfig {
|
|||||||
- `strategy` (optional): How to process agents
|
- `strategy` (optional): How to process agents
|
||||||
- `"parallel"` (default): All agents process simultaneously
|
- `"parallel"` (default): All agents process simultaneously
|
||||||
- `"sequential"`: Agents process in array order
|
- `"sequential"`: Agents process in array order
|
||||||
|
|
||||||
- `[peerId]`: WhatsApp group JID, E.164 number, or other peer ID
|
- `[peerId]`: WhatsApp group JID, E.164 number, or other peer ID
|
||||||
- Value: Array of agent IDs that should process messages
|
- Value: Array of agent IDs that should process messages
|
||||||
|
|
||||||
@@ -395,6 +429,7 @@ interface OpenClawConfig {
|
|||||||
## Future Enhancements
|
## Future Enhancements
|
||||||
|
|
||||||
Planned features:
|
Planned features:
|
||||||
|
|
||||||
- [ ] Shared context mode (agents see each other's responses)
|
- [ ] Shared context mode (agents see each other's responses)
|
||||||
- [ ] Agent coordination (agents can signal each other)
|
- [ ] Agent coordination (agents can signal each other)
|
||||||
- [ ] Dynamic agent selection (choose agents based on message content)
|
- [ ] Dynamic agent selection (choose agents based on message content)
|
||||||
|
|||||||
@@ -4,12 +4,15 @@ read_when:
|
|||||||
- Setting up BlueBubbles channel
|
- Setting up BlueBubbles channel
|
||||||
- Troubleshooting webhook pairing
|
- Troubleshooting webhook pairing
|
||||||
- Configuring iMessage on macOS
|
- Configuring iMessage on macOS
|
||||||
|
title: "BlueBubbles"
|
||||||
---
|
---
|
||||||
|
|
||||||
# BlueBubbles (macOS REST)
|
# BlueBubbles (macOS REST)
|
||||||
|
|
||||||
Status: bundled plugin that talks to the BlueBubbles macOS server over HTTP. **Recommended for iMessage integration** due to its richer API and easier setup compared to the legacy imsg channel.
|
Status: bundled plugin that talks to the BlueBubbles macOS server over HTTP. **Recommended for iMessage integration** due to its richer API and easier setup compared to the legacy imsg channel.
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
- Runs on macOS via the BlueBubbles helper app ([bluebubbles.app](https://bluebubbles.app)).
|
- Runs on macOS via the BlueBubbles helper app ([bluebubbles.app](https://bluebubbles.app)).
|
||||||
- Recommended/tested: macOS Sequoia (15). macOS Tahoe (26) works; edit is currently broken on Tahoe, and group icon updates may report success but not sync.
|
- Recommended/tested: macOS Sequoia (15). macOS Tahoe (26) works; edit is currently broken on Tahoe, and group icon updates may report success but not sync.
|
||||||
- OpenClaw talks to it through its REST API (`GET /api/v1/ping`, `POST /message/text`, `POST /chat/:id/*`).
|
- OpenClaw talks to it through its REST API (`GET /api/v1/ping`, `POST /message/text`, `POST /chat/:id/*`).
|
||||||
@@ -20,6 +23,7 @@ Status: bundled plugin that talks to the BlueBubbles macOS server over HTTP. **R
|
|||||||
- Advanced features: edit, unsend, reply threading, message effects, group management.
|
- Advanced features: edit, unsend, reply threading, message effects, group management.
|
||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
||||||
1. Install the BlueBubbles server on your Mac (follow the instructions at [bluebubbles.app/install](https://bluebubbles.app/install)).
|
1. Install the BlueBubbles server on your Mac (follow the instructions at [bluebubbles.app/install](https://bluebubbles.app/install)).
|
||||||
2. In the BlueBubbles config, enable the web API and set a password.
|
2. In the BlueBubbles config, enable the web API and set a password.
|
||||||
3. Run `openclaw onboard` and select BlueBubbles, or configure manually:
|
3. Run `openclaw onboard` and select BlueBubbles, or configure manually:
|
||||||
@@ -30,21 +34,24 @@ Status: bundled plugin that talks to the BlueBubbles macOS server over HTTP. **R
|
|||||||
enabled: true,
|
enabled: true,
|
||||||
serverUrl: "http://192.168.1.100:1234",
|
serverUrl: "http://192.168.1.100:1234",
|
||||||
password: "example-password",
|
password: "example-password",
|
||||||
webhookPath: "/bluebubbles-webhook"
|
webhookPath: "/bluebubbles-webhook",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
4. Point BlueBubbles webhooks to your gateway (example: `https://your-gateway-host:3000/bluebubbles-webhook?password=<password>`).
|
4. Point BlueBubbles webhooks to your gateway (example: `https://your-gateway-host:3000/bluebubbles-webhook?password=<password>`).
|
||||||
5. Start the gateway; it will register the webhook handler and start pairing.
|
5. Start the gateway; it will register the webhook handler and start pairing.
|
||||||
|
|
||||||
## Onboarding
|
## Onboarding
|
||||||
|
|
||||||
BlueBubbles is available in the interactive setup wizard:
|
BlueBubbles is available in the interactive setup wizard:
|
||||||
|
|
||||||
```
|
```
|
||||||
openclaw onboard
|
openclaw onboard
|
||||||
```
|
```
|
||||||
|
|
||||||
The wizard prompts for:
|
The wizard prompts for:
|
||||||
|
|
||||||
- **Server URL** (required): BlueBubbles server address (e.g., `http://192.168.1.100:1234`)
|
- **Server URL** (required): BlueBubbles server address (e.g., `http://192.168.1.100:1234`)
|
||||||
- **Password** (required): API password from BlueBubbles Server settings
|
- **Password** (required): API password from BlueBubbles Server settings
|
||||||
- **Webhook path** (optional): Defaults to `/bluebubbles-webhook`
|
- **Webhook path** (optional): Defaults to `/bluebubbles-webhook`
|
||||||
@@ -52,12 +59,15 @@ The wizard prompts for:
|
|||||||
- **Allow list**: Phone numbers, emails, or chat targets
|
- **Allow list**: Phone numbers, emails, or chat targets
|
||||||
|
|
||||||
You can also add BlueBubbles via CLI:
|
You can also add BlueBubbles via CLI:
|
||||||
|
|
||||||
```
|
```
|
||||||
openclaw channels add bluebubbles --http-url http://192.168.1.100:1234 --password <password>
|
openclaw channels add bluebubbles --http-url http://192.168.1.100:1234 --password <password>
|
||||||
```
|
```
|
||||||
|
|
||||||
## Access control (DMs + groups)
|
## Access control (DMs + groups)
|
||||||
|
|
||||||
DMs:
|
DMs:
|
||||||
|
|
||||||
- Default: `channels.bluebubbles.dmPolicy = "pairing"`.
|
- Default: `channels.bluebubbles.dmPolicy = "pairing"`.
|
||||||
- Unknown senders receive a pairing code; messages are ignored until approved (codes expire after 1 hour).
|
- Unknown senders receive a pairing code; messages are ignored until approved (codes expire after 1 hour).
|
||||||
- Approve via:
|
- Approve via:
|
||||||
@@ -66,16 +76,20 @@ DMs:
|
|||||||
- Pairing is the default token exchange. Details: [Pairing](/start/pairing)
|
- Pairing is the default token exchange. Details: [Pairing](/start/pairing)
|
||||||
|
|
||||||
Groups:
|
Groups:
|
||||||
|
|
||||||
- `channels.bluebubbles.groupPolicy = open | allowlist | disabled` (default: `allowlist`).
|
- `channels.bluebubbles.groupPolicy = open | allowlist | disabled` (default: `allowlist`).
|
||||||
- `channels.bluebubbles.groupAllowFrom` controls who can trigger in groups when `allowlist` is set.
|
- `channels.bluebubbles.groupAllowFrom` controls who can trigger in groups when `allowlist` is set.
|
||||||
|
|
||||||
### Mention gating (groups)
|
### Mention gating (groups)
|
||||||
|
|
||||||
BlueBubbles supports mention gating for group chats, matching iMessage/WhatsApp behavior:
|
BlueBubbles supports mention gating for group chats, matching iMessage/WhatsApp behavior:
|
||||||
|
|
||||||
- Uses `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`) to detect mentions.
|
- Uses `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`) to detect mentions.
|
||||||
- When `requireMention` is enabled for a group, the agent only responds when mentioned.
|
- When `requireMention` is enabled for a group, the agent only responds when mentioned.
|
||||||
- Control commands from authorized senders bypass mention gating.
|
- Control commands from authorized senders bypass mention gating.
|
||||||
|
|
||||||
Per-group configuration:
|
Per-group configuration:
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
channels: {
|
channels: {
|
||||||
@@ -83,20 +97,22 @@ Per-group configuration:
|
|||||||
groupPolicy: "allowlist",
|
groupPolicy: "allowlist",
|
||||||
groupAllowFrom: ["+15555550123"],
|
groupAllowFrom: ["+15555550123"],
|
||||||
groups: {
|
groups: {
|
||||||
"*": { requireMention: true }, // default for all groups
|
"*": { requireMention: true }, // default for all groups
|
||||||
"iMessage;-;chat123": { requireMention: false } // override for specific group
|
"iMessage;-;chat123": { requireMention: false }, // override for specific group
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Command gating
|
### Command gating
|
||||||
|
|
||||||
- Control commands (e.g., `/config`, `/model`) require authorization.
|
- Control commands (e.g., `/config`, `/model`) require authorization.
|
||||||
- Uses `allowFrom` and `groupAllowFrom` to determine command authorization.
|
- Uses `allowFrom` and `groupAllowFrom` to determine command authorization.
|
||||||
- Authorized senders can run control commands even without mentioning in groups.
|
- Authorized senders can run control commands even without mentioning in groups.
|
||||||
|
|
||||||
## Typing + read receipts
|
## Typing + read receipts
|
||||||
|
|
||||||
- **Typing indicators**: Sent automatically before and during response generation.
|
- **Typing indicators**: Sent automatically before and during response generation.
|
||||||
- **Read receipts**: Controlled by `channels.bluebubbles.sendReadReceipts` (default: `true`).
|
- **Read receipts**: Controlled by `channels.bluebubbles.sendReadReceipts` (default: `true`).
|
||||||
- **Typing indicators**: OpenClaw sends typing start events; BlueBubbles clears typing automatically on send or timeout (manual stop via DELETE is unreliable).
|
- **Typing indicators**: OpenClaw sends typing start events; BlueBubbles clears typing automatically on send or timeout (manual stop via DELETE is unreliable).
|
||||||
@@ -105,13 +121,14 @@ Per-group configuration:
|
|||||||
{
|
{
|
||||||
channels: {
|
channels: {
|
||||||
bluebubbles: {
|
bluebubbles: {
|
||||||
sendReadReceipts: false // disable read receipts
|
sendReadReceipts: false, // disable read receipts
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Advanced actions
|
## Advanced actions
|
||||||
|
|
||||||
BlueBubbles supports advanced message actions when enabled in config:
|
BlueBubbles supports advanced message actions when enabled in config:
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
@@ -119,24 +136,25 @@ BlueBubbles supports advanced message actions when enabled in config:
|
|||||||
channels: {
|
channels: {
|
||||||
bluebubbles: {
|
bluebubbles: {
|
||||||
actions: {
|
actions: {
|
||||||
reactions: true, // tapbacks (default: true)
|
reactions: true, // tapbacks (default: true)
|
||||||
edit: true, // edit sent messages (macOS 13+, broken on macOS 26 Tahoe)
|
edit: true, // edit sent messages (macOS 13+, broken on macOS 26 Tahoe)
|
||||||
unsend: true, // unsend messages (macOS 13+)
|
unsend: true, // unsend messages (macOS 13+)
|
||||||
reply: true, // reply threading by message GUID
|
reply: true, // reply threading by message GUID
|
||||||
sendWithEffect: true, // message effects (slam, loud, etc.)
|
sendWithEffect: true, // message effects (slam, loud, etc.)
|
||||||
renameGroup: true, // rename group chats
|
renameGroup: true, // rename group chats
|
||||||
setGroupIcon: true, // set group chat icon/photo (flaky on macOS 26 Tahoe)
|
setGroupIcon: true, // set group chat icon/photo (flaky on macOS 26 Tahoe)
|
||||||
addParticipant: true, // add participants to groups
|
addParticipant: true, // add participants to groups
|
||||||
removeParticipant: true, // remove participants from groups
|
removeParticipant: true, // remove participants from groups
|
||||||
leaveGroup: true, // leave group chats
|
leaveGroup: true, // leave group chats
|
||||||
sendAttachment: true // send attachments/media
|
sendAttachment: true, // send attachments/media
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Available actions:
|
Available actions:
|
||||||
|
|
||||||
- **react**: Add/remove tapback reactions (`messageId`, `emoji`, `remove`)
|
- **react**: Add/remove tapback reactions (`messageId`, `emoji`, `remove`)
|
||||||
- **edit**: Edit a sent message (`messageId`, `text`)
|
- **edit**: Edit a sent message (`messageId`, `text`)
|
||||||
- **unsend**: Unsend a message (`messageId`)
|
- **unsend**: Unsend a message (`messageId`)
|
||||||
@@ -151,39 +169,47 @@ Available actions:
|
|||||||
- 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.
|
- 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.
|
||||||
|
|
||||||
### Message IDs (short vs full)
|
### Message IDs (short vs full)
|
||||||
OpenClaw may surface *short* message IDs (e.g., `1`, `2`) to save tokens.
|
|
||||||
|
OpenClaw may surface _short_ message IDs (e.g., `1`, `2`) to save tokens.
|
||||||
|
|
||||||
- `MessageSid` / `ReplyToId` can be short IDs.
|
- `MessageSid` / `ReplyToId` can be short IDs.
|
||||||
- `MessageSidFull` / `ReplyToIdFull` contain the provider full IDs.
|
- `MessageSidFull` / `ReplyToIdFull` contain the provider full IDs.
|
||||||
- Short IDs are in-memory; they can expire on restart or cache eviction.
|
- Short IDs are in-memory; they can expire on restart or cache eviction.
|
||||||
- Actions accept short or full `messageId`, but short IDs will error if no longer available.
|
- Actions accept short or full `messageId`, but short IDs will error if no longer available.
|
||||||
|
|
||||||
Use full IDs for durable automations and storage:
|
Use full IDs for durable automations and storage:
|
||||||
|
|
||||||
- Templates: `{{MessageSidFull}}`, `{{ReplyToIdFull}}`
|
- Templates: `{{MessageSidFull}}`, `{{ReplyToIdFull}}`
|
||||||
- Context: `MessageSidFull` / `ReplyToIdFull` in inbound payloads
|
- Context: `MessageSidFull` / `ReplyToIdFull` in inbound payloads
|
||||||
|
|
||||||
See [Configuration](/gateway/configuration) for template variables.
|
See [Configuration](/gateway/configuration) for template variables.
|
||||||
|
|
||||||
## Block streaming
|
## Block streaming
|
||||||
|
|
||||||
Control whether responses are sent as a single message or streamed in blocks:
|
Control whether responses are sent as a single message or streamed in blocks:
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
channels: {
|
channels: {
|
||||||
bluebubbles: {
|
bluebubbles: {
|
||||||
blockStreaming: true // enable block streaming (default behavior)
|
blockStreaming: true, // enable block streaming (off by default)
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Media + limits
|
## Media + limits
|
||||||
|
|
||||||
- Inbound attachments are downloaded and stored in the media cache.
|
- Inbound attachments are downloaded and stored in the media cache.
|
||||||
- Media cap via `channels.bluebubbles.mediaMaxMb` (default: 8 MB).
|
- Media cap via `channels.bluebubbles.mediaMaxMb` (default: 8 MB).
|
||||||
- Outbound text is chunked to `channels.bluebubbles.textChunkLimit` (default: 4000 chars).
|
- Outbound text is chunked to `channels.bluebubbles.textChunkLimit` (default: 4000 chars).
|
||||||
|
|
||||||
## Configuration reference
|
## Configuration reference
|
||||||
|
|
||||||
Full configuration: [Configuration](/gateway/configuration)
|
Full configuration: [Configuration](/gateway/configuration)
|
||||||
|
|
||||||
Provider options:
|
Provider options:
|
||||||
|
|
||||||
- `channels.bluebubbles.enabled`: Enable/disable the channel.
|
- `channels.bluebubbles.enabled`: Enable/disable the channel.
|
||||||
- `channels.bluebubbles.serverUrl`: BlueBubbles REST API base URL.
|
- `channels.bluebubbles.serverUrl`: BlueBubbles REST API base URL.
|
||||||
- `channels.bluebubbles.password`: API password.
|
- `channels.bluebubbles.password`: API password.
|
||||||
@@ -194,7 +220,7 @@ Provider options:
|
|||||||
- `channels.bluebubbles.groupAllowFrom`: Group sender allowlist.
|
- `channels.bluebubbles.groupAllowFrom`: Group sender allowlist.
|
||||||
- `channels.bluebubbles.groups`: Per-group config (`requireMention`, etc.).
|
- `channels.bluebubbles.groups`: Per-group config (`requireMention`, etc.).
|
||||||
- `channels.bluebubbles.sendReadReceipts`: Send read receipts (default: `true`).
|
- `channels.bluebubbles.sendReadReceipts`: Send read receipts (default: `true`).
|
||||||
- `channels.bluebubbles.blockStreaming`: Enable block streaming (default: `true`).
|
- `channels.bluebubbles.blockStreaming`: Enable block streaming (default: `false`; required for streaming replies).
|
||||||
- `channels.bluebubbles.textChunkLimit`: Outbound chunk size in chars (default: 4000).
|
- `channels.bluebubbles.textChunkLimit`: Outbound chunk size in chars (default: 4000).
|
||||||
- `channels.bluebubbles.chunkMode`: `length` (default) splits only when exceeding `textChunkLimit`; `newline` splits on blank lines (paragraph boundaries) before length chunking.
|
- `channels.bluebubbles.chunkMode`: `length` (default) splits only when exceeding `textChunkLimit`; `newline` splits on blank lines (paragraph boundaries) before length chunking.
|
||||||
- `channels.bluebubbles.mediaMaxMb`: Inbound media cap in MB (default: 8).
|
- `channels.bluebubbles.mediaMaxMb`: Inbound media cap in MB (default: 8).
|
||||||
@@ -204,11 +230,14 @@ Provider options:
|
|||||||
- `channels.bluebubbles.accounts`: Multi-account configuration.
|
- `channels.bluebubbles.accounts`: Multi-account configuration.
|
||||||
|
|
||||||
Related global options:
|
Related global options:
|
||||||
|
|
||||||
- `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`).
|
- `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`).
|
||||||
- `messages.responsePrefix`.
|
- `messages.responsePrefix`.
|
||||||
|
|
||||||
## Addressing / delivery targets
|
## Addressing / delivery targets
|
||||||
|
|
||||||
Prefer `chat_guid` for stable routing:
|
Prefer `chat_guid` for stable routing:
|
||||||
|
|
||||||
- `chat_guid:iMessage;-;+15555550123` (preferred for groups)
|
- `chat_guid:iMessage;-;+15555550123` (preferred for groups)
|
||||||
- `chat_id:123`
|
- `chat_id:123`
|
||||||
- `chat_identifier:...`
|
- `chat_identifier:...`
|
||||||
@@ -216,12 +245,14 @@ Prefer `chat_guid` for stable routing:
|
|||||||
- If a direct handle does not have an existing DM chat, OpenClaw will create one via `POST /api/v1/chat/new`. This requires the BlueBubbles Private API to be enabled.
|
- If a direct handle does not have an existing DM chat, OpenClaw will create one via `POST /api/v1/chat/new`. This requires the BlueBubbles Private API to be enabled.
|
||||||
|
|
||||||
## Security
|
## Security
|
||||||
|
|
||||||
- Webhook requests are authenticated by comparing `guid`/`password` query params or headers against `channels.bluebubbles.password`. Requests from `localhost` are also accepted.
|
- Webhook requests are authenticated by comparing `guid`/`password` query params or headers against `channels.bluebubbles.password`. Requests from `localhost` are also accepted.
|
||||||
- Keep the API password and webhook endpoint secret (treat them like credentials).
|
- Keep the API password and webhook endpoint secret (treat them like credentials).
|
||||||
- Localhost trust means a same-host reverse proxy can unintentionally bypass the password. If you proxy the gateway, require auth at the proxy and configure `gateway.trustedProxies`. See [Gateway security](/gateway/security#reverse-proxy-configuration).
|
- Localhost trust means a same-host reverse proxy can unintentionally bypass the password. If you proxy the gateway, require auth at the proxy and configure `gateway.trustedProxies`. See [Gateway security](/gateway/security#reverse-proxy-configuration).
|
||||||
- Enable HTTPS + firewall rules on the BlueBubbles server if exposing it outside your LAN.
|
- Enable HTTPS + firewall rules on the BlueBubbles server if exposing it outside your LAN.
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
- If typing/read events stop working, check the BlueBubbles webhook logs and verify the gateway path matches `channels.bluebubbles.webhookPath`.
|
- If typing/read events stop working, check the BlueBubbles webhook logs and verify the gateway path matches `channels.bluebubbles.webhookPath`.
|
||||||
- Pairing codes expire after one hour; use `openclaw pairing list bluebubbles` and `openclaw pairing approve bluebubbles <code>`.
|
- Pairing codes expire after one hour; use `openclaw pairing list bluebubbles` and `openclaw pairing approve bluebubbles <code>`.
|
||||||
- Reactions require the BlueBubbles private API (`POST /api/v1/message/react`); ensure the server version exposes it.
|
- Reactions require the BlueBubbles private API (`POST /api/v1/message/react`); ensure the server version exposes it.
|
||||||
|
|||||||
@@ -2,42 +2,47 @@
|
|||||||
summary: "Discord bot support status, capabilities, and configuration"
|
summary: "Discord bot support status, capabilities, and configuration"
|
||||||
read_when:
|
read_when:
|
||||||
- Working on Discord channel features
|
- Working on Discord channel features
|
||||||
|
title: "Discord"
|
||||||
---
|
---
|
||||||
# Discord (Bot API)
|
|
||||||
|
|
||||||
|
# Discord (Bot API)
|
||||||
|
|
||||||
Status: ready for DM and guild text channels via the official Discord bot gateway.
|
Status: ready for DM and guild text channels via the official Discord bot gateway.
|
||||||
|
|
||||||
## Quick setup (beginner)
|
## Quick setup (beginner)
|
||||||
1) Create a Discord bot and copy the bot token.
|
|
||||||
2) In the Discord app settings, enable **Message Content Intent** (and **Server Members Intent** if you plan to use allowlists or name lookups).
|
1. Create a Discord bot and copy the bot token.
|
||||||
3) Set the token for OpenClaw:
|
2. In the Discord app settings, enable **Message Content Intent** (and **Server Members Intent** if you plan to use allowlists or name lookups).
|
||||||
|
3. Set the token for OpenClaw:
|
||||||
- Env: `DISCORD_BOT_TOKEN=...`
|
- Env: `DISCORD_BOT_TOKEN=...`
|
||||||
- Or config: `channels.discord.token: "..."`.
|
- Or config: `channels.discord.token: "..."`.
|
||||||
- If both are set, config takes precedence (env fallback is default-account only).
|
- If both are set, config takes precedence (env fallback is default-account only).
|
||||||
4) Invite the bot to your server with message permissions (create a private server if you just want DMs).
|
4. Invite the bot to your server with message permissions (create a private server if you just want DMs).
|
||||||
5) Start the gateway.
|
5. Start the gateway.
|
||||||
6) DM access is pairing by default; approve the pairing code on first contact.
|
6. DM access is pairing by default; approve the pairing code on first contact.
|
||||||
|
|
||||||
Minimal config:
|
Minimal config:
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
channels: {
|
channels: {
|
||||||
discord: {
|
discord: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
token: "YOUR_BOT_TOKEN"
|
token: "YOUR_BOT_TOKEN",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Goals
|
## Goals
|
||||||
|
|
||||||
- Talk to OpenClaw via Discord DMs or guild channels.
|
- Talk to OpenClaw via Discord DMs or guild channels.
|
||||||
- Direct chats collapse into the agent's main session (default `agent:main:main`); guild channels stay isolated as `agent:<agentId>:discord:channel:<channelId>` (display names use `discord:<guildSlug>#<channelSlug>`).
|
- Direct chats collapse into the agent's main session (default `agent:main:main`); guild channels stay isolated as `agent:<agentId>:discord:channel:<channelId>` (display names use `discord:<guildSlug>#<channelSlug>`).
|
||||||
- Group DMs are ignored by default; enable via `channels.discord.dm.groupEnabled` and optionally restrict by `channels.discord.dm.groupChannels`.
|
- Group DMs are ignored by default; enable via `channels.discord.dm.groupEnabled` and optionally restrict by `channels.discord.dm.groupChannels`.
|
||||||
- Keep routing deterministic: replies always go back to the channel they arrived on.
|
- Keep routing deterministic: replies always go back to the channel they arrived on.
|
||||||
|
|
||||||
## How it works
|
## How it works
|
||||||
|
|
||||||
1. Create a Discord application → Bot, enable the intents you need (DMs + guild messages + message content), and grab the bot token.
|
1. Create a Discord application → Bot, enable the intents you need (DMs + guild messages + message content), and grab the bot token.
|
||||||
2. Invite the bot to your server with the permissions required to read/send messages where you want to use it.
|
2. Invite the bot to your server with the permissions required to read/send messages where you want to use it.
|
||||||
3. Configure OpenClaw with `channels.discord.token` (or `DISCORD_BOT_TOKEN` as a fallback).
|
3. Configure OpenClaw with `channels.discord.token` (or `DISCORD_BOT_TOKEN` as a fallback).
|
||||||
@@ -64,12 +69,14 @@ Note: Slugs are lowercase with spaces replaced by `-`. Channel names are slugged
|
|||||||
Note: Guild context `[from:]` lines include `author.tag` + `id` to make ping-ready replies easy.
|
Note: Guild context `[from:]` lines include `author.tag` + `id` to make ping-ready replies easy.
|
||||||
|
|
||||||
## Config writes
|
## Config writes
|
||||||
|
|
||||||
By default, Discord is allowed to write config updates triggered by `/config set|unset` (requires `commands.config: true`).
|
By default, Discord is allowed to write config updates triggered by `/config set|unset` (requires `commands.config: true`).
|
||||||
|
|
||||||
Disable with:
|
Disable with:
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
channels: { discord: { configWrites: false } }
|
channels: { discord: { configWrites: false } },
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -78,28 +85,34 @@ Disable with:
|
|||||||
This is the “Discord Developer Portal” setup for running OpenClaw in a server (guild) channel like `#help`.
|
This is the “Discord Developer Portal” setup for running OpenClaw in a server (guild) channel like `#help`.
|
||||||
|
|
||||||
### 1) Create the Discord app + bot user
|
### 1) Create the Discord app + bot user
|
||||||
|
|
||||||
1. Discord Developer Portal → **Applications** → **New Application**
|
1. Discord Developer Portal → **Applications** → **New Application**
|
||||||
2. In your app:
|
2. In your app:
|
||||||
- **Bot** → **Add Bot**
|
- **Bot** → **Add Bot**
|
||||||
- Copy the **Bot Token** (this is what you put in `DISCORD_BOT_TOKEN`)
|
- Copy the **Bot Token** (this is what you put in `DISCORD_BOT_TOKEN`)
|
||||||
|
|
||||||
### 2) Enable the gateway intents OpenClaw needs
|
### 2) Enable the gateway intents OpenClaw needs
|
||||||
|
|
||||||
Discord blocks “privileged intents” unless you explicitly enable them.
|
Discord blocks “privileged intents” unless you explicitly enable them.
|
||||||
|
|
||||||
In **Bot** → **Privileged Gateway Intents**, enable:
|
In **Bot** → **Privileged Gateway Intents**, enable:
|
||||||
|
|
||||||
- **Message Content Intent** (required to read message text in most guilds; without it you’ll see “Used disallowed intents” or the bot will connect but not react to messages)
|
- **Message Content Intent** (required to read message text in most guilds; without it you’ll see “Used disallowed intents” or the bot will connect but not react to messages)
|
||||||
- **Server Members Intent** (recommended; required for some member/user lookups and allowlist matching in guilds)
|
- **Server Members Intent** (recommended; required for some member/user lookups and allowlist matching in guilds)
|
||||||
|
|
||||||
You usually do **not** need **Presence Intent**.
|
You usually do **not** need **Presence Intent**.
|
||||||
|
|
||||||
### 3) Generate an invite URL (OAuth2 URL Generator)
|
### 3) Generate an invite URL (OAuth2 URL Generator)
|
||||||
|
|
||||||
In your app: **OAuth2** → **URL Generator**
|
In your app: **OAuth2** → **URL Generator**
|
||||||
|
|
||||||
**Scopes**
|
**Scopes**
|
||||||
|
|
||||||
- ✅ `bot`
|
- ✅ `bot`
|
||||||
- ✅ `applications.commands` (required for native commands)
|
- ✅ `applications.commands` (required for native commands)
|
||||||
|
|
||||||
**Bot Permissions** (minimal baseline)
|
**Bot Permissions** (minimal baseline)
|
||||||
|
|
||||||
- ✅ View Channels
|
- ✅ View Channels
|
||||||
- ✅ Send Messages
|
- ✅ Send Messages
|
||||||
- ✅ Read Message History
|
- ✅ Read Message History
|
||||||
@@ -113,6 +126,7 @@ Avoid **Administrator** unless you’re debugging and fully trust the bot.
|
|||||||
Copy the generated URL, open it, pick your server, and install the bot.
|
Copy the generated URL, open it, pick your server, and install the bot.
|
||||||
|
|
||||||
### 4) Get the ids (guild/user/channel)
|
### 4) Get the ids (guild/user/channel)
|
||||||
|
|
||||||
Discord uses numeric ids everywhere; OpenClaw config prefers ids.
|
Discord uses numeric ids everywhere; OpenClaw config prefers ids.
|
||||||
|
|
||||||
1. Discord (desktop/web) → **User Settings** → **Advanced** → enable **Developer Mode**
|
1. Discord (desktop/web) → **User Settings** → **Advanced** → enable **Developer Mode**
|
||||||
@@ -124,7 +138,9 @@ Discord uses numeric ids everywhere; OpenClaw config prefers ids.
|
|||||||
### 5) Configure OpenClaw
|
### 5) Configure OpenClaw
|
||||||
|
|
||||||
#### Token
|
#### Token
|
||||||
|
|
||||||
Set the bot token via env var (recommended on servers):
|
Set the bot token via env var (recommended on servers):
|
||||||
|
|
||||||
- `DISCORD_BOT_TOKEN=...`
|
- `DISCORD_BOT_TOKEN=...`
|
||||||
|
|
||||||
Or via config:
|
Or via config:
|
||||||
@@ -134,15 +150,16 @@ Or via config:
|
|||||||
channels: {
|
channels: {
|
||||||
discord: {
|
discord: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
token: "YOUR_BOT_TOKEN"
|
token: "YOUR_BOT_TOKEN",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Multi-account support: use `channels.discord.accounts` with per-account tokens and optional `name`. See [`gateway/configuration`](/gateway/configuration#telegramaccounts--discordaccounts--slackaccounts--signalaccounts--imessageaccounts) for the shared pattern.
|
Multi-account support: use `channels.discord.accounts` with per-account tokens and optional `name`. See [`gateway/configuration`](/gateway/configuration#telegramaccounts--discordaccounts--slackaccounts--signalaccounts--imessageaccounts) for the shared pattern.
|
||||||
|
|
||||||
#### Allowlist + channel routing
|
#### Allowlist + channel routing
|
||||||
|
|
||||||
Example “single server, only allow me, only allow #help”:
|
Example “single server, only allow me, only allow #help”:
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
@@ -152,26 +169,27 @@ Example “single server, only allow me, only allow #help”:
|
|||||||
enabled: true,
|
enabled: true,
|
||||||
dm: { enabled: false },
|
dm: { enabled: false },
|
||||||
guilds: {
|
guilds: {
|
||||||
"YOUR_GUILD_ID": {
|
YOUR_GUILD_ID: {
|
||||||
users: ["YOUR_USER_ID"],
|
users: ["YOUR_USER_ID"],
|
||||||
requireMention: true,
|
requireMention: true,
|
||||||
channels: {
|
channels: {
|
||||||
help: { allow: true, requireMention: true }
|
help: { allow: true, requireMention: true },
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
retry: {
|
retry: {
|
||||||
attempts: 3,
|
attempts: 3,
|
||||||
minDelayMs: 500,
|
minDelayMs: 500,
|
||||||
maxDelayMs: 30000,
|
maxDelayMs: 30000,
|
||||||
jitter: 0.1
|
jitter: 0.1,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
- `requireMention: true` means the bot only replies when mentioned (recommended for shared channels).
|
- `requireMention: true` means the bot only replies when mentioned (recommended for shared channels).
|
||||||
- `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`) also count as mentions for guild messages.
|
- `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`) also count as mentions for guild messages.
|
||||||
- Multi-agent override: set per-agent patterns on `agents.list[].groupChat.mentionPatterns`.
|
- Multi-agent override: set per-agent patterns on `agents.list[].groupChat.mentionPatterns`.
|
||||||
@@ -182,11 +200,13 @@ Notes:
|
|||||||
- Warning: If you allow replies to other bots (`channels.discord.allowBots=true`), prevent bot-to-bot reply loops with `requireMention`, `channels.discord.guilds.*.channels.<id>.users` allowlists, and/or clear guardrails in `AGENTS.md` and `SOUL.md`.
|
- Warning: If you allow replies to other bots (`channels.discord.allowBots=true`), prevent bot-to-bot reply loops with `requireMention`, `channels.discord.guilds.*.channels.<id>.users` allowlists, and/or clear guardrails in `AGENTS.md` and `SOUL.md`.
|
||||||
|
|
||||||
### 6) Verify it works
|
### 6) Verify it works
|
||||||
|
|
||||||
1. Start the gateway.
|
1. Start the gateway.
|
||||||
2. In your server channel, send: `@Krill hello` (or whatever your bot name is).
|
2. In your server channel, send: `@Krill hello` (or whatever your bot name is).
|
||||||
3. If nothing happens: check **Troubleshooting** below.
|
3. If nothing happens: check **Troubleshooting** below.
|
||||||
|
|
||||||
### Troubleshooting
|
### Troubleshooting
|
||||||
|
|
||||||
- First: run `openclaw doctor` and `openclaw channels status --probe` (actionable warnings + quick audits).
|
- First: run `openclaw doctor` and `openclaw channels status --probe` (actionable warnings + quick audits).
|
||||||
- **“Used disallowed intents”**: enable **Message Content Intent** (and likely **Server Members Intent**) in the Developer Portal, then restart the gateway.
|
- **“Used disallowed intents”**: enable **Message Content Intent** (and likely **Server Members Intent**) in the Developer Portal, then restart the gateway.
|
||||||
- **Bot connects but never replies in a guild channel**:
|
- **Bot connects but never replies in a guild channel**:
|
||||||
@@ -202,8 +222,14 @@ Notes:
|
|||||||
- `requireMention` must live under `channels.discord.guilds` (or a specific channel). `channels.discord.requireMention` at the top level is ignored.
|
- `requireMention` must live under `channels.discord.guilds` (or a specific channel). `channels.discord.requireMention` at the top level is ignored.
|
||||||
- **Permission audits** (`channels status --probe`) only check numeric channel IDs. If you use slugs/names as `channels.discord.guilds.*.channels` keys, the audit can’t verify permissions.
|
- **Permission audits** (`channels status --probe`) only check numeric channel IDs. If you use slugs/names as `channels.discord.guilds.*.channels` keys, the audit can’t verify permissions.
|
||||||
- **DMs don’t work**: `channels.discord.dm.enabled=false`, `channels.discord.dm.policy="disabled"`, or you haven’t been approved yet (`channels.discord.dm.policy="pairing"`).
|
- **DMs don’t work**: `channels.discord.dm.enabled=false`, `channels.discord.dm.policy="disabled"`, or you haven’t been approved yet (`channels.discord.dm.policy="pairing"`).
|
||||||
|
- **Exec approvals in Discord**: Discord supports a **button UI** for exec approvals in DMs (Allow once / Always allow / Deny). `/approve <id> ...` is only for forwarded approvals and won’t resolve Discord’s button prompts. If you see `❌ Failed to submit approval: Error: unknown approval id` or the UI never shows up, check:
|
||||||
|
- `channels.discord.execApprovals.enabled: true` in your config.
|
||||||
|
- Your Discord user ID is listed in `channels.discord.execApprovals.approvers` (the UI is only sent to approvers).
|
||||||
|
- Use the buttons in the DM prompt (**Allow once**, **Always allow**, **Deny**).
|
||||||
|
- See [Exec approvals](/tools/exec-approvals) and [Slash commands](/tools/slash-commands) for the broader approvals and command flow.
|
||||||
|
|
||||||
## Capabilities & limits
|
## Capabilities & limits
|
||||||
|
|
||||||
- DMs and guild text channels (threads are treated as separate channels; voice not supported).
|
- DMs and guild text channels (threads are treated as separate channels; voice not supported).
|
||||||
- Typing indicators sent best-effort; message chunking uses `channels.discord.textChunkLimit` (default 2000) and splits tall replies by line count (`channels.discord.maxLinesPerMessage`, default 17).
|
- Typing indicators sent best-effort; message chunking uses `channels.discord.textChunkLimit` (default 2000) and splits tall replies by line count (`channels.discord.maxLinesPerMessage`, default 17).
|
||||||
- Optional newline chunking: set `channels.discord.chunkMode="newline"` to split on blank lines (paragraph boundaries) before length chunking.
|
- Optional newline chunking: set `channels.discord.chunkMode="newline"` to split on blank lines (paragraph boundaries) before length chunking.
|
||||||
@@ -213,6 +239,7 @@ Notes:
|
|||||||
- Native reply threading is **off by default**; enable with `channels.discord.replyToMode` and reply tags.
|
- Native reply threading is **off by default**; enable with `channels.discord.replyToMode` and reply tags.
|
||||||
|
|
||||||
## Retry policy
|
## Retry policy
|
||||||
|
|
||||||
Outbound Discord API calls retry on rate limits (429) using Discord `retry_after` when available, with exponential backoff and jitter. Configure via `channels.discord.retry`. See [Retry policy](/concepts/retry).
|
Outbound Discord API calls retry on rate limits (429) using Discord `retry_after` when available, with exponential backoff and jitter. Configure via `channels.discord.retry`. See [Retry policy](/concepts/retry).
|
||||||
|
|
||||||
## Config
|
## Config
|
||||||
@@ -227,9 +254,9 @@ Outbound Discord API calls retry on rate limits (429) using Discord `retry_after
|
|||||||
guilds: {
|
guilds: {
|
||||||
"*": {
|
"*": {
|
||||||
channels: {
|
channels: {
|
||||||
general: { allow: true }
|
general: { allow: true },
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
mediaMaxMb: 8,
|
mediaMaxMb: 8,
|
||||||
actions: {
|
actions: {
|
||||||
@@ -250,7 +277,7 @@ Outbound Discord API calls retry on rate limits (429) using Discord `retry_after
|
|||||||
channels: true,
|
channels: true,
|
||||||
voiceStatus: true,
|
voiceStatus: true,
|
||||||
events: true,
|
events: true,
|
||||||
moderation: false
|
moderation: false,
|
||||||
},
|
},
|
||||||
replyToMode: "off",
|
replyToMode: "off",
|
||||||
dm: {
|
dm: {
|
||||||
@@ -258,7 +285,7 @@ Outbound Discord API calls retry on rate limits (429) using Discord `retry_after
|
|||||||
policy: "pairing", // pairing | allowlist | open | disabled
|
policy: "pairing", // pairing | allowlist | open | disabled
|
||||||
allowFrom: ["123456789012345678", "steipete"],
|
allowFrom: ["123456789012345678", "steipete"],
|
||||||
groupEnabled: false,
|
groupEnabled: false,
|
||||||
groupChannels: ["openclaw-dm"]
|
groupChannels: ["openclaw-dm"],
|
||||||
},
|
},
|
||||||
guilds: {
|
guilds: {
|
||||||
"*": { requireMention: true },
|
"*": { requireMention: true },
|
||||||
@@ -274,13 +301,13 @@ Outbound Discord API calls retry on rate limits (429) using Discord `retry_after
|
|||||||
requireMention: true,
|
requireMention: true,
|
||||||
users: ["987654321098765432"],
|
users: ["987654321098765432"],
|
||||||
skills: ["search", "docs"],
|
skills: ["search", "docs"],
|
||||||
systemPrompt: "Keep answers short."
|
systemPrompt: "Keep answers short.",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -318,6 +345,7 @@ ack reaction after the bot replies.
|
|||||||
- `historyLimit`: number of recent guild messages to include as context when replying to a mention (default 20; falls back to `messages.groupChat.historyLimit`; `0` disables).
|
- `historyLimit`: number of recent guild messages to include as context when replying to a mention (default 20; falls back to `messages.groupChat.historyLimit`; `0` disables).
|
||||||
- `dmHistoryLimit`: DM history limit in user turns. Per-user overrides: `dms["<user_id>"].historyLimit`.
|
- `dmHistoryLimit`: DM history limit in user turns. Per-user overrides: `dms["<user_id>"].historyLimit`.
|
||||||
- `retry`: retry policy for outbound Discord API calls (attempts, minDelayMs, maxDelayMs, jitter).
|
- `retry`: retry policy for outbound Discord API calls (attempts, minDelayMs, maxDelayMs, jitter).
|
||||||
|
- `pluralkit`: resolve PluralKit proxied messages so system members appear as distinct senders.
|
||||||
- `actions`: per-action tool gates; omit to allow all (set `false` to disable).
|
- `actions`: per-action tool gates; omit to allow all (set `false` to disable).
|
||||||
- `reactions` (covers react + read reactions)
|
- `reactions` (covers react + read reactions)
|
||||||
- `stickers`, `emojiUploads`, `stickerUploads`, `polls`, `permissions`, `messages`, `threads`, `pins`, `search`
|
- `stickers`, `emojiUploads`, `stickerUploads`, `polls`, `permissions`, `messages`, `threads`, `pins`, `search`
|
||||||
@@ -325,49 +353,84 @@ ack reaction after the bot replies.
|
|||||||
- `channels` (create/edit/delete channels + categories + permissions)
|
- `channels` (create/edit/delete channels + categories + permissions)
|
||||||
- `roles` (role add/remove, default `false`)
|
- `roles` (role add/remove, default `false`)
|
||||||
- `moderation` (timeout/kick/ban, default `false`)
|
- `moderation` (timeout/kick/ban, default `false`)
|
||||||
|
- `execApprovals`: Discord-only exec approval DMs (button UI). Supports `enabled`, `approvers`, `agentFilter`, `sessionFilter`.
|
||||||
|
|
||||||
Reaction notifications use `guilds.<id>.reactionNotifications`:
|
Reaction notifications use `guilds.<id>.reactionNotifications`:
|
||||||
|
|
||||||
- `off`: no reaction events.
|
- `off`: no reaction events.
|
||||||
- `own`: reactions on the bot's own messages (default).
|
- `own`: reactions on the bot's own messages (default).
|
||||||
- `all`: all reactions on all messages.
|
- `all`: all reactions on all messages.
|
||||||
- `allowlist`: reactions from `guilds.<id>.users` on all messages (empty list disables).
|
- `allowlist`: reactions from `guilds.<id>.users` on all messages (empty list disables).
|
||||||
|
|
||||||
|
### PluralKit (PK) support
|
||||||
|
|
||||||
|
Enable PK lookups so proxied messages resolve to the underlying system + member.
|
||||||
|
When enabled, OpenClaw uses the member identity for allowlists and labels the
|
||||||
|
sender as `Member (PK:System)` to avoid accidental Discord pings.
|
||||||
|
|
||||||
|
```json5
|
||||||
|
{
|
||||||
|
channels: {
|
||||||
|
discord: {
|
||||||
|
pluralkit: {
|
||||||
|
enabled: true,
|
||||||
|
token: "pk_live_...", // optional; required for private systems
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Allowlist notes (PK-enabled):
|
||||||
|
|
||||||
|
- Use `pk:<memberId>` in `dm.allowFrom`, `guilds.<id>.users`, or per-channel `users`.
|
||||||
|
- Member display names are also matched by name/slug.
|
||||||
|
- Lookups use the **original** Discord message ID (the pre-proxy message), so
|
||||||
|
the PK API only resolves it within its 30-minute window.
|
||||||
|
- If PK lookups fail (e.g., private system without a token), proxied messages
|
||||||
|
are treated as bot messages and are dropped unless `channels.discord.allowBots=true`.
|
||||||
|
|
||||||
### Tool action defaults
|
### Tool action defaults
|
||||||
|
|
||||||
| Action group | Default | Notes |
|
| Action group | Default | Notes |
|
||||||
| --- | --- | --- |
|
| -------------- | -------- | ---------------------------------- |
|
||||||
| reactions | enabled | React + list reactions + emojiList |
|
| reactions | enabled | React + list reactions + emojiList |
|
||||||
| stickers | enabled | Send stickers |
|
| stickers | enabled | Send stickers |
|
||||||
| emojiUploads | enabled | Upload emojis |
|
| emojiUploads | enabled | Upload emojis |
|
||||||
| stickerUploads | enabled | Upload stickers |
|
| stickerUploads | enabled | Upload stickers |
|
||||||
| polls | enabled | Create polls |
|
| polls | enabled | Create polls |
|
||||||
| permissions | enabled | Channel permission snapshot |
|
| permissions | enabled | Channel permission snapshot |
|
||||||
| messages | enabled | Read/send/edit/delete |
|
| messages | enabled | Read/send/edit/delete |
|
||||||
| threads | enabled | Create/list/reply |
|
| threads | enabled | Create/list/reply |
|
||||||
| pins | enabled | Pin/unpin/list |
|
| pins | enabled | Pin/unpin/list |
|
||||||
| search | enabled | Message search (preview feature) |
|
| search | enabled | Message search (preview feature) |
|
||||||
| memberInfo | enabled | Member info |
|
| memberInfo | enabled | Member info |
|
||||||
| roleInfo | enabled | Role list |
|
| roleInfo | enabled | Role list |
|
||||||
| channelInfo | enabled | Channel info + list |
|
| channelInfo | enabled | Channel info + list |
|
||||||
| channels | enabled | Channel/category management |
|
| channels | enabled | Channel/category management |
|
||||||
| voiceStatus | enabled | Voice state lookup |
|
| voiceStatus | enabled | Voice state lookup |
|
||||||
| events | enabled | List/create scheduled events |
|
| events | enabled | List/create scheduled events |
|
||||||
| roles | disabled | Role add/remove |
|
| roles | disabled | Role add/remove |
|
||||||
| moderation | disabled | Timeout/kick/ban |
|
| moderation | disabled | Timeout/kick/ban |
|
||||||
|
|
||||||
- `replyToMode`: `off` (default), `first`, or `all`. Applies only when the model includes a reply tag.
|
- `replyToMode`: `off` (default), `first`, or `all`. Applies only when the model includes a reply tag.
|
||||||
|
|
||||||
## Reply tags
|
## Reply tags
|
||||||
|
|
||||||
To request a threaded reply, the model can include one tag in its output:
|
To request a threaded reply, the model can include one tag in its output:
|
||||||
|
|
||||||
- `[[reply_to_current]]` — reply to the triggering Discord message.
|
- `[[reply_to_current]]` — reply to the triggering Discord message.
|
||||||
- `[[reply_to:<id>]]` — reply to a specific message id from context/history.
|
- `[[reply_to:<id>]]` — reply to a specific message id from context/history.
|
||||||
Current message ids are appended to prompts as `[message_id: …]`; history entries already include ids.
|
Current message ids are appended to prompts as `[message_id: …]`; history entries already include ids.
|
||||||
|
|
||||||
Behavior is controlled by `channels.discord.replyToMode`:
|
Behavior is controlled by `channels.discord.replyToMode`:
|
||||||
|
|
||||||
- `off`: ignore tags.
|
- `off`: ignore tags.
|
||||||
- `first`: only the first outbound chunk/attachment is a reply.
|
- `first`: only the first outbound chunk/attachment is a reply.
|
||||||
- `all`: every outbound chunk/attachment is a reply.
|
- `all`: every outbound chunk/attachment is a reply.
|
||||||
|
|
||||||
Allowlist matching notes:
|
Allowlist matching notes:
|
||||||
|
|
||||||
- `allowFrom`/`users`/`groupChannels` accept ids, names, tags, or mentions like `<@id>`.
|
- `allowFrom`/`users`/`groupChannels` accept ids, names, tags, or mentions like `<@id>`.
|
||||||
- Prefixes like `discord:`/`user:` (users) and `channel:` (group DMs) are supported.
|
- Prefixes like `discord:`/`user:` (users) and `channel:` (group DMs) are supported.
|
||||||
- Use `*` to allow any sender/channel.
|
- Use `*` to allow any sender/channel.
|
||||||
@@ -379,12 +442,15 @@ Allowlist matching notes:
|
|||||||
and logs the mapping; unresolved entries are kept as typed.
|
and logs the mapping; unresolved entries are kept as typed.
|
||||||
|
|
||||||
Native command notes:
|
Native command notes:
|
||||||
|
|
||||||
- The registered commands mirror OpenClaw’s chat commands.
|
- The registered commands mirror OpenClaw’s chat commands.
|
||||||
- Native commands honor the same allowlists as DMs/guild messages (`channels.discord.dm.allowFrom`, `channels.discord.guilds`, per-channel rules).
|
- Native commands honor the same allowlists as DMs/guild messages (`channels.discord.dm.allowFrom`, `channels.discord.guilds`, per-channel rules).
|
||||||
- Slash commands may still be visible in Discord UI to users who aren’t allowlisted; OpenClaw enforces allowlists on execution and replies “not authorized”.
|
- Slash commands may still be visible in Discord UI to users who aren’t allowlisted; OpenClaw enforces allowlists on execution and replies “not authorized”.
|
||||||
|
|
||||||
## Tool actions
|
## Tool actions
|
||||||
|
|
||||||
The agent can call `discord` with actions like:
|
The agent can call `discord` with actions like:
|
||||||
|
|
||||||
- `react` / `reactions` (add or list reactions)
|
- `react` / `reactions` (add or list reactions)
|
||||||
- `sticker`, `poll`, `permissions`
|
- `sticker`, `poll`, `permissions`
|
||||||
- `readMessages`, `sendMessage`, `editMessage`, `deleteMessage`
|
- `readMessages`, `sendMessage`, `editMessage`, `deleteMessage`
|
||||||
@@ -399,6 +465,7 @@ Discord message ids are surfaced in the injected context (`[discord message id:
|
|||||||
Emoji can be unicode (e.g., `✅`) or custom emoji syntax like `<:party_blob:1234567890>`.
|
Emoji can be unicode (e.g., `✅`) or custom emoji syntax like `<:party_blob:1234567890>`.
|
||||||
|
|
||||||
## Safety & ops
|
## Safety & ops
|
||||||
|
|
||||||
- Treat the bot token like a password; prefer the `DISCORD_BOT_TOKEN` env var on supervised hosts or lock down the config file permissions.
|
- Treat the bot token like a password; prefer the `DISCORD_BOT_TOKEN` env var on supervised hosts or lock down the config file permissions.
|
||||||
- Only grant the bot permissions it needs (typically Read/Send Messages).
|
- Only grant the bot permissions it needs (typically Read/Send Messages).
|
||||||
- If the bot is stuck or rate limited, restart the gateway (`openclaw gateway --force`) after confirming no other processes own the Discord session.
|
- If the bot is stuck or rate limited, restart the gateway (`openclaw gateway --force`) after confirming no other processes own the Discord session.
|
||||||
|
|||||||
@@ -2,27 +2,30 @@
|
|||||||
summary: "Google Chat app support status, capabilities, and configuration"
|
summary: "Google Chat app support status, capabilities, and configuration"
|
||||||
read_when:
|
read_when:
|
||||||
- Working on Google Chat channel features
|
- Working on Google Chat channel features
|
||||||
|
title: "Google Chat"
|
||||||
---
|
---
|
||||||
|
|
||||||
# Google Chat (Chat API)
|
# Google Chat (Chat API)
|
||||||
|
|
||||||
Status: ready for DMs + spaces via Google Chat API webhooks (HTTP only).
|
Status: ready for DMs + spaces via Google Chat API webhooks (HTTP only).
|
||||||
|
|
||||||
## Quick setup (beginner)
|
## Quick setup (beginner)
|
||||||
1) Create a Google Cloud project and enable the **Google Chat API**.
|
|
||||||
|
1. Create a Google Cloud project and enable the **Google Chat API**.
|
||||||
- Go to: [Google Chat API Credentials](https://console.cloud.google.com/apis/api/chat.googleapis.com/credentials)
|
- Go to: [Google Chat API Credentials](https://console.cloud.google.com/apis/api/chat.googleapis.com/credentials)
|
||||||
- Enable the API if it is not already enabled.
|
- Enable the API if it is not already enabled.
|
||||||
2) Create a **Service Account**:
|
2. Create a **Service Account**:
|
||||||
- Press **Create Credentials** > **Service Account**.
|
- Press **Create Credentials** > **Service Account**.
|
||||||
- Name it whatever you want (e.g., `openclaw-chat`).
|
- Name it whatever you want (e.g., `openclaw-chat`).
|
||||||
- Leave permissions blank (press **Continue**).
|
- Leave permissions blank (press **Continue**).
|
||||||
- Leave principals with access blank (press **Done**).
|
- Leave principals with access blank (press **Done**).
|
||||||
3) Create and download the **JSON Key**:
|
3. Create and download the **JSON Key**:
|
||||||
- In the list of service accounts, click on the one you just created.
|
- In the list of service accounts, click on the one you just created.
|
||||||
- Go to the **Keys** tab.
|
- Go to the **Keys** tab.
|
||||||
- Click **Add Key** > **Create new key**.
|
- Click **Add Key** > **Create new key**.
|
||||||
- Select **JSON** and press **Create**.
|
- Select **JSON** and press **Create**.
|
||||||
4) Store the downloaded JSON file on your gateway host (e.g., `~/.openclaw/googlechat-service-account.json`).
|
4. Store the downloaded JSON file on your gateway host (e.g., `~/.openclaw/googlechat-service-account.json`).
|
||||||
5) Create a Google Chat app in the [Google Cloud Console Chat Configuration](https://console.cloud.google.com/apis/api/chat.googleapis.com/hangouts-chat):
|
5. Create a Google Chat app in the [Google Cloud Console Chat Configuration](https://console.cloud.google.com/apis/api/chat.googleapis.com/hangouts-chat):
|
||||||
- Fill in the **Application info**:
|
- Fill in the **Application info**:
|
||||||
- **App name**: (e.g. `OpenClaw`)
|
- **App name**: (e.g. `OpenClaw`)
|
||||||
- **Avatar URL**: (e.g. `https://openclaw.ai/logo.png`)
|
- **Avatar URL**: (e.g. `https://openclaw.ai/logo.png`)
|
||||||
@@ -31,44 +34,51 @@ Status: ready for DMs + spaces via Google Chat API webhooks (HTTP only).
|
|||||||
- Under **Functionality**, check **Join spaces and group conversations**.
|
- Under **Functionality**, check **Join spaces and group conversations**.
|
||||||
- Under **Connection settings**, select **HTTP endpoint URL**.
|
- Under **Connection settings**, select **HTTP endpoint URL**.
|
||||||
- Under **Triggers**, select **Use a common HTTP endpoint URL for all triggers** and set it to your gateway's public URL followed by `/googlechat`.
|
- Under **Triggers**, select **Use a common HTTP endpoint URL for all triggers** and set it to your gateway's public URL followed by `/googlechat`.
|
||||||
- *Tip: Run `openclaw status` to find your gateway's public URL.*
|
- _Tip: Run `openclaw status` to find your gateway's public URL._
|
||||||
- Under **Visibility**, check **Make this Chat app available to specific people and groups in <Your Domain>**.
|
- Under **Visibility**, check **Make this Chat app available to specific people and groups in <Your Domain>**.
|
||||||
- Enter your email address (e.g. `user@example.com`) in the text box.
|
- Enter your email address (e.g. `user@example.com`) in the text box.
|
||||||
- Click **Save** at the bottom.
|
- Click **Save** at the bottom.
|
||||||
6) **Enable the app status**:
|
6. **Enable the app status**:
|
||||||
- After saving, **refresh the page**.
|
- After saving, **refresh the page**.
|
||||||
- Look for the **App status** section (usually near the top or bottom after saving).
|
- Look for the **App status** section (usually near the top or bottom after saving).
|
||||||
- Change the status to **Live - available to users**.
|
- Change the status to **Live - available to users**.
|
||||||
- Click **Save** again.
|
- Click **Save** again.
|
||||||
7) Configure OpenClaw with the service account path + webhook audience:
|
7. Configure OpenClaw with the service account path + webhook audience:
|
||||||
- Env: `GOOGLE_CHAT_SERVICE_ACCOUNT_FILE=/path/to/service-account.json`
|
- Env: `GOOGLE_CHAT_SERVICE_ACCOUNT_FILE=/path/to/service-account.json`
|
||||||
- Or config: `channels.googlechat.serviceAccountFile: "/path/to/service-account.json"`.
|
- Or config: `channels.googlechat.serviceAccountFile: "/path/to/service-account.json"`.
|
||||||
8) Set the webhook audience type + value (matches your Chat app config).
|
8. Set the webhook audience type + value (matches your Chat app config).
|
||||||
9) Start the gateway. Google Chat will POST to your webhook path.
|
9. Start the gateway. Google Chat will POST to your webhook path.
|
||||||
|
|
||||||
## Add to Google Chat
|
## Add to Google Chat
|
||||||
|
|
||||||
Once the gateway is running and your email is added to the visibility list:
|
Once the gateway is running and your email is added to the visibility list:
|
||||||
1) Go to [Google Chat](https://chat.google.com/).
|
|
||||||
2) Click the **+** (plus) icon next to **Direct Messages**.
|
1. Go to [Google Chat](https://chat.google.com/).
|
||||||
3) In the search bar (where you usually add people), type the **App name** you configured in the Google Cloud Console.
|
2. Click the **+** (plus) icon next to **Direct Messages**.
|
||||||
- **Note**: The bot will *not* appear in the "Marketplace" browse list because it is a private app. You must search for it by name.
|
3. In the search bar (where you usually add people), type the **App name** you configured in the Google Cloud Console.
|
||||||
4) Select your bot from the results.
|
- **Note**: The bot will _not_ appear in the "Marketplace" browse list because it is a private app. You must search for it by name.
|
||||||
5) Click **Add** or **Chat** to start a 1:1 conversation.
|
4. Select your bot from the results.
|
||||||
6) Send "Hello" to trigger the assistant!
|
5. Click **Add** or **Chat** to start a 1:1 conversation.
|
||||||
|
6. Send "Hello" to trigger the assistant!
|
||||||
|
|
||||||
## Public URL (Webhook-only)
|
## Public URL (Webhook-only)
|
||||||
|
|
||||||
Google Chat webhooks require a public HTTPS endpoint. For security, **only expose the `/googlechat` path** to the internet. Keep the OpenClaw dashboard and other sensitive endpoints on your private network.
|
Google Chat webhooks require a public HTTPS endpoint. For security, **only expose the `/googlechat` path** to the internet. Keep the OpenClaw dashboard and other sensitive endpoints on your private network.
|
||||||
|
|
||||||
### Option A: Tailscale Funnel (Recommended)
|
### Option A: Tailscale Funnel (Recommended)
|
||||||
|
|
||||||
Use Tailscale Serve for the private dashboard and Funnel for the public webhook path. This keeps `/` private while exposing only `/googlechat`.
|
Use Tailscale Serve for the private dashboard and Funnel for the public webhook path. This keeps `/` private while exposing only `/googlechat`.
|
||||||
|
|
||||||
1. **Check what address your gateway is bound to:**
|
1. **Check what address your gateway is bound to:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ss -tlnp | grep 18789
|
ss -tlnp | grep 18789
|
||||||
```
|
```
|
||||||
|
|
||||||
Note the IP address (e.g., `127.0.0.1`, `0.0.0.0`, or your Tailscale IP like `100.x.x.x`).
|
Note the IP address (e.g., `127.0.0.1`, `0.0.0.0`, or your Tailscale IP like `100.x.x.x`).
|
||||||
|
|
||||||
2. **Expose the dashboard to the tailnet only (port 8443):**
|
2. **Expose the dashboard to the tailnet only (port 8443):**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# If bound to localhost (127.0.0.1 or 0.0.0.0):
|
# If bound to localhost (127.0.0.1 or 0.0.0.0):
|
||||||
tailscale serve --bg --https 8443 http://127.0.0.1:18789
|
tailscale serve --bg --https 8443 http://127.0.0.1:18789
|
||||||
@@ -78,6 +88,7 @@ Use Tailscale Serve for the private dashboard and Funnel for the public webhook
|
|||||||
```
|
```
|
||||||
|
|
||||||
3. **Expose only the webhook path publicly:**
|
3. **Expose only the webhook path publicly:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# If bound to localhost (127.0.0.1 or 0.0.0.0):
|
# If bound to localhost (127.0.0.1 or 0.0.0.0):
|
||||||
tailscale funnel --bg --set-path /googlechat http://127.0.0.1:18789/googlechat
|
tailscale funnel --bg --set-path /googlechat http://127.0.0.1:18789/googlechat
|
||||||
@@ -106,16 +117,21 @@ Use the public URL (without `:8443`) in the Google Chat app config.
|
|||||||
> Note: This configuration persists across reboots. To remove it later, run `tailscale funnel reset` and `tailscale serve reset`.
|
> Note: This configuration persists across reboots. To remove it later, run `tailscale funnel reset` and `tailscale serve reset`.
|
||||||
|
|
||||||
### Option B: Reverse Proxy (Caddy)
|
### Option B: Reverse Proxy (Caddy)
|
||||||
|
|
||||||
If you use a reverse proxy like Caddy, only proxy the specific path:
|
If you use a reverse proxy like Caddy, only proxy the specific path:
|
||||||
|
|
||||||
```caddy
|
```caddy
|
||||||
your-domain.com {
|
your-domain.com {
|
||||||
reverse_proxy /googlechat* localhost:18789
|
reverse_proxy /googlechat* localhost:18789
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
With this config, any request to `your-domain.com/` will be ignored or returned as 404, while `your-domain.com/googlechat` is safely routed to OpenClaw.
|
With this config, any request to `your-domain.com/` will be ignored or returned as 404, while `your-domain.com/googlechat` is safely routed to OpenClaw.
|
||||||
|
|
||||||
### Option C: Cloudflare Tunnel
|
### Option C: Cloudflare Tunnel
|
||||||
|
|
||||||
Configure your tunnel's ingress rules to only route the webhook path:
|
Configure your tunnel's ingress rules to only route the webhook path:
|
||||||
|
|
||||||
- **Path**: `/googlechat` -> `http://localhost:18789/googlechat`
|
- **Path**: `/googlechat` -> `http://localhost:18789/googlechat`
|
||||||
- **Default Rule**: HTTP 404 (Not Found)
|
- **Default Rule**: HTTP 404 (Not Found)
|
||||||
|
|
||||||
@@ -133,15 +149,18 @@ Configure your tunnel's ingress rules to only route the webhook path:
|
|||||||
5. Group spaces require @-mention by default. Use `botUser` if mention detection needs the app’s user name.
|
5. Group spaces require @-mention by default. Use `botUser` if mention detection needs the app’s user name.
|
||||||
|
|
||||||
## Targets
|
## Targets
|
||||||
|
|
||||||
Use these identifiers for delivery and allowlists:
|
Use these identifiers for delivery and allowlists:
|
||||||
|
|
||||||
- Direct messages: `users/<userId>` or `users/<email>` (email addresses are accepted).
|
- Direct messages: `users/<userId>` or `users/<email>` (email addresses are accepted).
|
||||||
- Spaces: `spaces/<spaceId>`.
|
- Spaces: `spaces/<spaceId>`.
|
||||||
|
|
||||||
## Config highlights
|
## Config highlights
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
channels: {
|
channels: {
|
||||||
"googlechat": {
|
googlechat: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
serviceAccountFile: "/path/to/service-account.json",
|
serviceAccountFile: "/path/to/service-account.json",
|
||||||
audienceType: "app-url",
|
audienceType: "app-url",
|
||||||
@@ -150,7 +169,7 @@ Use these identifiers for delivery and allowlists:
|
|||||||
botUser: "users/1234567890", // optional; helps mention detection
|
botUser: "users/1234567890", // optional; helps mention detection
|
||||||
dm: {
|
dm: {
|
||||||
policy: "pairing",
|
policy: "pairing",
|
||||||
allowFrom: ["users/1234567890", "name@example.com"]
|
allowFrom: ["users/1234567890", "name@example.com"],
|
||||||
},
|
},
|
||||||
groupPolicy: "allowlist",
|
groupPolicy: "allowlist",
|
||||||
groups: {
|
groups: {
|
||||||
@@ -158,18 +177,19 @@ Use these identifiers for delivery and allowlists:
|
|||||||
allow: true,
|
allow: true,
|
||||||
requireMention: true,
|
requireMention: true,
|
||||||
users: ["users/1234567890"],
|
users: ["users/1234567890"],
|
||||||
systemPrompt: "Short answers only."
|
systemPrompt: "Short answers only.",
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
actions: { reactions: true },
|
actions: { reactions: true },
|
||||||
typingIndicator: "message",
|
typingIndicator: "message",
|
||||||
mediaMaxMb: 20
|
mediaMaxMb: 20,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
- Service account credentials can also be passed inline with `serviceAccount` (JSON string).
|
- Service account credentials can also be passed inline with `serviceAccount` (JSON string).
|
||||||
- Default webhook path is `/googlechat` if `webhookPath` isn’t set.
|
- Default webhook path is `/googlechat` if `webhookPath` isn’t set.
|
||||||
- Reactions are available via the `reactions` tool and `channels action` when `actions.reactions` is enabled.
|
- Reactions are available via the `reactions` tool and `channels action` when `actions.reactions` is enabled.
|
||||||
@@ -179,22 +199,29 @@ Notes:
|
|||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
### 405 Method Not Allowed
|
### 405 Method Not Allowed
|
||||||
|
|
||||||
If Google Cloud Logs Explorer shows errors like:
|
If Google Cloud Logs Explorer shows errors like:
|
||||||
|
|
||||||
```
|
```
|
||||||
status code: 405, reason phrase: HTTP error response: HTTP/1.1 405 Method Not Allowed
|
status code: 405, reason phrase: HTTP error response: HTTP/1.1 405 Method Not Allowed
|
||||||
```
|
```
|
||||||
|
|
||||||
This means the webhook handler isn't registered. Common causes:
|
This means the webhook handler isn't registered. Common causes:
|
||||||
|
|
||||||
1. **Channel not configured**: The `channels.googlechat` section is missing from your config. Verify with:
|
1. **Channel not configured**: The `channels.googlechat` section is missing from your config. Verify with:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
openclaw config get channels.googlechat
|
openclaw config get channels.googlechat
|
||||||
```
|
```
|
||||||
|
|
||||||
If it returns "Config path not found", add the configuration (see [Config highlights](#config-highlights)).
|
If it returns "Config path not found", add the configuration (see [Config highlights](#config-highlights)).
|
||||||
|
|
||||||
2. **Plugin not enabled**: Check plugin status:
|
2. **Plugin not enabled**: Check plugin status:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
openclaw plugins list | grep googlechat
|
openclaw plugins list | grep googlechat
|
||||||
```
|
```
|
||||||
|
|
||||||
If it shows "disabled", add `plugins.entries.googlechat.enabled: true` to your config.
|
If it shows "disabled", add `plugins.entries.googlechat.enabled: true` to your config.
|
||||||
|
|
||||||
3. **Gateway not restarted**: After adding config, restart the gateway:
|
3. **Gateway not restarted**: After adding config, restart the gateway:
|
||||||
@@ -203,18 +230,21 @@ This means the webhook handler isn't registered. Common causes:
|
|||||||
```
|
```
|
||||||
|
|
||||||
Verify the channel is running:
|
Verify the channel is running:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
openclaw channels status
|
openclaw channels status
|
||||||
# Should show: Google Chat default: enabled, configured, ...
|
# Should show: Google Chat default: enabled, configured, ...
|
||||||
```
|
```
|
||||||
|
|
||||||
### Other issues
|
### Other issues
|
||||||
|
|
||||||
- Check `openclaw channels status --probe` for auth errors or missing audience config.
|
- Check `openclaw channels status --probe` for auth errors or missing audience config.
|
||||||
- If no messages arrive, confirm the Chat app's webhook URL + event subscriptions.
|
- If no messages arrive, confirm the Chat app's webhook URL + event subscriptions.
|
||||||
- If mention gating blocks replies, set `botUser` to the app's user resource name and verify `requireMention`.
|
- If mention gating blocks replies, set `botUser` to the app's user resource name and verify `requireMention`.
|
||||||
- Use `openclaw logs --follow` while sending a test message to see if requests reach the gateway.
|
- Use `openclaw logs --follow` while sending a test message to see if requests reach the gateway.
|
||||||
|
|
||||||
Related docs:
|
Related docs:
|
||||||
|
|
||||||
- [Gateway configuration](/gateway/configuration)
|
- [Gateway configuration](/gateway/configuration)
|
||||||
- [Security](/gateway/security)
|
- [Security](/gateway/security)
|
||||||
- [Reactions](/tools/reactions)
|
- [Reactions](/tools/reactions)
|
||||||
|
|||||||
@@ -2,26 +2,30 @@
|
|||||||
summary: "Telegram Bot API integration via grammY with setup notes"
|
summary: "Telegram Bot API integration via grammY with setup notes"
|
||||||
read_when:
|
read_when:
|
||||||
- Working on Telegram or grammY pathways
|
- Working on Telegram or grammY pathways
|
||||||
|
title: grammY
|
||||||
---
|
---
|
||||||
|
|
||||||
# grammY Integration (Telegram Bot API)
|
# grammY Integration (Telegram Bot API)
|
||||||
|
|
||||||
|
|
||||||
# Why grammY
|
# Why grammY
|
||||||
|
|
||||||
- TS-first Bot API client with built-in long-poll + webhook helpers, middleware, error handling, rate limiter.
|
- TS-first Bot API client with built-in long-poll + webhook helpers, middleware, error handling, rate limiter.
|
||||||
- Cleaner media helpers than hand-rolling fetch + FormData; supports all Bot API methods.
|
- Cleaner media helpers than hand-rolling fetch + FormData; supports all Bot API methods.
|
||||||
- Extensible: proxy support via custom fetch, session middleware (optional), type-safe context.
|
- Extensible: proxy support via custom fetch, session middleware (optional), type-safe context.
|
||||||
|
|
||||||
# What we shipped
|
# What we shipped
|
||||||
|
|
||||||
- **Single client path:** fetch-based implementation removed; grammY is now the sole Telegram client (send + gateway) with the grammY throttler enabled by default.
|
- **Single client path:** fetch-based implementation removed; grammY is now the sole Telegram client (send + gateway) with the grammY throttler enabled by default.
|
||||||
- **Gateway:** `monitorTelegramProvider` builds a grammY `Bot`, wires mention/allowlist gating, media download via `getFile`/`download`, and delivers replies with `sendMessage/sendPhoto/sendVideo/sendAudio/sendDocument`. Supports long-poll or webhook via `webhookCallback`.
|
- **Gateway:** `monitorTelegramProvider` builds a grammY `Bot`, wires mention/allowlist gating, media download via `getFile`/`download`, and delivers replies with `sendMessage/sendPhoto/sendVideo/sendAudio/sendDocument`. Supports long-poll or webhook via `webhookCallback`.
|
||||||
- **Proxy:** optional `channels.telegram.proxy` uses `undici.ProxyAgent` through grammY’s `client.baseFetch`.
|
- **Proxy:** optional `channels.telegram.proxy` uses `undici.ProxyAgent` through grammY’s `client.baseFetch`.
|
||||||
- **Webhook support:** `webhook-set.ts` wraps `setWebhook/deleteWebhook`; `webhook.ts` hosts the callback with health + graceful shutdown. Gateway enables webhook mode when `channels.telegram.webhookUrl` is set (otherwise it long-polls).
|
- **Webhook support:** `webhook-set.ts` wraps `setWebhook/deleteWebhook`; `webhook.ts` hosts the callback with health + graceful shutdown. Gateway enables webhook mode when `channels.telegram.webhookUrl` + `channels.telegram.webhookSecret` are set (otherwise it long-polls).
|
||||||
- **Sessions:** direct chats collapse into the agent main session (`agent:<agentId>:<mainKey>`); groups use `agent:<agentId>:telegram:group:<chatId>`; replies route back to the same channel.
|
- **Sessions:** direct chats collapse into the agent main session (`agent:<agentId>:<mainKey>`); groups use `agent:<agentId>:telegram:group:<chatId>`; replies route back to the same channel.
|
||||||
- **Config knobs:** `channels.telegram.botToken`, `channels.telegram.dmPolicy`, `channels.telegram.groups` (allowlist + mention defaults), `channels.telegram.allowFrom`, `channels.telegram.groupAllowFrom`, `channels.telegram.groupPolicy`, `channels.telegram.mediaMaxMb`, `channels.telegram.linkPreview`, `channels.telegram.proxy`, `channels.telegram.webhookSecret`, `channels.telegram.webhookUrl`.
|
- **Config knobs:** `channels.telegram.botToken`, `channels.telegram.dmPolicy`, `channels.telegram.groups` (allowlist + mention defaults), `channels.telegram.allowFrom`, `channels.telegram.groupAllowFrom`, `channels.telegram.groupPolicy`, `channels.telegram.mediaMaxMb`, `channels.telegram.linkPreview`, `channels.telegram.proxy`, `channels.telegram.webhookSecret`, `channels.telegram.webhookUrl`.
|
||||||
- **Draft streaming:** optional `channels.telegram.streamMode` uses `sendMessageDraft` in private topic chats (Bot API 9.3+). This is separate from channel block streaming.
|
- **Draft streaming:** optional `channels.telegram.streamMode` uses `sendMessageDraft` in private topic chats (Bot API 9.3+). This is separate from channel block streaming.
|
||||||
- **Tests:** grammy mocks cover DM + group mention gating and outbound send; more media/webhook fixtures still welcome.
|
- **Tests:** grammy mocks cover DM + group mention gating and outbound send; more media/webhook fixtures still welcome.
|
||||||
|
|
||||||
Open questions
|
Open questions
|
||||||
|
|
||||||
- Optional grammY plugins (throttler) if we hit Bot API 429s.
|
- Optional grammY plugins (throttler) if we hit Bot API 429s.
|
||||||
- Add more structured media tests (stickers, voice notes).
|
- Add more structured media tests (stickers, voice notes).
|
||||||
- Make webhook listen port configurable (currently fixed to 8787 unless wired through the gateway).
|
- Make webhook listen port configurable (currently fixed to 8787 unless wired through the gateway).
|
||||||
|
|||||||
@@ -3,74 +3,84 @@ summary: "iMessage support via imsg (JSON-RPC over stdio), setup, and chat_id ro
|
|||||||
read_when:
|
read_when:
|
||||||
- Setting up iMessage support
|
- Setting up iMessage support
|
||||||
- Debugging iMessage send/receive
|
- Debugging iMessage send/receive
|
||||||
|
title: iMessage
|
||||||
---
|
---
|
||||||
# iMessage (imsg)
|
|
||||||
|
|
||||||
|
# iMessage (imsg)
|
||||||
|
|
||||||
Status: external CLI integration. Gateway spawns `imsg rpc` (JSON-RPC over stdio).
|
Status: external CLI integration. Gateway spawns `imsg rpc` (JSON-RPC over stdio).
|
||||||
|
|
||||||
## Quick setup (beginner)
|
## Quick setup (beginner)
|
||||||
1) Ensure Messages is signed in on this Mac.
|
|
||||||
2) Install `imsg`:
|
1. Ensure Messages is signed in on this Mac.
|
||||||
|
2. Install `imsg`:
|
||||||
- `brew install steipete/tap/imsg`
|
- `brew install steipete/tap/imsg`
|
||||||
3) Configure OpenClaw with `channels.imessage.cliPath` and `channels.imessage.dbPath`.
|
3. Configure OpenClaw with `channels.imessage.cliPath` and `channels.imessage.dbPath`.
|
||||||
4) Start the gateway and approve any macOS prompts (Automation + Full Disk Access).
|
4. Start the gateway and approve any macOS prompts (Automation + Full Disk Access).
|
||||||
|
|
||||||
Minimal config:
|
Minimal config:
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
channels: {
|
channels: {
|
||||||
imessage: {
|
imessage: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
cliPath: "/usr/local/bin/imsg",
|
cliPath: "/usr/local/bin/imsg",
|
||||||
dbPath: "/Users/<you>/Library/Messages/chat.db"
|
dbPath: "/Users/<you>/Library/Messages/chat.db",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## What it is
|
## What it is
|
||||||
|
|
||||||
- iMessage channel backed by `imsg` on macOS.
|
- iMessage channel backed by `imsg` on macOS.
|
||||||
- Deterministic routing: replies always go back to iMessage.
|
- Deterministic routing: replies always go back to iMessage.
|
||||||
- DMs share the agent's main session; groups are isolated (`agent:<agentId>:imessage:group:<chat_id>`).
|
- DMs share the agent's main session; groups are isolated (`agent:<agentId>:imessage:group:<chat_id>`).
|
||||||
- If a multi-participant thread arrives with `is_group=false`, you can still isolate it by `chat_id` using `channels.imessage.groups` (see “Group-ish threads” below).
|
- If a multi-participant thread arrives with `is_group=false`, you can still isolate it by `chat_id` using `channels.imessage.groups` (see “Group-ish threads” below).
|
||||||
|
|
||||||
## Config writes
|
## Config writes
|
||||||
|
|
||||||
By default, iMessage is allowed to write config updates triggered by `/config set|unset` (requires `commands.config: true`).
|
By default, iMessage is allowed to write config updates triggered by `/config set|unset` (requires `commands.config: true`).
|
||||||
|
|
||||||
Disable with:
|
Disable with:
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
channels: { imessage: { configWrites: false } }
|
channels: { imessage: { configWrites: false } },
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
- macOS with Messages signed in.
|
- macOS with Messages signed in.
|
||||||
- Full Disk Access for OpenClaw + `imsg` (Messages DB access).
|
- Full Disk Access for OpenClaw + `imsg` (Messages DB access).
|
||||||
- Automation permission when sending.
|
- Automation permission when sending.
|
||||||
- `channels.imessage.cliPath` can point to any command that proxies stdin/stdout (for example, a wrapper script that SSHes to another Mac and runs `imsg rpc`).
|
- `channels.imessage.cliPath` can point to any command that proxies stdin/stdout (for example, a wrapper script that SSHes to another Mac and runs `imsg rpc`).
|
||||||
|
|
||||||
## Setup (fast path)
|
## Setup (fast path)
|
||||||
1) Ensure Messages is signed in on this Mac.
|
|
||||||
2) Configure iMessage and start the gateway.
|
1. Ensure Messages is signed in on this Mac.
|
||||||
|
2. Configure iMessage and start the gateway.
|
||||||
|
|
||||||
### Dedicated bot macOS user (for isolated identity)
|
### Dedicated bot macOS user (for isolated identity)
|
||||||
|
|
||||||
If you want the bot to send from a **separate iMessage identity** (and keep your personal Messages clean), use a dedicated Apple ID + a dedicated macOS user.
|
If you want the bot to send from a **separate iMessage identity** (and keep your personal Messages clean), use a dedicated Apple ID + a dedicated macOS user.
|
||||||
|
|
||||||
1) Create a dedicated Apple ID (example: `my-cool-bot@icloud.com`).
|
1. Create a dedicated Apple ID (example: `my-cool-bot@icloud.com`).
|
||||||
- Apple may require a phone number for verification / 2FA.
|
- Apple may require a phone number for verification / 2FA.
|
||||||
2) Create a macOS user (example: `openclawhome`) and sign into it.
|
2. Create a macOS user (example: `openclawhome`) and sign into it.
|
||||||
3) Open Messages in that macOS user and sign into iMessage using the bot Apple ID.
|
3. Open Messages in that macOS user and sign into iMessage using the bot Apple ID.
|
||||||
4) Enable Remote Login (System Settings → General → Sharing → Remote Login).
|
4. Enable Remote Login (System Settings → General → Sharing → Remote Login).
|
||||||
5) Install `imsg`:
|
5. Install `imsg`:
|
||||||
- `brew install steipete/tap/imsg`
|
- `brew install steipete/tap/imsg`
|
||||||
6) Set up SSH so `ssh <bot-macos-user>@localhost true` works without a password.
|
6. Set up SSH so `ssh <bot-macos-user>@localhost true` works without a password.
|
||||||
7) Point `channels.imessage.accounts.bot.cliPath` at an SSH wrapper that runs `imsg` as the bot user.
|
7. Point `channels.imessage.accounts.bot.cliPath` at an SSH wrapper that runs `imsg` as the bot user.
|
||||||
|
|
||||||
First-run note: sending/receiving may require GUI approvals (Automation + Full Disk Access) in the *bot macOS user*. If `imsg rpc` looks stuck or exits, log into that user (Screen Sharing helps), run a one-time `imsg chats --limit 1` / `imsg send ...`, approve prompts, then retry.
|
First-run note: sending/receiving may require GUI approvals (Automation + Full Disk Access) in the _bot macOS user_. If `imsg rpc` looks stuck or exits, log into that user (Screen Sharing helps), run a one-time `imsg chats --limit 1` / `imsg send ...`, approve prompts, then retry.
|
||||||
|
|
||||||
Example wrapper (`chmod +x`). Replace `<bot-macos-user>` with your actual macOS username:
|
Example wrapper (`chmod +x`). Replace `<bot-macos-user>` with your actual macOS username:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
@@ -82,6 +92,7 @@ exec /usr/bin/ssh -o BatchMode=yes -o ConnectTimeout=5 -T <bot-macos-user>@local
|
|||||||
```
|
```
|
||||||
|
|
||||||
Example config:
|
Example config:
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
channels: {
|
channels: {
|
||||||
@@ -92,20 +103,22 @@ Example config:
|
|||||||
name: "Bot",
|
name: "Bot",
|
||||||
enabled: true,
|
enabled: true,
|
||||||
cliPath: "/path/to/imsg-bot",
|
cliPath: "/path/to/imsg-bot",
|
||||||
dbPath: "/Users/<bot-macos-user>/Library/Messages/chat.db"
|
dbPath: "/Users/<bot-macos-user>/Library/Messages/chat.db",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
For single-account setups, use flat options (`channels.imessage.cliPath`, `channels.imessage.dbPath`) instead of the `accounts` map.
|
For single-account setups, use flat options (`channels.imessage.cliPath`, `channels.imessage.dbPath`) instead of the `accounts` map.
|
||||||
|
|
||||||
### Remote/SSH variant (optional)
|
### Remote/SSH variant (optional)
|
||||||
|
|
||||||
If you want iMessage on another Mac, set `channels.imessage.cliPath` to a wrapper that runs `imsg` on the remote macOS host over SSH. OpenClaw only needs stdio.
|
If you want iMessage on another Mac, set `channels.imessage.cliPath` to a wrapper that runs `imsg` on the remote macOS host over SSH. OpenClaw only needs stdio.
|
||||||
|
|
||||||
Example wrapper:
|
Example wrapper:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
exec ssh -T gateway-host imsg "$@"
|
exec ssh -T gateway-host imsg "$@"
|
||||||
@@ -117,20 +130,22 @@ exec ssh -T gateway-host imsg "$@"
|
|||||||
{
|
{
|
||||||
channels: {
|
channels: {
|
||||||
imessage: {
|
imessage: {
|
||||||
cliPath: "~/imsg-ssh", // SSH wrapper to remote Mac
|
cliPath: "~/imsg-ssh", // SSH wrapper to remote Mac
|
||||||
remoteHost: "user@gateway-host", // for SCP file transfer
|
remoteHost: "user@gateway-host", // for SCP file transfer
|
||||||
includeAttachments: true
|
includeAttachments: true,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
If `remoteHost` is not set, OpenClaw attempts to auto-detect it by parsing the SSH command in your wrapper script. Explicit configuration is recommended for reliability.
|
If `remoteHost` is not set, OpenClaw attempts to auto-detect it by parsing the SSH command in your wrapper script. Explicit configuration is recommended for reliability.
|
||||||
|
|
||||||
#### Remote Mac via Tailscale (example)
|
#### Remote Mac via Tailscale (example)
|
||||||
|
|
||||||
If the Gateway runs on a Linux host/VM but iMessage must run on a Mac, Tailscale is the simplest bridge: the Gateway talks to the Mac over the tailnet, runs `imsg` via SSH, and SCPs attachments back.
|
If the Gateway runs on a Linux host/VM but iMessage must run on a Mac, Tailscale is the simplest bridge: the Gateway talks to the Mac over the tailnet, runs `imsg` via SSH, and SCPs attachments back.
|
||||||
|
|
||||||
Architecture:
|
Architecture:
|
||||||
|
|
||||||
```
|
```
|
||||||
┌──────────────────────────────┐ SSH (imsg rpc) ┌──────────────────────────┐
|
┌──────────────────────────────┐ SSH (imsg rpc) ┌──────────────────────────┐
|
||||||
│ Gateway host (Linux/VM) │──────────────────────────────────▶│ Mac with Messages + imsg │
|
│ Gateway host (Linux/VM) │──────────────────────────────────▶│ Mac with Messages + imsg │
|
||||||
@@ -144,6 +159,7 @@ Architecture:
|
|||||||
```
|
```
|
||||||
|
|
||||||
Concrete config example (Tailscale hostname):
|
Concrete config example (Tailscale hostname):
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
channels: {
|
channels: {
|
||||||
@@ -152,19 +168,21 @@ Concrete config example (Tailscale hostname):
|
|||||||
cliPath: "~/.openclaw/scripts/imsg-ssh",
|
cliPath: "~/.openclaw/scripts/imsg-ssh",
|
||||||
remoteHost: "bot@mac-mini.tailnet-1234.ts.net",
|
remoteHost: "bot@mac-mini.tailnet-1234.ts.net",
|
||||||
includeAttachments: true,
|
includeAttachments: true,
|
||||||
dbPath: "/Users/bot/Library/Messages/chat.db"
|
dbPath: "/Users/bot/Library/Messages/chat.db",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Example wrapper (`~/.openclaw/scripts/imsg-ssh`):
|
Example wrapper (`~/.openclaw/scripts/imsg-ssh`):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
exec ssh -T bot@mac-mini.tailnet-1234.ts.net imsg "$@"
|
exec ssh -T bot@mac-mini.tailnet-1234.ts.net imsg "$@"
|
||||||
```
|
```
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
- Ensure the Mac is signed in to Messages, and Remote Login is enabled.
|
- Ensure the Mac is signed in to Messages, and Remote Login is enabled.
|
||||||
- Use SSH keys so `ssh bot@mac-mini.tailnet-1234.ts.net` works without prompts.
|
- Use SSH keys so `ssh bot@mac-mini.tailnet-1234.ts.net` works without prompts.
|
||||||
- `remoteHost` should match the SSH target so SCP can fetch attachments.
|
- `remoteHost` should match the SSH target so SCP can fetch attachments.
|
||||||
@@ -172,7 +190,9 @@ Notes:
|
|||||||
Multi-account support: use `channels.imessage.accounts` with per-account config and optional `name`. See [`gateway/configuration`](/gateway/configuration#telegramaccounts--discordaccounts--slackaccounts--signalaccounts--imessageaccounts) for the shared pattern. Don't commit `~/.openclaw/openclaw.json` (it often contains tokens).
|
Multi-account support: use `channels.imessage.accounts` with per-account config and optional `name`. See [`gateway/configuration`](/gateway/configuration#telegramaccounts--discordaccounts--slackaccounts--signalaccounts--imessageaccounts) for the shared pattern. Don't commit `~/.openclaw/openclaw.json` (it often contains tokens).
|
||||||
|
|
||||||
## Access control (DMs + groups)
|
## Access control (DMs + groups)
|
||||||
|
|
||||||
DMs:
|
DMs:
|
||||||
|
|
||||||
- Default: `channels.imessage.dmPolicy = "pairing"`.
|
- Default: `channels.imessage.dmPolicy = "pairing"`.
|
||||||
- Unknown senders receive a pairing code; messages are ignored until approved (codes expire after 1 hour).
|
- Unknown senders receive a pairing code; messages are ignored until approved (codes expire after 1 hour).
|
||||||
- Approve via:
|
- Approve via:
|
||||||
@@ -181,23 +201,28 @@ DMs:
|
|||||||
- Pairing is the default token exchange for iMessage DMs. Details: [Pairing](/start/pairing)
|
- Pairing is the default token exchange for iMessage DMs. Details: [Pairing](/start/pairing)
|
||||||
|
|
||||||
Groups:
|
Groups:
|
||||||
|
|
||||||
- `channels.imessage.groupPolicy = open | allowlist | disabled`.
|
- `channels.imessage.groupPolicy = open | allowlist | disabled`.
|
||||||
- `channels.imessage.groupAllowFrom` controls who can trigger in groups when `allowlist` is set.
|
- `channels.imessage.groupAllowFrom` controls who can trigger in groups when `allowlist` is set.
|
||||||
- Mention gating uses `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`) because iMessage has no native mention metadata.
|
- Mention gating uses `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`) because iMessage has no native mention metadata.
|
||||||
- Multi-agent override: set per-agent patterns on `agents.list[].groupChat.mentionPatterns`.
|
- Multi-agent override: set per-agent patterns on `agents.list[].groupChat.mentionPatterns`.
|
||||||
|
|
||||||
## How it works (behavior)
|
## How it works (behavior)
|
||||||
|
|
||||||
- `imsg` streams message events; the gateway normalizes them into the shared channel envelope.
|
- `imsg` streams message events; the gateway normalizes them into the shared channel envelope.
|
||||||
- Replies always route back to the same chat id or handle.
|
- Replies always route back to the same chat id or handle.
|
||||||
|
|
||||||
## Group-ish threads (`is_group=false`)
|
## Group-ish threads (`is_group=false`)
|
||||||
|
|
||||||
Some iMessage threads can have multiple participants but still arrive with `is_group=false` depending on how Messages stores the chat identifier.
|
Some iMessage threads can have multiple participants but still arrive with `is_group=false` depending on how Messages stores the chat identifier.
|
||||||
|
|
||||||
If you explicitly configure a `chat_id` under `channels.imessage.groups`, OpenClaw treats that thread as a “group” for:
|
If you explicitly configure a `chat_id` under `channels.imessage.groups`, OpenClaw treats that thread as a “group” for:
|
||||||
|
|
||||||
- session isolation (separate `agent:<agentId>:imessage:group:<chat_id>` session key)
|
- session isolation (separate `agent:<agentId>:imessage:group:<chat_id>` session key)
|
||||||
- group allowlisting / mention gating behavior
|
- group allowlisting / mention gating behavior
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
channels: {
|
channels: {
|
||||||
@@ -205,39 +230,47 @@ Example:
|
|||||||
groupPolicy: "allowlist",
|
groupPolicy: "allowlist",
|
||||||
groupAllowFrom: ["+15555550123"],
|
groupAllowFrom: ["+15555550123"],
|
||||||
groups: {
|
groups: {
|
||||||
"42": { "requireMention": false }
|
"42": { requireMention: false },
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
This is useful when you want an isolated personality/model for a specific thread (see [Multi-agent routing](/concepts/multi-agent)). For filesystem isolation, see [Sandboxing](/gateway/sandboxing).
|
This is useful when you want an isolated personality/model for a specific thread (see [Multi-agent routing](/concepts/multi-agent)). For filesystem isolation, see [Sandboxing](/gateway/sandboxing).
|
||||||
|
|
||||||
## Media + limits
|
## Media + limits
|
||||||
|
|
||||||
- Optional attachment ingestion via `channels.imessage.includeAttachments`.
|
- Optional attachment ingestion via `channels.imessage.includeAttachments`.
|
||||||
- Media cap via `channels.imessage.mediaMaxMb`.
|
- Media cap via `channels.imessage.mediaMaxMb`.
|
||||||
|
|
||||||
## Limits
|
## Limits
|
||||||
|
|
||||||
- Outbound text is chunked to `channels.imessage.textChunkLimit` (default 4000).
|
- Outbound text is chunked to `channels.imessage.textChunkLimit` (default 4000).
|
||||||
- Optional newline chunking: set `channels.imessage.chunkMode="newline"` to split on blank lines (paragraph boundaries) before length chunking.
|
- Optional newline chunking: set `channels.imessage.chunkMode="newline"` to split on blank lines (paragraph boundaries) before length chunking.
|
||||||
- Media uploads are capped by `channels.imessage.mediaMaxMb` (default 16).
|
- Media uploads are capped by `channels.imessage.mediaMaxMb` (default 16).
|
||||||
|
|
||||||
## Addressing / delivery targets
|
## Addressing / delivery targets
|
||||||
|
|
||||||
Prefer `chat_id` for stable routing:
|
Prefer `chat_id` for stable routing:
|
||||||
|
|
||||||
- `chat_id:123` (preferred)
|
- `chat_id:123` (preferred)
|
||||||
- `chat_guid:...`
|
- `chat_guid:...`
|
||||||
- `chat_identifier:...`
|
- `chat_identifier:...`
|
||||||
- direct handles: `imessage:+1555` / `sms:+1555` / `user@example.com`
|
- direct handles: `imessage:+1555` / `sms:+1555` / `user@example.com`
|
||||||
|
|
||||||
List chats:
|
List chats:
|
||||||
|
|
||||||
```
|
```
|
||||||
imsg chats --limit 20
|
imsg chats --limit 20
|
||||||
```
|
```
|
||||||
|
|
||||||
## Configuration reference (iMessage)
|
## Configuration reference (iMessage)
|
||||||
|
|
||||||
Full configuration: [Configuration](/gateway/configuration)
|
Full configuration: [Configuration](/gateway/configuration)
|
||||||
|
|
||||||
Provider options:
|
Provider options:
|
||||||
|
|
||||||
- `channels.imessage.enabled`: enable/disable channel startup.
|
- `channels.imessage.enabled`: enable/disable channel startup.
|
||||||
- `channels.imessage.cliPath`: path to `imsg`.
|
- `channels.imessage.cliPath`: path to `imsg`.
|
||||||
- `channels.imessage.dbPath`: Messages DB path.
|
- `channels.imessage.dbPath`: Messages DB path.
|
||||||
@@ -257,5 +290,6 @@ Provider options:
|
|||||||
- `channels.imessage.chunkMode`: `length` (default) or `newline` to split on blank lines (paragraph boundaries) before length chunking.
|
- `channels.imessage.chunkMode`: `length` (default) or `newline` to split on blank lines (paragraph boundaries) before length chunking.
|
||||||
|
|
||||||
Related global options:
|
Related global options:
|
||||||
|
|
||||||
- `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`).
|
- `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`).
|
||||||
- `messages.responsePrefix`.
|
- `messages.responsePrefix`.
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ summary: "Messaging platforms OpenClaw can connect to"
|
|||||||
read_when:
|
read_when:
|
||||||
- You want to choose a chat channel for OpenClaw
|
- You want to choose a chat channel for OpenClaw
|
||||||
- You need a quick overview of supported messaging platforms
|
- You need a quick overview of supported messaging platforms
|
||||||
|
title: "Chat Channels"
|
||||||
---
|
---
|
||||||
|
|
||||||
# Chat Channels
|
# Chat Channels
|
||||||
|
|
||||||
OpenClaw can talk to you on any chat app you already use. Each channel connects via the Gateway.
|
OpenClaw can talk to you on any chat app you already use. Each channel connects via the Gateway.
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ read_when:
|
|||||||
- You want to connect OpenClaw to LINE
|
- You want to connect OpenClaw to LINE
|
||||||
- You need LINE webhook + credential setup
|
- You need LINE webhook + credential setup
|
||||||
- You want LINE-specific message options
|
- You want LINE-specific message options
|
||||||
|
title: LINE
|
||||||
---
|
---
|
||||||
|
|
||||||
# LINE (plugin)
|
# LINE (plugin)
|
||||||
@@ -32,12 +33,12 @@ openclaw plugins install ./extensions/line
|
|||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
1) Create a LINE Developers account and open the Console:
|
1. Create a LINE Developers account and open the Console:
|
||||||
https://developers.line.biz/console/
|
https://developers.line.biz/console/
|
||||||
2) Create (or pick) a Provider and add a **Messaging API** channel.
|
2. Create (or pick) a Provider and add a **Messaging API** channel.
|
||||||
3) Copy the **Channel access token** and **Channel secret** from the channel settings.
|
3. Copy the **Channel access token** and **Channel secret** from the channel settings.
|
||||||
4) Enable **Use webhook** in the Messaging API settings.
|
4. Enable **Use webhook** in the Messaging API settings.
|
||||||
5) Set the webhook URL to your gateway endpoint (HTTPS required):
|
5. Set the webhook URL to your gateway endpoint (HTTPS required):
|
||||||
|
|
||||||
```
|
```
|
||||||
https://gateway-host/line/webhook
|
https://gateway-host/line/webhook
|
||||||
@@ -58,9 +59,9 @@ Minimal config:
|
|||||||
enabled: true,
|
enabled: true,
|
||||||
channelAccessToken: "LINE_CHANNEL_ACCESS_TOKEN",
|
channelAccessToken: "LINE_CHANNEL_ACCESS_TOKEN",
|
||||||
channelSecret: "LINE_CHANNEL_SECRET",
|
channelSecret: "LINE_CHANNEL_SECRET",
|
||||||
dmPolicy: "pairing"
|
dmPolicy: "pairing",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -76,9 +77,9 @@ Token/secret files:
|
|||||||
channels: {
|
channels: {
|
||||||
line: {
|
line: {
|
||||||
tokenFile: "/path/to/line-token.txt",
|
tokenFile: "/path/to/line-token.txt",
|
||||||
secretFile: "/path/to/line-secret.txt"
|
secretFile: "/path/to/line-secret.txt",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -92,11 +93,11 @@ Multiple accounts:
|
|||||||
marketing: {
|
marketing: {
|
||||||
channelAccessToken: "...",
|
channelAccessToken: "...",
|
||||||
channelSecret: "...",
|
channelSecret: "...",
|
||||||
webhookPath: "/line/marketing"
|
webhookPath: "/line/marketing",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -148,11 +149,13 @@ messages.
|
|||||||
title: "Office",
|
title: "Office",
|
||||||
address: "123 Main St",
|
address: "123 Main St",
|
||||||
latitude: 35.681236,
|
latitude: 35.681236,
|
||||||
longitude: 139.767125
|
longitude: 139.767125,
|
||||||
},
|
},
|
||||||
flexMessage: {
|
flexMessage: {
|
||||||
altText: "Status card",
|
altText: "Status card",
|
||||||
contents: { /* Flex payload */ }
|
contents: {
|
||||||
|
/* Flex payload */
|
||||||
|
},
|
||||||
},
|
},
|
||||||
templateMessage: {
|
templateMessage: {
|
||||||
type: "confirm",
|
type: "confirm",
|
||||||
@@ -160,10 +163,10 @@ messages.
|
|||||||
confirmLabel: "Yes",
|
confirmLabel: "Yes",
|
||||||
confirmData: "yes",
|
confirmData: "yes",
|
||||||
cancelLabel: "No",
|
cancelLabel: "No",
|
||||||
cancelData: "no"
|
cancelData: "no",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -3,20 +3,24 @@ summary: "Inbound channel location parsing (Telegram + WhatsApp) and context fie
|
|||||||
read_when:
|
read_when:
|
||||||
- Adding or modifying channel location parsing
|
- Adding or modifying channel location parsing
|
||||||
- Using location context fields in agent prompts or tools
|
- Using location context fields in agent prompts or tools
|
||||||
|
title: "Channel Location Parsing"
|
||||||
---
|
---
|
||||||
|
|
||||||
# Channel location parsing
|
# Channel location parsing
|
||||||
|
|
||||||
OpenClaw normalizes shared locations from chat channels into:
|
OpenClaw normalizes shared locations from chat channels into:
|
||||||
|
|
||||||
- human-readable text appended to the inbound body, and
|
- human-readable text appended to the inbound body, and
|
||||||
- structured fields in the auto-reply context payload.
|
- structured fields in the auto-reply context payload.
|
||||||
|
|
||||||
Currently supported:
|
Currently supported:
|
||||||
|
|
||||||
- **Telegram** (location pins + venues + live locations)
|
- **Telegram** (location pins + venues + live locations)
|
||||||
- **WhatsApp** (locationMessage + liveLocationMessage)
|
- **WhatsApp** (locationMessage + liveLocationMessage)
|
||||||
- **Matrix** (`m.location` with `geo_uri`)
|
- **Matrix** (`m.location` with `geo_uri`)
|
||||||
|
|
||||||
## Text formatting
|
## Text formatting
|
||||||
|
|
||||||
Locations are rendered as friendly lines without brackets:
|
Locations are rendered as friendly lines without brackets:
|
||||||
|
|
||||||
- Pin:
|
- Pin:
|
||||||
@@ -27,13 +31,16 @@ Locations are rendered as friendly lines without brackets:
|
|||||||
- `🛰 Live location: 48.858844, 2.294351 ±12m`
|
- `🛰 Live location: 48.858844, 2.294351 ±12m`
|
||||||
|
|
||||||
If the channel includes a caption/comment, it is appended on the next line:
|
If the channel includes a caption/comment, it is appended on the next line:
|
||||||
|
|
||||||
```
|
```
|
||||||
📍 48.858844, 2.294351 ±12m
|
📍 48.858844, 2.294351 ±12m
|
||||||
Meet here
|
Meet here
|
||||||
```
|
```
|
||||||
|
|
||||||
## Context fields
|
## Context fields
|
||||||
|
|
||||||
When a location is present, these fields are added to `ctx`:
|
When a location is present, these fields are added to `ctx`:
|
||||||
|
|
||||||
- `LocationLat` (number)
|
- `LocationLat` (number)
|
||||||
- `LocationLon` (number)
|
- `LocationLon` (number)
|
||||||
- `LocationAccuracy` (number, meters; optional)
|
- `LocationAccuracy` (number, meters; optional)
|
||||||
@@ -43,6 +50,7 @@ When a location is present, these fields are added to `ctx`:
|
|||||||
- `LocationIsLive` (boolean)
|
- `LocationIsLive` (boolean)
|
||||||
|
|
||||||
## Channel notes
|
## Channel notes
|
||||||
|
|
||||||
- **Telegram**: venues map to `LocationName/LocationAddress`; live locations use `live_period`.
|
- **Telegram**: venues map to `LocationName/LocationAddress`; live locations use `live_period`.
|
||||||
- **WhatsApp**: `locationMessage.comment` and `liveLocationMessage.caption` are appended as the caption line.
|
- **WhatsApp**: `locationMessage.comment` and `liveLocationMessage.caption` are appended as the caption line.
|
||||||
- **Matrix**: `geo_uri` is parsed as a pin location; altitude is ignored and `LocationIsLive` is always false.
|
- **Matrix**: `geo_uri` is parsed as a pin location; altitude is ignored and `LocationIsLive` is always false.
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
summary: "Matrix support status, capabilities, and configuration"
|
summary: "Matrix support status, capabilities, and configuration"
|
||||||
read_when:
|
read_when:
|
||||||
- Working on Matrix channel features
|
- Working on Matrix channel features
|
||||||
|
title: "Matrix"
|
||||||
---
|
---
|
||||||
|
|
||||||
# Matrix (plugin)
|
# Matrix (plugin)
|
||||||
|
|
||||||
Matrix is an open, decentralized messaging protocol. OpenClaw connects as a Matrix **user**
|
Matrix is an open, decentralized messaging protocol. OpenClaw connects as a Matrix **user**
|
||||||
@@ -36,13 +38,13 @@ Details: [Plugins](/plugin)
|
|||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
1) Install the Matrix plugin:
|
1. Install the Matrix plugin:
|
||||||
- From npm: `openclaw plugins install @openclaw/matrix`
|
- From npm: `openclaw plugins install @openclaw/matrix`
|
||||||
- From a local checkout: `openclaw plugins install ./extensions/matrix`
|
- From a local checkout: `openclaw plugins install ./extensions/matrix`
|
||||||
2) Create a Matrix account on a homeserver:
|
2. Create a Matrix account on a homeserver:
|
||||||
- Browse hosting options at [https://matrix.org/ecosystem/hosting/](https://matrix.org/ecosystem/hosting/)
|
- Browse hosting options at [https://matrix.org/ecosystem/hosting/](https://matrix.org/ecosystem/hosting/)
|
||||||
- Or host it yourself.
|
- Or host it yourself.
|
||||||
3) Get an access token for the bot account:
|
3. Get an access token for the bot account:
|
||||||
- Use the Matrix login API with `curl` at your home server:
|
- Use the Matrix login API with `curl` at your home server:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -63,14 +65,15 @@ Details: [Plugins](/plugin)
|
|||||||
- Or set `channels.matrix.userId` + `channels.matrix.password`: OpenClaw calls the same
|
- Or set `channels.matrix.userId` + `channels.matrix.password`: OpenClaw calls the same
|
||||||
login endpoint, stores the access token in `~/.openclaw/credentials/matrix/credentials.json`,
|
login endpoint, stores the access token in `~/.openclaw/credentials/matrix/credentials.json`,
|
||||||
and reuses it on next start.
|
and reuses it on next start.
|
||||||
4) Configure credentials:
|
|
||||||
|
4. Configure credentials:
|
||||||
- Env: `MATRIX_HOMESERVER`, `MATRIX_ACCESS_TOKEN` (or `MATRIX_USER_ID` + `MATRIX_PASSWORD`)
|
- Env: `MATRIX_HOMESERVER`, `MATRIX_ACCESS_TOKEN` (or `MATRIX_USER_ID` + `MATRIX_PASSWORD`)
|
||||||
- Or config: `channels.matrix.*`
|
- Or config: `channels.matrix.*`
|
||||||
- If both are set, config takes precedence.
|
- If both are set, config takes precedence.
|
||||||
- With access token: user ID is fetched automatically via `/whoami`.
|
- With access token: user ID is fetched automatically via `/whoami`.
|
||||||
- When set, `channels.matrix.userId` should be the full Matrix ID (example: `@bot:example.org`).
|
- When set, `channels.matrix.userId` should be the full Matrix ID (example: `@bot:example.org`).
|
||||||
5) Restart the gateway (or finish onboarding).
|
5. Restart the gateway (or finish onboarding).
|
||||||
6) Start a DM with the bot or invite it to a room from any Matrix client
|
6. Start a DM with the bot or invite it to a room from any Matrix client
|
||||||
(Element, Beeper, etc.; see https://matrix.org/ecosystem/clients/). Beeper requires E2EE,
|
(Element, Beeper, etc.; see https://matrix.org/ecosystem/clients/). Beeper requires E2EE,
|
||||||
so set `channels.matrix.encryption: true` and verify the device.
|
so set `channels.matrix.encryption: true` and verify the device.
|
||||||
|
|
||||||
@@ -83,9 +86,9 @@ Minimal config (access token, user ID auto-fetched):
|
|||||||
enabled: true,
|
enabled: true,
|
||||||
homeserver: "https://matrix.example.org",
|
homeserver: "https://matrix.example.org",
|
||||||
accessToken: "syt_***",
|
accessToken: "syt_***",
|
||||||
dm: { policy: "pairing" }
|
dm: { policy: "pairing" },
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -99,9 +102,9 @@ E2EE config (end to end encryption enabled):
|
|||||||
homeserver: "https://matrix.example.org",
|
homeserver: "https://matrix.example.org",
|
||||||
accessToken: "syt_***",
|
accessToken: "syt_***",
|
||||||
encryption: true,
|
encryption: true,
|
||||||
dm: { policy: "pairing" }
|
dm: { policy: "pairing" },
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -145,12 +148,12 @@ Once verified, the bot can decrypt messages in encrypted rooms.
|
|||||||
- `openclaw pairing list matrix`
|
- `openclaw pairing list matrix`
|
||||||
- `openclaw pairing approve matrix <CODE>`
|
- `openclaw pairing approve matrix <CODE>`
|
||||||
- Public DMs: `channels.matrix.dm.policy="open"` plus `channels.matrix.dm.allowFrom=["*"]`.
|
- Public DMs: `channels.matrix.dm.policy="open"` plus `channels.matrix.dm.allowFrom=["*"]`.
|
||||||
- `channels.matrix.dm.allowFrom` accepts user IDs or display names. The wizard resolves display names to user IDs when directory search is available.
|
- `channels.matrix.dm.allowFrom` accepts full Matrix user IDs (example: `@user:server`). The wizard resolves display names to user IDs when directory search finds a single exact match.
|
||||||
|
|
||||||
## Rooms (groups)
|
## Rooms (groups)
|
||||||
|
|
||||||
- Default: `channels.matrix.groupPolicy = "allowlist"` (mention-gated). Use `channels.defaults.groupPolicy` to override the default when unset.
|
- Default: `channels.matrix.groupPolicy = "allowlist"` (mention-gated). Use `channels.defaults.groupPolicy` to override the default when unset.
|
||||||
- Allowlist rooms with `channels.matrix.groups` (room IDs, aliases, or names):
|
- Allowlist rooms with `channels.matrix.groups` (room IDs or aliases; names are resolved to IDs when directory search finds a single exact match):
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
@@ -159,20 +162,20 @@ Once verified, the bot can decrypt messages in encrypted rooms.
|
|||||||
groupPolicy: "allowlist",
|
groupPolicy: "allowlist",
|
||||||
groups: {
|
groups: {
|
||||||
"!roomId:example.org": { allow: true },
|
"!roomId:example.org": { allow: true },
|
||||||
"#alias:example.org": { allow: true }
|
"#alias:example.org": { allow: true },
|
||||||
},
|
},
|
||||||
groupAllowFrom: ["@owner:example.org"]
|
groupAllowFrom: ["@owner:example.org"],
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- `requireMention: false` enables auto-reply in that room.
|
- `requireMention: false` enables auto-reply in that room.
|
||||||
- `groups."*"` can set defaults for mention gating across rooms.
|
- `groups."*"` can set defaults for mention gating across rooms.
|
||||||
- `groupAllowFrom` restricts which senders can trigger the bot in rooms (optional).
|
- `groupAllowFrom` restricts which senders can trigger the bot in rooms (full Matrix user IDs).
|
||||||
- Per-room `users` allowlists can further restrict senders inside a specific room.
|
- Per-room `users` allowlists can further restrict senders inside a specific room (use full Matrix user IDs).
|
||||||
- The configure wizard prompts for room allowlists (room IDs, aliases, or names) and resolves names when possible.
|
- The configure wizard prompts for room allowlists (room IDs, aliases, or names) and resolves names only on an exact, unique match.
|
||||||
- On startup, OpenClaw resolves room/user names in allowlists to IDs and logs the mapping; unresolved entries are kept as typed.
|
- On startup, OpenClaw resolves room/user names in allowlists to IDs and logs the mapping; unresolved entries are ignored for allowlist matching.
|
||||||
- Invites are auto-joined by default; control with `channels.matrix.autoJoin` and `channels.matrix.autoJoinAllowlist`.
|
- Invites are auto-joined by default; control with `channels.matrix.autoJoin` and `channels.matrix.autoJoinAllowlist`.
|
||||||
- To allow **no rooms**, set `channels.matrix.groupPolicy: "disabled"` (or keep an empty allowlist).
|
- To allow **no rooms**, set `channels.matrix.groupPolicy: "disabled"` (or keep an empty allowlist).
|
||||||
- Legacy key: `channels.matrix.rooms` (same shape as `groups`).
|
- Legacy key: `channels.matrix.rooms` (same shape as `groups`).
|
||||||
@@ -187,17 +190,17 @@ Once verified, the bot can decrypt messages in encrypted rooms.
|
|||||||
|
|
||||||
## Capabilities
|
## Capabilities
|
||||||
|
|
||||||
| Feature | Status |
|
| Feature | Status |
|
||||||
|---------|--------|
|
| --------------- | ------------------------------------------------------------------------------------- |
|
||||||
| Direct messages | ✅ Supported |
|
| Direct messages | ✅ Supported |
|
||||||
| Rooms | ✅ Supported |
|
| Rooms | ✅ Supported |
|
||||||
| Threads | ✅ Supported |
|
| Threads | ✅ Supported |
|
||||||
| Media | ✅ Supported |
|
| Media | ✅ Supported |
|
||||||
| E2EE | ✅ Supported (crypto module required) |
|
| E2EE | ✅ Supported (crypto module required) |
|
||||||
| Reactions | ✅ Supported (send/read via tools) |
|
| Reactions | ✅ Supported (send/read via tools) |
|
||||||
| Polls | ✅ Send supported; inbound poll starts are converted to text (responses/ends ignored) |
|
| Polls | ✅ Send supported; inbound poll starts are converted to text (responses/ends ignored) |
|
||||||
| Location | ✅ Supported (geo URI; altitude ignored) |
|
| Location | ✅ Supported (geo URI; altitude ignored) |
|
||||||
| Native commands | ✅ Supported |
|
| Native commands | ✅ Supported |
|
||||||
|
|
||||||
## Configuration reference (Matrix)
|
## Configuration reference (Matrix)
|
||||||
|
|
||||||
@@ -217,9 +220,9 @@ Provider options:
|
|||||||
- `channels.matrix.textChunkLimit`: outbound text chunk size (chars).
|
- `channels.matrix.textChunkLimit`: outbound text chunk size (chars).
|
||||||
- `channels.matrix.chunkMode`: `length` (default) or `newline` to split on blank lines (paragraph boundaries) before length chunking.
|
- `channels.matrix.chunkMode`: `length` (default) or `newline` to split on blank lines (paragraph boundaries) before length chunking.
|
||||||
- `channels.matrix.dm.policy`: `pairing | allowlist | open | disabled` (default: pairing).
|
- `channels.matrix.dm.policy`: `pairing | allowlist | open | disabled` (default: pairing).
|
||||||
- `channels.matrix.dm.allowFrom`: DM allowlist (user IDs or display names). `open` requires `"*"`. The wizard resolves names to IDs when possible.
|
- `channels.matrix.dm.allowFrom`: DM allowlist (full Matrix user IDs). `open` requires `"*"`. The wizard resolves names to IDs when possible.
|
||||||
- `channels.matrix.groupPolicy`: `allowlist | open | disabled` (default: allowlist).
|
- `channels.matrix.groupPolicy`: `allowlist | open | disabled` (default: allowlist).
|
||||||
- `channels.matrix.groupAllowFrom`: allowlisted senders for group messages.
|
- `channels.matrix.groupAllowFrom`: allowlisted senders for group messages (full Matrix user IDs).
|
||||||
- `channels.matrix.allowlistOnly`: force allowlist rules for DMs + rooms.
|
- `channels.matrix.allowlistOnly`: force allowlist rules for DMs + rooms.
|
||||||
- `channels.matrix.groups`: group allowlist + per-room settings map.
|
- `channels.matrix.groups`: group allowlist + per-room settings map.
|
||||||
- `channels.matrix.rooms`: legacy group allowlist/config.
|
- `channels.matrix.rooms`: legacy group allowlist/config.
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ summary: "Mattermost bot setup and OpenClaw config"
|
|||||||
read_when:
|
read_when:
|
||||||
- Setting up Mattermost
|
- Setting up Mattermost
|
||||||
- Debugging Mattermost routing
|
- Debugging Mattermost routing
|
||||||
|
title: "Mattermost"
|
||||||
---
|
---
|
||||||
|
|
||||||
# Mattermost (plugin)
|
# Mattermost (plugin)
|
||||||
@@ -12,14 +13,17 @@ Mattermost is a self-hostable team messaging platform; see the official site at
|
|||||||
[mattermost.com](https://mattermost.com) for product details and downloads.
|
[mattermost.com](https://mattermost.com) for product details and downloads.
|
||||||
|
|
||||||
## Plugin required
|
## Plugin required
|
||||||
|
|
||||||
Mattermost ships as a plugin and is not bundled with the core install.
|
Mattermost ships as a plugin and is not bundled with the core install.
|
||||||
|
|
||||||
Install via CLI (npm registry):
|
Install via CLI (npm registry):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
openclaw plugins install @openclaw/mattermost
|
openclaw plugins install @openclaw/mattermost
|
||||||
```
|
```
|
||||||
|
|
||||||
Local checkout (when running from a git repo):
|
Local checkout (when running from a git repo):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
openclaw plugins install ./extensions/mattermost
|
openclaw plugins install ./extensions/mattermost
|
||||||
```
|
```
|
||||||
@@ -30,12 +34,14 @@ OpenClaw will offer the local install path automatically.
|
|||||||
Details: [Plugins](/plugin)
|
Details: [Plugins](/plugin)
|
||||||
|
|
||||||
## Quick setup
|
## Quick setup
|
||||||
1) Install the Mattermost plugin.
|
|
||||||
2) Create a Mattermost bot account and copy the **bot token**.
|
1. Install the Mattermost plugin.
|
||||||
3) Copy the Mattermost **base URL** (e.g., `https://chat.example.com`).
|
2. Create a Mattermost bot account and copy the **bot token**.
|
||||||
4) Configure OpenClaw and start the gateway.
|
3. Copy the Mattermost **base URL** (e.g., `https://chat.example.com`).
|
||||||
|
4. Configure OpenClaw and start the gateway.
|
||||||
|
|
||||||
Minimal config:
|
Minimal config:
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
channels: {
|
channels: {
|
||||||
@@ -43,13 +49,14 @@ Minimal config:
|
|||||||
enabled: true,
|
enabled: true,
|
||||||
botToken: "mm-token",
|
botToken: "mm-token",
|
||||||
baseUrl: "https://chat.example.com",
|
baseUrl: "https://chat.example.com",
|
||||||
dmPolicy: "pairing"
|
dmPolicy: "pairing",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Environment variables (default account)
|
## Environment variables (default account)
|
||||||
|
|
||||||
Set these on the gateway host if you prefer env vars:
|
Set these on the gateway host if you prefer env vars:
|
||||||
|
|
||||||
- `MATTERMOST_BOT_TOKEN=...`
|
- `MATTERMOST_BOT_TOKEN=...`
|
||||||
@@ -58,6 +65,7 @@ Set these on the gateway host if you prefer env vars:
|
|||||||
Env vars apply only to the **default** account (`default`). Other accounts must use config values.
|
Env vars apply only to the **default** account (`default`). Other accounts must use config values.
|
||||||
|
|
||||||
## Chat modes
|
## Chat modes
|
||||||
|
|
||||||
Mattermost responds to DMs automatically. Channel behavior is controlled by `chatmode`:
|
Mattermost responds to DMs automatically. Channel behavior is controlled by `chatmode`:
|
||||||
|
|
||||||
- `oncall` (default): respond only when @mentioned in channels.
|
- `oncall` (default): respond only when @mentioned in channels.
|
||||||
@@ -65,22 +73,25 @@ Mattermost responds to DMs automatically. Channel behavior is controlled by `cha
|
|||||||
- `onchar`: respond when a message starts with a trigger prefix.
|
- `onchar`: respond when a message starts with a trigger prefix.
|
||||||
|
|
||||||
Config example:
|
Config example:
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
channels: {
|
channels: {
|
||||||
mattermost: {
|
mattermost: {
|
||||||
chatmode: "onchar",
|
chatmode: "onchar",
|
||||||
oncharPrefixes: [">", "!"]
|
oncharPrefixes: [">", "!"],
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
- `onchar` still responds to explicit @mentions.
|
- `onchar` still responds to explicit @mentions.
|
||||||
- `channels.mattermost.requireMention` is honored for legacy configs but `chatmode` is preferred.
|
- `channels.mattermost.requireMention` is honored for legacy configs but `chatmode` is preferred.
|
||||||
|
|
||||||
## Access control (DMs)
|
## Access control (DMs)
|
||||||
|
|
||||||
- Default: `channels.mattermost.dmPolicy = "pairing"` (unknown senders get a pairing code).
|
- Default: `channels.mattermost.dmPolicy = "pairing"` (unknown senders get a pairing code).
|
||||||
- Approve via:
|
- Approve via:
|
||||||
- `openclaw pairing list mattermost`
|
- `openclaw pairing list mattermost`
|
||||||
@@ -88,11 +99,13 @@ Notes:
|
|||||||
- Public DMs: `channels.mattermost.dmPolicy="open"` plus `channels.mattermost.allowFrom=["*"]`.
|
- Public DMs: `channels.mattermost.dmPolicy="open"` plus `channels.mattermost.allowFrom=["*"]`.
|
||||||
|
|
||||||
## Channels (groups)
|
## Channels (groups)
|
||||||
|
|
||||||
- Default: `channels.mattermost.groupPolicy = "allowlist"` (mention-gated).
|
- Default: `channels.mattermost.groupPolicy = "allowlist"` (mention-gated).
|
||||||
- Allowlist senders with `channels.mattermost.groupAllowFrom` (user IDs or `@username`).
|
- Allowlist senders with `channels.mattermost.groupAllowFrom` (user IDs or `@username`).
|
||||||
- Open channels: `channels.mattermost.groupPolicy="open"` (mention-gated).
|
- Open channels: `channels.mattermost.groupPolicy="open"` (mention-gated).
|
||||||
|
|
||||||
## Targets for outbound delivery
|
## Targets for outbound delivery
|
||||||
|
|
||||||
Use these target formats with `openclaw message send` or cron/webhooks:
|
Use these target formats with `openclaw message send` or cron/webhooks:
|
||||||
|
|
||||||
- `channel:<id>` for a channel
|
- `channel:<id>` for a channel
|
||||||
@@ -102,6 +115,7 @@ Use these target formats with `openclaw message send` or cron/webhooks:
|
|||||||
Bare IDs are treated as channels.
|
Bare IDs are treated as channels.
|
||||||
|
|
||||||
## Multi-account
|
## Multi-account
|
||||||
|
|
||||||
Mattermost supports multiple accounts under `channels.mattermost.accounts`:
|
Mattermost supports multiple accounts under `channels.mattermost.accounts`:
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
@@ -110,14 +124,15 @@ Mattermost supports multiple accounts under `channels.mattermost.accounts`:
|
|||||||
mattermost: {
|
mattermost: {
|
||||||
accounts: {
|
accounts: {
|
||||||
default: { name: "Primary", botToken: "mm-token", baseUrl: "https://chat.example.com" },
|
default: { name: "Primary", botToken: "mm-token", baseUrl: "https://chat.example.com" },
|
||||||
alerts: { name: "Alerts", botToken: "mm-token-2", baseUrl: "https://alerts.example.com" }
|
alerts: { name: "Alerts", botToken: "mm-token-2", baseUrl: "https://alerts.example.com" },
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
- No replies in channels: ensure the bot is in the channel and mention it (oncall), use a trigger prefix (onchar), or set `chatmode: "onmessage"`.
|
- No replies in channels: ensure the bot is in the channel and mention it (oncall), use a trigger prefix (onchar), or set `chatmode: "onmessage"`.
|
||||||
- Auth errors: check the bot token, base URL, and whether the account is enabled.
|
- Auth errors: check the bot token, base URL, and whether the account is enabled.
|
||||||
- Multi-account issues: env vars only apply to the `default` account.
|
- Multi-account issues: env vars only apply to the `default` account.
|
||||||
|
|||||||
@@ -2,17 +2,19 @@
|
|||||||
summary: "Microsoft Teams bot support status, capabilities, and configuration"
|
summary: "Microsoft Teams bot support status, capabilities, and configuration"
|
||||||
read_when:
|
read_when:
|
||||||
- Working on MS Teams channel features
|
- Working on MS Teams channel features
|
||||||
|
title: "Microsoft Teams"
|
||||||
---
|
---
|
||||||
|
|
||||||
# Microsoft Teams (plugin)
|
# Microsoft Teams (plugin)
|
||||||
|
|
||||||
> "Abandon all hope, ye who enter here."
|
> "Abandon all hope, ye who enter here."
|
||||||
|
|
||||||
|
|
||||||
Updated: 2026-01-21
|
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.
|
||||||
|
|
||||||
## Plugin required
|
## Plugin required
|
||||||
|
|
||||||
Microsoft Teams ships as a plugin and is not bundled with the core install.
|
Microsoft Teams ships as a plugin and is not bundled with the core install.
|
||||||
|
|
||||||
**Breaking change (2026.1.15):** MS Teams moved out of core. If you use it, you must install the plugin.
|
**Breaking change (2026.1.15):** MS Teams moved out of core. If you use it, you must install the plugin.
|
||||||
@@ -20,11 +22,13 @@ Microsoft Teams ships as a plugin and is not bundled with the core install.
|
|||||||
Explainable: keeps core installs lighter and lets MS Teams dependencies update independently.
|
Explainable: keeps core installs lighter and lets MS Teams dependencies update independently.
|
||||||
|
|
||||||
Install via CLI (npm registry):
|
Install via CLI (npm registry):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
openclaw plugins install @openclaw/msteams
|
openclaw plugins install @openclaw/msteams
|
||||||
```
|
```
|
||||||
|
|
||||||
Local checkout (when running from a git repo):
|
Local checkout (when running from a git repo):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
openclaw plugins install ./extensions/msteams
|
openclaw plugins install ./extensions/msteams
|
||||||
```
|
```
|
||||||
@@ -35,13 +39,15 @@ OpenClaw will offer the local install path automatically.
|
|||||||
Details: [Plugins](/plugin)
|
Details: [Plugins](/plugin)
|
||||||
|
|
||||||
## Quick setup (beginner)
|
## Quick setup (beginner)
|
||||||
1) Install the Microsoft Teams plugin.
|
|
||||||
2) Create an **Azure Bot** (App ID + client secret + tenant ID).
|
1. Install the Microsoft Teams plugin.
|
||||||
3) Configure OpenClaw with those credentials.
|
2. Create an **Azure Bot** (App ID + client secret + tenant ID).
|
||||||
4) Expose `/api/messages` (port 3978 by default) via a public URL or tunnel.
|
3. Configure OpenClaw with those credentials.
|
||||||
5) Install the Teams app package and start the gateway.
|
4. Expose `/api/messages` (port 3978 by default) via a public URL or tunnel.
|
||||||
|
5. Install the Teams app package and start the gateway.
|
||||||
|
|
||||||
Minimal config:
|
Minimal config:
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
channels: {
|
channels: {
|
||||||
@@ -50,53 +56,61 @@ Minimal config:
|
|||||||
appId: "<APP_ID>",
|
appId: "<APP_ID>",
|
||||||
appPassword: "<APP_PASSWORD>",
|
appPassword: "<APP_PASSWORD>",
|
||||||
tenantId: "<TENANT_ID>",
|
tenantId: "<TENANT_ID>",
|
||||||
webhook: { port: 3978, path: "/api/messages" }
|
webhook: { port: 3978, path: "/api/messages" },
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: group chats are blocked by default (`channels.msteams.groupPolicy: "allowlist"`). To allow group replies, set `channels.msteams.groupAllowFrom` (or use `groupPolicy: "open"` to allow any member, mention-gated).
|
Note: group chats are blocked by default (`channels.msteams.groupPolicy: "allowlist"`). To allow group replies, set `channels.msteams.groupAllowFrom` (or use `groupPolicy: "open"` to allow any member, mention-gated).
|
||||||
|
|
||||||
## Goals
|
## Goals
|
||||||
|
|
||||||
- Talk to OpenClaw via Teams DMs, group chats, or channels.
|
- Talk to OpenClaw via Teams DMs, group chats, or channels.
|
||||||
- Keep routing deterministic: replies always go back to the channel they arrived on.
|
- Keep routing deterministic: replies always go back to the channel they arrived on.
|
||||||
- Default to safe channel behavior (mentions required unless configured otherwise).
|
- Default to safe channel behavior (mentions required unless configured otherwise).
|
||||||
|
|
||||||
## Config writes
|
## Config writes
|
||||||
|
|
||||||
By default, Microsoft Teams is allowed to write config updates triggered by `/config set|unset` (requires `commands.config: true`).
|
By default, Microsoft Teams is allowed to write config updates triggered by `/config set|unset` (requires `commands.config: true`).
|
||||||
|
|
||||||
Disable with:
|
Disable with:
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
channels: { msteams: { configWrites: false } }
|
channels: { msteams: { configWrites: false } },
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Access control (DMs + groups)
|
## Access control (DMs + groups)
|
||||||
|
|
||||||
**DM access**
|
**DM access**
|
||||||
|
|
||||||
- Default: `channels.msteams.dmPolicy = "pairing"`. Unknown senders are ignored until approved.
|
- Default: `channels.msteams.dmPolicy = "pairing"`. Unknown senders are ignored until approved.
|
||||||
- `channels.msteams.allowFrom` accepts AAD object IDs, UPNs, or display names. The wizard resolves names to IDs via Microsoft Graph when credentials allow.
|
- `channels.msteams.allowFrom` accepts AAD object IDs, UPNs, or display names. The wizard resolves names to IDs via Microsoft Graph when credentials allow.
|
||||||
|
|
||||||
**Group access**
|
**Group access**
|
||||||
|
|
||||||
- Default: `channels.msteams.groupPolicy = "allowlist"` (blocked unless you add `groupAllowFrom`). Use `channels.defaults.groupPolicy` to override the default when unset.
|
- Default: `channels.msteams.groupPolicy = "allowlist"` (blocked unless you add `groupAllowFrom`). Use `channels.defaults.groupPolicy` to override the default when unset.
|
||||||
- `channels.msteams.groupAllowFrom` controls which senders can trigger in group chats/channels (falls back to `channels.msteams.allowFrom`).
|
- `channels.msteams.groupAllowFrom` controls which senders can trigger in group chats/channels (falls back to `channels.msteams.allowFrom`).
|
||||||
- Set `groupPolicy: "open"` to allow any member (still mention‑gated by default).
|
- Set `groupPolicy: "open"` to allow any member (still mention‑gated by default).
|
||||||
- To allow **no channels**, set `channels.msteams.groupPolicy: "disabled"`.
|
- To allow **no channels**, set `channels.msteams.groupPolicy: "disabled"`.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
channels: {
|
channels: {
|
||||||
msteams: {
|
msteams: {
|
||||||
groupPolicy: "allowlist",
|
groupPolicy: "allowlist",
|
||||||
groupAllowFrom: ["user@org.com"]
|
groupAllowFrom: ["user@org.com"],
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Teams + channel allowlist**
|
**Teams + channel allowlist**
|
||||||
|
|
||||||
- Scope group/channel replies by listing teams and channels under `channels.msteams.teams`.
|
- Scope group/channel replies by listing teams and channels under `channels.msteams.teams`.
|
||||||
- Keys can be team IDs or names; channel keys can be conversation IDs or names.
|
- Keys can be team IDs or names; channel keys can be conversation IDs or names.
|
||||||
- When `groupPolicy="allowlist"` and a teams allowlist is present, only listed teams/channels are accepted (mention‑gated).
|
- When `groupPolicy="allowlist"` and a teams allowlist is present, only listed teams/channels are accepted (mention‑gated).
|
||||||
@@ -105,6 +119,7 @@ Example:
|
|||||||
and logs the mapping; unresolved entries are kept as typed.
|
and logs the mapping; unresolved entries are kept as typed.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
channels: {
|
channels: {
|
||||||
@@ -113,16 +128,17 @@ Example:
|
|||||||
teams: {
|
teams: {
|
||||||
"My Team": {
|
"My Team": {
|
||||||
channels: {
|
channels: {
|
||||||
"General": { requireMention: true }
|
General: { requireMention: true },
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## How it works
|
## How it works
|
||||||
|
|
||||||
1. Install the Microsoft Teams plugin.
|
1. Install the Microsoft Teams plugin.
|
||||||
2. Create an **Azure Bot** (App ID + secret + tenant ID).
|
2. Create an **Azure Bot** (App ID + secret + tenant ID).
|
||||||
3. Build a **Teams app package** that references the bot and includes the RSC permissions below.
|
3. Build a **Teams app package** that references the bot and includes the RSC permissions below.
|
||||||
@@ -139,14 +155,14 @@ Before configuring OpenClaw, you need to create an Azure Bot resource.
|
|||||||
1. Go to [Create Azure Bot](https://portal.azure.com/#create/Microsoft.AzureBot)
|
1. Go to [Create Azure Bot](https://portal.azure.com/#create/Microsoft.AzureBot)
|
||||||
2. Fill in the **Basics** tab:
|
2. Fill in the **Basics** tab:
|
||||||
|
|
||||||
| Field | Value |
|
| Field | Value |
|
||||||
|-------|-------|
|
| ------------------ | -------------------------------------------------------- |
|
||||||
| **Bot handle** | Your bot name, e.g., `openclaw-msteams` (must be unique) |
|
| **Bot handle** | Your bot name, e.g., `openclaw-msteams` (must be unique) |
|
||||||
| **Subscription** | Select your Azure subscription |
|
| **Subscription** | Select your Azure subscription |
|
||||||
| **Resource group** | Create new or use existing |
|
| **Resource group** | Create new or use existing |
|
||||||
| **Pricing tier** | **Free** for dev/testing |
|
| **Pricing tier** | **Free** for dev/testing |
|
||||||
| **Type of App** | **Single Tenant** (recommended - see note below) |
|
| **Type of App** | **Single Tenant** (recommended - see note below) |
|
||||||
| **Creation type** | **Create new Microsoft App ID** |
|
| **Creation type** | **Create new Microsoft App ID** |
|
||||||
|
|
||||||
> **Deprecation notice:** Creation of new multi-tenant bots was deprecated after 2025-07-31. Use **Single Tenant** for new bots.
|
> **Deprecation notice:** Creation of new multi-tenant bots was deprecated after 2025-07-31. Use **Single Tenant** for new bots.
|
||||||
|
|
||||||
@@ -178,6 +194,7 @@ Before configuring OpenClaw, you need to create an Azure Bot resource.
|
|||||||
Teams can't reach `localhost`. Use a tunnel for local development:
|
Teams can't reach `localhost`. Use a tunnel for local development:
|
||||||
|
|
||||||
**Option A: ngrok**
|
**Option A: ngrok**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ngrok http 3978
|
ngrok http 3978
|
||||||
# Copy the https URL, e.g., https://abc123.ngrok.io
|
# Copy the https URL, e.g., https://abc123.ngrok.io
|
||||||
@@ -185,6 +202,7 @@ ngrok http 3978
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Option B: Tailscale Funnel**
|
**Option B: Tailscale Funnel**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
tailscale funnel 3978
|
tailscale funnel 3978
|
||||||
# Use your Tailscale funnel URL as the messaging endpoint
|
# Use your Tailscale funnel URL as the messaging endpoint
|
||||||
@@ -207,16 +225,19 @@ This is often easier than hand-editing JSON manifests.
|
|||||||
## Testing the Bot
|
## Testing the Bot
|
||||||
|
|
||||||
**Option A: Azure Web Chat (verify webhook first)**
|
**Option A: Azure Web Chat (verify webhook first)**
|
||||||
|
|
||||||
1. In Azure Portal → your Azure Bot resource → **Test in Web Chat**
|
1. In Azure Portal → your Azure Bot resource → **Test in Web Chat**
|
||||||
2. Send a message - you should see a response
|
2. Send a message - you should see a response
|
||||||
3. This confirms your webhook endpoint works before Teams setup
|
3. This confirms your webhook endpoint works before Teams setup
|
||||||
|
|
||||||
**Option B: Teams (after app installation)**
|
**Option B: Teams (after app installation)**
|
||||||
|
|
||||||
1. Install the Teams app (sideload or org catalog)
|
1. Install the Teams app (sideload or org catalog)
|
||||||
2. Find the bot in Teams and send a DM
|
2. Find the bot in Teams and send a DM
|
||||||
3. Check gateway logs for incoming activity
|
3. Check gateway logs for incoming activity
|
||||||
|
|
||||||
## Setup (minimal text-only)
|
## Setup (minimal text-only)
|
||||||
|
|
||||||
1. **Install the Microsoft Teams plugin**
|
1. **Install the Microsoft Teams plugin**
|
||||||
- From npm: `openclaw plugins install @openclaw/msteams`
|
- From npm: `openclaw plugins install @openclaw/msteams`
|
||||||
- From a local checkout: `openclaw plugins install ./extensions/msteams`
|
- From a local checkout: `openclaw plugins install ./extensions/msteams`
|
||||||
@@ -236,6 +257,7 @@ This is often easier than hand-editing JSON manifests.
|
|||||||
- Zip all three files together: `manifest.json`, `outline.png`, `color.png`.
|
- Zip all three files together: `manifest.json`, `outline.png`, `color.png`.
|
||||||
|
|
||||||
4. **Configure OpenClaw**
|
4. **Configure OpenClaw**
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"msteams": {
|
"msteams": {
|
||||||
@@ -261,14 +283,17 @@ This is often easier than hand-editing JSON manifests.
|
|||||||
- The Teams channel starts automatically when the plugin is installed and `msteams` config exists with credentials.
|
- The Teams channel starts automatically when the plugin is installed and `msteams` config exists with credentials.
|
||||||
|
|
||||||
## History context
|
## History context
|
||||||
|
|
||||||
- `channels.msteams.historyLimit` controls how many recent channel/group messages are wrapped into the prompt.
|
- `channels.msteams.historyLimit` controls how many recent channel/group messages are wrapped into the prompt.
|
||||||
- Falls back to `messages.groupChat.historyLimit`. Set `0` to disable (default 50).
|
- Falls back to `messages.groupChat.historyLimit`. Set `0` to disable (default 50).
|
||||||
- DM history can be limited with `channels.msteams.dmHistoryLimit` (user turns). Per-user overrides: `channels.msteams.dms["<user_id>"].historyLimit`.
|
- DM history can be limited with `channels.msteams.dmHistoryLimit` (user turns). Per-user overrides: `channels.msteams.dms["<user_id>"].historyLimit`.
|
||||||
|
|
||||||
## Current Teams RSC Permissions (Manifest)
|
## Current Teams RSC Permissions (Manifest)
|
||||||
|
|
||||||
These are the **existing resourceSpecific permissions** in our Teams app manifest. They only apply inside the team/chat where the app is installed.
|
These are the **existing resourceSpecific permissions** in our Teams app manifest. They only apply inside the team/chat where the app is installed.
|
||||||
|
|
||||||
**For channels (team scope):**
|
**For channels (team scope):**
|
||||||
|
|
||||||
- `ChannelMessage.Read.Group` (Application) - receive all channel messages without @mention
|
- `ChannelMessage.Read.Group` (Application) - receive all channel messages without @mention
|
||||||
- `ChannelMessage.Send.Group` (Application)
|
- `ChannelMessage.Send.Group` (Application)
|
||||||
- `Member.Read.Group` (Application)
|
- `Member.Read.Group` (Application)
|
||||||
@@ -278,9 +303,11 @@ These are the **existing resourceSpecific permissions** in our Teams app manifes
|
|||||||
- `TeamSettings.Read.Group` (Application)
|
- `TeamSettings.Read.Group` (Application)
|
||||||
|
|
||||||
**For group chats:**
|
**For group chats:**
|
||||||
|
|
||||||
- `ChatMessage.Read.Chat` (Application) - receive all group chat messages without @mention
|
- `ChatMessage.Read.Chat` (Application) - receive all group chat messages without @mention
|
||||||
|
|
||||||
## Example Teams Manifest (redacted)
|
## Example Teams Manifest (redacted)
|
||||||
|
|
||||||
Minimal, valid example with the required fields. Replace IDs and URLs.
|
Minimal, valid example with the required fields. Replace IDs and URLs.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
@@ -330,6 +357,7 @@ Minimal, valid example with the required fields. Replace IDs and URLs.
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Manifest caveats (must-have fields)
|
### Manifest caveats (must-have fields)
|
||||||
|
|
||||||
- `bots[].botId` **must** match the Azure Bot App ID.
|
- `bots[].botId` **must** match the Azure Bot App ID.
|
||||||
- `webApplicationInfo.id` **must** match the Azure Bot App ID.
|
- `webApplicationInfo.id` **must** match the Azure Bot App ID.
|
||||||
- `bots[].scopes` must include the surfaces you plan to use (`personal`, `team`, `groupChat`).
|
- `bots[].scopes` must include the surfaces you plan to use (`personal`, `team`, `groupChat`).
|
||||||
@@ -352,34 +380,40 @@ To update an already-installed Teams app (e.g., to add RSC permissions):
|
|||||||
## Capabilities: RSC only vs Graph
|
## Capabilities: RSC only vs Graph
|
||||||
|
|
||||||
### With **Teams RSC only** (app installed, no Graph API permissions)
|
### With **Teams RSC only** (app installed, no Graph API permissions)
|
||||||
|
|
||||||
Works:
|
Works:
|
||||||
|
|
||||||
- Read channel message **text** content.
|
- Read channel message **text** content.
|
||||||
- Send channel message **text** content.
|
- Send channel message **text** content.
|
||||||
- Receive **personal (DM)** file attachments.
|
- Receive **personal (DM)** file attachments.
|
||||||
|
|
||||||
Does NOT work:
|
Does NOT work:
|
||||||
|
|
||||||
- Channel/group **image or file contents** (payload only includes HTML stub).
|
- Channel/group **image or file contents** (payload only includes HTML stub).
|
||||||
- Downloading attachments stored in SharePoint/OneDrive.
|
- Downloading attachments stored in SharePoint/OneDrive.
|
||||||
- Reading message history (beyond the live webhook event).
|
- Reading message history (beyond the live webhook event).
|
||||||
|
|
||||||
### With **Teams RSC + Microsoft Graph Application permissions**
|
### With **Teams RSC + Microsoft Graph Application permissions**
|
||||||
|
|
||||||
Adds:
|
Adds:
|
||||||
|
|
||||||
- Downloading hosted contents (images pasted into messages).
|
- Downloading hosted contents (images pasted into messages).
|
||||||
- Downloading file attachments stored in SharePoint/OneDrive.
|
- Downloading file attachments stored in SharePoint/OneDrive.
|
||||||
- Reading channel/chat message history via Graph.
|
- Reading channel/chat message history via Graph.
|
||||||
|
|
||||||
### RSC vs Graph API
|
### RSC vs Graph API
|
||||||
|
|
||||||
| Capability | RSC Permissions | Graph API |
|
| Capability | RSC Permissions | Graph API |
|
||||||
|------------|-----------------|-----------|
|
| ----------------------- | -------------------- | ----------------------------------- |
|
||||||
| **Real-time messages** | Yes (via webhook) | No (polling only) |
|
| **Real-time messages** | Yes (via webhook) | No (polling only) |
|
||||||
| **Historical messages** | No | Yes (can query history) |
|
| **Historical messages** | No | Yes (can query history) |
|
||||||
| **Setup complexity** | App manifest only | Requires admin consent + token flow |
|
| **Setup complexity** | App manifest only | Requires admin consent + token flow |
|
||||||
| **Works offline** | No (must be running) | Yes (query anytime) |
|
| **Works offline** | No (must be running) | Yes (query anytime) |
|
||||||
|
|
||||||
**Bottom line:** RSC is for real-time listening; Graph API is for historical access. For catching up on missed messages while offline, you need Graph API with `ChannelMessage.Read.All` (requires admin consent).
|
**Bottom line:** RSC is for real-time listening; Graph API is for historical access. For catching up on missed messages while offline, you need Graph API with `ChannelMessage.Read.All` (requires admin consent).
|
||||||
|
|
||||||
## Graph-enabled media + history (required for channels)
|
## Graph-enabled media + history (required for channels)
|
||||||
|
|
||||||
If you need images/files in **channels** or want to fetch **message history**, you must enable Microsoft Graph permissions and grant admin consent.
|
If you need images/files in **channels** or want to fetch **message history**, you must enable Microsoft Graph permissions and grant admin consent.
|
||||||
|
|
||||||
1. In Entra ID (Azure AD) **App Registration**, add Microsoft Graph **Application permissions**:
|
1. In Entra ID (Azure AD) **App Registration**, add Microsoft Graph **Application permissions**:
|
||||||
@@ -392,7 +426,9 @@ If you need images/files in **channels** or want to fetch **message history**, y
|
|||||||
## Known Limitations
|
## Known Limitations
|
||||||
|
|
||||||
### Webhook timeouts
|
### Webhook timeouts
|
||||||
|
|
||||||
Teams delivers messages via HTTP webhook. If processing takes too long (e.g., slow LLM responses), you may see:
|
Teams delivers messages via HTTP webhook. If processing takes too long (e.g., slow LLM responses), you may see:
|
||||||
|
|
||||||
- Gateway timeouts
|
- Gateway timeouts
|
||||||
- Teams retrying the message (causing duplicates)
|
- Teams retrying the message (causing duplicates)
|
||||||
- Dropped replies
|
- Dropped replies
|
||||||
@@ -400,12 +436,15 @@ Teams delivers messages via HTTP webhook. If processing takes too long (e.g., sl
|
|||||||
OpenClaw handles this by returning quickly and sending replies proactively, but very slow responses may still cause issues.
|
OpenClaw handles this by returning quickly and sending replies proactively, but very slow responses may still cause issues.
|
||||||
|
|
||||||
### Formatting
|
### Formatting
|
||||||
|
|
||||||
Teams markdown is more limited than Slack or Discord:
|
Teams markdown is more limited than Slack or Discord:
|
||||||
- Basic formatting works: **bold**, *italic*, `code`, links
|
|
||||||
|
- Basic formatting works: **bold**, _italic_, `code`, links
|
||||||
- Complex markdown (tables, nested lists) may not render correctly
|
- Complex markdown (tables, nested lists) may not render correctly
|
||||||
- Adaptive Cards are supported for polls and arbitrary card sends (see below)
|
- Adaptive Cards are supported for polls and arbitrary card sends (see below)
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
Key settings (see `/gateway/configuration` for shared channel patterns):
|
Key settings (see `/gateway/configuration` for shared channel patterns):
|
||||||
|
|
||||||
- `channels.msteams.enabled`: enable/disable the channel.
|
- `channels.msteams.enabled`: enable/disable the channel.
|
||||||
@@ -417,6 +456,7 @@ Key settings (see `/gateway/configuration` for shared channel patterns):
|
|||||||
- `channels.msteams.textChunkLimit`: outbound text chunk size.
|
- `channels.msteams.textChunkLimit`: outbound text chunk size.
|
||||||
- `channels.msteams.chunkMode`: `length` (default) or `newline` to split on blank lines (paragraph boundaries) before length chunking.
|
- `channels.msteams.chunkMode`: `length` (default) or `newline` to split on blank lines (paragraph boundaries) before length chunking.
|
||||||
- `channels.msteams.mediaAllowHosts`: allowlist for inbound attachment hosts (defaults to Microsoft/Teams domains).
|
- `channels.msteams.mediaAllowHosts`: allowlist for inbound attachment hosts (defaults to Microsoft/Teams domains).
|
||||||
|
- `channels.msteams.mediaAuthAllowHosts`: allowlist for attaching Authorization headers on media retries (defaults to Graph + Bot Framework hosts).
|
||||||
- `channels.msteams.requireMention`: require @mention in channels/groups (default true).
|
- `channels.msteams.requireMention`: require @mention in channels/groups (default true).
|
||||||
- `channels.msteams.replyStyle`: `thread | top-level` (see [Reply Style](#reply-style-threads-vs-posts)).
|
- `channels.msteams.replyStyle`: `thread | top-level` (see [Reply Style](#reply-style-threads-vs-posts)).
|
||||||
- `channels.msteams.teams.<teamId>.replyStyle`: per-team override.
|
- `channels.msteams.teams.<teamId>.replyStyle`: per-team override.
|
||||||
@@ -430,6 +470,7 @@ Key settings (see `/gateway/configuration` for shared channel patterns):
|
|||||||
- `channels.msteams.sharePointSiteId`: SharePoint site ID for file uploads in group chats/channels (see [Sending files in group chats](#sending-files-in-group-chats)).
|
- `channels.msteams.sharePointSiteId`: SharePoint site ID for file uploads in group chats/channels (see [Sending files in group chats](#sending-files-in-group-chats)).
|
||||||
|
|
||||||
## Routing & Sessions
|
## Routing & Sessions
|
||||||
|
|
||||||
- Session keys follow the standard agent format (see [/concepts/session](/concepts/session)):
|
- Session keys follow the standard agent format (see [/concepts/session](/concepts/session)):
|
||||||
- Direct messages share the main session (`agent:<agentId>:<mainKey>`).
|
- Direct messages share the main session (`agent:<agentId>:<mainKey>`).
|
||||||
- Channel/group messages use conversation id:
|
- Channel/group messages use conversation id:
|
||||||
@@ -440,12 +481,13 @@ Key settings (see `/gateway/configuration` for shared channel patterns):
|
|||||||
|
|
||||||
Teams recently introduced two channel UI styles over the same underlying data model:
|
Teams recently introduced two channel UI styles over the same underlying data model:
|
||||||
|
|
||||||
| Style | Description | Recommended `replyStyle` |
|
| Style | Description | Recommended `replyStyle` |
|
||||||
|-------|-------------|--------------------------|
|
| ------------------------ | --------------------------------------------------------- | ------------------------ |
|
||||||
| **Posts** (classic) | Messages appear as cards with threaded replies underneath | `thread` (default) |
|
| **Posts** (classic) | Messages appear as cards with threaded replies underneath | `thread` (default) |
|
||||||
| **Threads** (Slack-like) | Messages flow linearly, more like Slack | `top-level` |
|
| **Threads** (Slack-like) | Messages flow linearly, more like Slack | `top-level` |
|
||||||
|
|
||||||
**The problem:** The Teams API does not expose which UI style a channel uses. If you use the wrong `replyStyle`:
|
**The problem:** The Teams API does not expose which UI style a channel uses. If you use the wrong `replyStyle`:
|
||||||
|
|
||||||
- `thread` in a Threads-style channel → replies appear nested awkwardly
|
- `thread` in a Threads-style channel → replies appear nested awkwardly
|
||||||
- `top-level` in a Posts-style channel → replies appear as separate top-level posts instead of in-thread
|
- `top-level` in a Posts-style channel → replies appear as separate top-level posts instead of in-thread
|
||||||
|
|
||||||
@@ -471,21 +513,23 @@ Teams recently introduced two channel UI styles over the same underlying data mo
|
|||||||
## Attachments & Images
|
## Attachments & Images
|
||||||
|
|
||||||
**Current limitations:**
|
**Current limitations:**
|
||||||
|
|
||||||
- **DMs:** Images and file attachments work via Teams bot file APIs.
|
- **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.
|
- **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.
|
||||||
|
|
||||||
Without Graph permissions, channel messages with images will be received as text-only (the image content is not accessible to the bot).
|
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).
|
By default, OpenClaw only downloads media from Microsoft/Teams hostnames. Override with `channels.msteams.mediaAllowHosts` (use `["*"]` to allow any host).
|
||||||
|
Authorization headers are only attached for hosts in `channels.msteams.mediaAuthAllowHosts` (defaults to Graph + Bot Framework hosts). Keep this list strict (avoid multi-tenant suffixes).
|
||||||
|
|
||||||
## Sending files in group chats
|
## Sending files in group chats
|
||||||
|
|
||||||
Bots can send files in DMs using the FileConsentCard flow (built-in). However, **sending files in group chats/channels** requires additional setup:
|
Bots can send files in DMs using the FileConsentCard flow (built-in). However, **sending files in group chats/channels** requires additional setup:
|
||||||
|
|
||||||
| Context | How files are sent | Setup needed |
|
| Context | How files are sent | Setup needed |
|
||||||
|---------|-------------------|--------------|
|
| ------------------------ | -------------------------------------------- | ----------------------------------------------- |
|
||||||
| **DMs** | FileConsentCard → user accepts → bot uploads | Works out of the box |
|
| **DMs** | FileConsentCard → user accepts → bot uploads | Works out of the box |
|
||||||
| **Group chats/channels** | Upload to SharePoint → share link | Requires `sharePointSiteId` + Graph permissions |
|
| **Group chats/channels** | Upload to SharePoint → share link | Requires `sharePointSiteId` + Graph permissions |
|
||||||
| **Images (any context)** | Base64-encoded inline | Works out of the box |
|
| **Images (any context)** | Base64-encoded inline | Works out of the box |
|
||||||
|
|
||||||
### Why group chats need SharePoint
|
### Why group chats need SharePoint
|
||||||
|
|
||||||
@@ -500,6 +544,7 @@ Bots don't have a personal OneDrive drive (the `/me/drive` Graph API endpoint do
|
|||||||
2. **Grant admin consent** for the tenant.
|
2. **Grant admin consent** for the tenant.
|
||||||
|
|
||||||
3. **Get your SharePoint site ID:**
|
3. **Get your SharePoint site ID:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Via Graph Explorer or curl with a valid token:
|
# Via Graph Explorer or curl with a valid token:
|
||||||
curl -H "Authorization: Bearer $TOKEN" \
|
curl -H "Authorization: Bearer $TOKEN" \
|
||||||
@@ -518,35 +563,36 @@ Bots don't have a personal OneDrive drive (the `/me/drive` Graph API endpoint do
|
|||||||
channels: {
|
channels: {
|
||||||
msteams: {
|
msteams: {
|
||||||
// ... other config ...
|
// ... other config ...
|
||||||
sharePointSiteId: "contoso.sharepoint.com,guid1,guid2"
|
sharePointSiteId: "contoso.sharepoint.com,guid1,guid2",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Sharing behavior
|
### Sharing behavior
|
||||||
|
|
||||||
| Permission | Sharing behavior |
|
| Permission | Sharing behavior |
|
||||||
|------------|------------------|
|
| --------------------------------------- | --------------------------------------------------------- |
|
||||||
| `Sites.ReadWrite.All` only | Organization-wide sharing link (anyone in org can access) |
|
| `Sites.ReadWrite.All` only | Organization-wide sharing link (anyone in org can access) |
|
||||||
| `Sites.ReadWrite.All` + `Chat.Read.All` | Per-user sharing link (only chat members can access) |
|
| `Sites.ReadWrite.All` + `Chat.Read.All` | Per-user sharing link (only chat members can access) |
|
||||||
|
|
||||||
Per-user sharing is more secure as only the chat participants can access the file. If `Chat.Read.All` permission is missing, the bot falls back to organization-wide sharing.
|
Per-user sharing is more secure as only the chat participants can access the file. If `Chat.Read.All` permission is missing, the bot falls back to organization-wide sharing.
|
||||||
|
|
||||||
### Fallback behavior
|
### Fallback behavior
|
||||||
|
|
||||||
| Scenario | Result |
|
| Scenario | Result |
|
||||||
|----------|--------|
|
| ------------------------------------------------- | -------------------------------------------------- |
|
||||||
| Group chat + file + `sharePointSiteId` configured | Upload to SharePoint, send sharing link |
|
| Group chat + file + `sharePointSiteId` configured | Upload to SharePoint, send sharing link |
|
||||||
| Group chat + file + no `sharePointSiteId` | Attempt OneDrive upload (may fail), send text only |
|
| Group chat + file + no `sharePointSiteId` | Attempt OneDrive upload (may fail), send text only |
|
||||||
| Personal chat + file | FileConsentCard flow (works without SharePoint) |
|
| Personal chat + file | FileConsentCard flow (works without SharePoint) |
|
||||||
| Any context + image | Base64-encoded inline (works without SharePoint) |
|
| Any context + image | Base64-encoded inline (works without SharePoint) |
|
||||||
|
|
||||||
### Files stored location
|
### Files stored location
|
||||||
|
|
||||||
Uploaded files are stored in a `/OpenClawShared/` folder in the configured SharePoint site's default document library.
|
Uploaded files are stored in a `/OpenClawShared/` folder in the configured SharePoint site's default document library.
|
||||||
|
|
||||||
## Polls (Adaptive Cards)
|
## Polls (Adaptive Cards)
|
||||||
|
|
||||||
OpenClaw sends Teams polls as Adaptive Cards (there is no native Teams poll API).
|
OpenClaw sends Teams polls as Adaptive Cards (there is no native Teams poll API).
|
||||||
|
|
||||||
- CLI: `openclaw message poll --channel msteams --target conversation:<id> ...`
|
- CLI: `openclaw message poll --channel msteams --target conversation:<id> ...`
|
||||||
@@ -555,11 +601,13 @@ OpenClaw sends Teams polls as Adaptive Cards (there is no native Teams poll API)
|
|||||||
- Polls do not auto-post result summaries yet (inspect the store file if needed).
|
- Polls do not auto-post result summaries yet (inspect the store file if needed).
|
||||||
|
|
||||||
## Adaptive Cards (arbitrary)
|
## Adaptive Cards (arbitrary)
|
||||||
|
|
||||||
Send any Adaptive Card JSON to Teams users or conversations using the `message` tool or CLI.
|
Send any Adaptive Card JSON to Teams users or conversations using the `message` tool or CLI.
|
||||||
|
|
||||||
The `card` parameter accepts an Adaptive Card JSON object. When `card` is provided, the message text is optional.
|
The `card` parameter accepts an Adaptive Card JSON object. When `card` is provided, the message text is optional.
|
||||||
|
|
||||||
**Agent tool:**
|
**Agent tool:**
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"action": "send",
|
"action": "send",
|
||||||
@@ -568,12 +616,13 @@ The `card` parameter accepts an Adaptive Card JSON object. When `card` is provid
|
|||||||
"card": {
|
"card": {
|
||||||
"type": "AdaptiveCard",
|
"type": "AdaptiveCard",
|
||||||
"version": "1.5",
|
"version": "1.5",
|
||||||
"body": [{"type": "TextBlock", "text": "Hello!"}]
|
"body": [{ "type": "TextBlock", "text": "Hello!" }]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**CLI:**
|
**CLI:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
openclaw message send --channel msteams \
|
openclaw message send --channel msteams \
|
||||||
--target "conversation:19:abc...@thread.tacv2" \
|
--target "conversation:19:abc...@thread.tacv2" \
|
||||||
@@ -586,14 +635,15 @@ See [Adaptive Cards documentation](https://adaptivecards.io/) for card schema an
|
|||||||
|
|
||||||
MSTeams targets use prefixes to distinguish between users and conversations:
|
MSTeams targets use prefixes to distinguish between users and conversations:
|
||||||
|
|
||||||
| Target type | Format | Example |
|
| Target type | Format | Example |
|
||||||
|-------------|--------|---------|
|
| ------------------- | -------------------------------- | --------------------------------------------------- |
|
||||||
| User (by ID) | `user:<aad-object-id>` | `user:40a1a0ed-4ff2-4164-a219-55518990c197` |
|
| User (by ID) | `user:<aad-object-id>` | `user:40a1a0ed-4ff2-4164-a219-55518990c197` |
|
||||||
| User (by name) | `user:<display-name>` | `user:John Smith` (requires Graph API) |
|
| User (by name) | `user:<display-name>` | `user:John Smith` (requires Graph API) |
|
||||||
| Group/channel | `conversation:<conversation-id>` | `conversation:19:abc123...@thread.tacv2` |
|
| Group/channel | `conversation:<conversation-id>` | `conversation:19:abc123...@thread.tacv2` |
|
||||||
| Group/channel (raw) | `<conversation-id>` | `19:abc123...@thread.tacv2` (if contains `@thread`) |
|
| Group/channel (raw) | `<conversation-id>` | `19:abc123...@thread.tacv2` (if contains `@thread`) |
|
||||||
|
|
||||||
**CLI examples:**
|
**CLI examples:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Send to a user by ID
|
# Send to a user by ID
|
||||||
openclaw message send --channel msteams --target "user:40a1a0ed-..." --message "Hello"
|
openclaw message send --channel msteams --target "user:40a1a0ed-..." --message "Hello"
|
||||||
@@ -610,6 +660,7 @@ openclaw message send --channel msteams --target "conversation:19:abc...@thread.
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Agent tool examples:**
|
**Agent tool examples:**
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"action": "send",
|
"action": "send",
|
||||||
@@ -624,13 +675,18 @@ openclaw message send --channel msteams --target "conversation:19:abc...@thread.
|
|||||||
"action": "send",
|
"action": "send",
|
||||||
"channel": "msteams",
|
"channel": "msteams",
|
||||||
"target": "conversation:19:abc...@thread.tacv2",
|
"target": "conversation:19:abc...@thread.tacv2",
|
||||||
"card": {"type": "AdaptiveCard", "version": "1.5", "body": [{"type": "TextBlock", "text": "Hello"}]}
|
"card": {
|
||||||
|
"type": "AdaptiveCard",
|
||||||
|
"version": "1.5",
|
||||||
|
"body": [{ "type": "TextBlock", "text": "Hello" }]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: Without the `user:` prefix, names default to group/team resolution. Always use `user:` when targeting people by display name.
|
Note: Without the `user:` prefix, names default to group/team resolution. Always use `user:` when targeting people by display name.
|
||||||
|
|
||||||
## Proactive messaging
|
## Proactive messaging
|
||||||
|
|
||||||
- Proactive messages are only possible **after** a user has interacted, because we store conversation references at that point.
|
- Proactive messages are only possible **after** a user has interacted, because we store conversation references at that point.
|
||||||
- See `/gateway/configuration` for `dmPolicy` and allowlist gating.
|
- See `/gateway/configuration` for `dmPolicy` and allowlist gating.
|
||||||
|
|
||||||
@@ -639,6 +695,7 @@ Note: Without the `user:` prefix, names default to group/team resolution. Always
|
|||||||
The `groupId` query parameter in Teams URLs is **NOT** the team ID used for configuration. Extract IDs from the URL path instead:
|
The `groupId` query parameter in Teams URLs is **NOT** the team ID used for configuration. Extract IDs from the URL path instead:
|
||||||
|
|
||||||
**Team URL:**
|
**Team URL:**
|
||||||
|
|
||||||
```
|
```
|
||||||
https://teams.microsoft.com/l/team/19%3ABk4j...%40thread.tacv2/conversations?groupId=...
|
https://teams.microsoft.com/l/team/19%3ABk4j...%40thread.tacv2/conversations?groupId=...
|
||||||
└────────────────────────────┘
|
└────────────────────────────┘
|
||||||
@@ -646,6 +703,7 @@ https://teams.microsoft.com/l/team/19%3ABk4j...%40thread.tacv2/conversations?gro
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Channel URL:**
|
**Channel URL:**
|
||||||
|
|
||||||
```
|
```
|
||||||
https://teams.microsoft.com/l/channel/19%3A15bc...%40thread.tacv2/ChannelName?groupId=...
|
https://teams.microsoft.com/l/channel/19%3A15bc...%40thread.tacv2/ChannelName?groupId=...
|
||||||
└─────────────────────────┘
|
└─────────────────────────┘
|
||||||
@@ -653,6 +711,7 @@ https://teams.microsoft.com/l/channel/19%3A15bc...%40thread.tacv2/ChannelName?gr
|
|||||||
```
|
```
|
||||||
|
|
||||||
**For config:**
|
**For config:**
|
||||||
|
|
||||||
- Team ID = path segment after `/team/` (URL-decoded, e.g., `19:Bk4j...@thread.tacv2`)
|
- Team ID = path segment after `/team/` (URL-decoded, e.g., `19:Bk4j...@thread.tacv2`)
|
||||||
- Channel ID = path segment after `/channel/` (URL-decoded)
|
- Channel ID = path segment after `/channel/` (URL-decoded)
|
||||||
- **Ignore** the `groupId` query parameter
|
- **Ignore** the `groupId` query parameter
|
||||||
@@ -661,15 +720,16 @@ https://teams.microsoft.com/l/channel/19%3A15bc...%40thread.tacv2/ChannelName?gr
|
|||||||
|
|
||||||
Bots have limited support in private channels:
|
Bots have limited support in private channels:
|
||||||
|
|
||||||
| Feature | Standard Channels | Private Channels |
|
| Feature | Standard Channels | Private Channels |
|
||||||
|---------|-------------------|------------------|
|
| ---------------------------- | ----------------- | ---------------------- |
|
||||||
| Bot installation | Yes | Limited |
|
| Bot installation | Yes | Limited |
|
||||||
| Real-time messages (webhook) | Yes | May not work |
|
| Real-time messages (webhook) | Yes | May not work |
|
||||||
| RSC permissions | Yes | May behave differently |
|
| RSC permissions | Yes | May behave differently |
|
||||||
| @mentions | Yes | If bot is accessible |
|
| @mentions | Yes | If bot is accessible |
|
||||||
| Graph API history | Yes | Yes (with permissions) |
|
| Graph API history | Yes | Yes (with permissions) |
|
||||||
|
|
||||||
**Workarounds if private channels don't work:**
|
**Workarounds if private channels don't work:**
|
||||||
|
|
||||||
1. Use standard channels for bot interactions
|
1. Use standard channels for bot interactions
|
||||||
2. Use DMs - users can always message the bot directly
|
2. Use DMs - users can always message the bot directly
|
||||||
3. Use Graph API for historical access (requires `ChannelMessage.Read.All`)
|
3. Use Graph API for historical access (requires `ChannelMessage.Read.All`)
|
||||||
@@ -698,6 +758,7 @@ Bots have limited support in private channels:
|
|||||||
4. Confirm you're using the right scope: `ChannelMessage.Read.Group` for teams, `ChatMessage.Read.Chat` for group chats
|
4. Confirm you're using the right scope: `ChannelMessage.Read.Group` for teams, `ChatMessage.Read.Chat` for group chats
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
- [Create Azure Bot](https://learn.microsoft.com/en-us/azure/bot-service/bot-service-quickstart-registration) - Azure Bot setup guide
|
- [Create Azure Bot](https://learn.microsoft.com/en-us/azure/bot-service/bot-service-quickstart-registration) - Azure Bot setup guide
|
||||||
- [Teams Developer Portal](https://dev.teams.microsoft.com/apps) - create/manage Teams apps
|
- [Teams Developer Portal](https://dev.teams.microsoft.com/apps) - create/manage Teams apps
|
||||||
- [Teams app manifest schema](https://learn.microsoft.com/en-us/microsoftteams/platform/resources/schema/manifest-schema)
|
- [Teams app manifest schema](https://learn.microsoft.com/en-us/microsoftteams/platform/resources/schema/manifest-schema)
|
||||||
|
|||||||
@@ -2,20 +2,25 @@
|
|||||||
summary: "Nextcloud Talk support status, capabilities, and configuration"
|
summary: "Nextcloud Talk support status, capabilities, and configuration"
|
||||||
read_when:
|
read_when:
|
||||||
- Working on Nextcloud Talk channel features
|
- Working on Nextcloud Talk channel features
|
||||||
|
title: "Nextcloud Talk"
|
||||||
---
|
---
|
||||||
|
|
||||||
# Nextcloud Talk (plugin)
|
# Nextcloud Talk (plugin)
|
||||||
|
|
||||||
Status: supported via plugin (webhook bot). Direct messages, rooms, reactions, and markdown messages are supported.
|
Status: supported via plugin (webhook bot). Direct messages, rooms, reactions, and markdown messages are supported.
|
||||||
|
|
||||||
## Plugin required
|
## Plugin required
|
||||||
|
|
||||||
Nextcloud Talk ships as a plugin and is not bundled with the core install.
|
Nextcloud Talk ships as a plugin and is not bundled with the core install.
|
||||||
|
|
||||||
Install via CLI (npm registry):
|
Install via CLI (npm registry):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
openclaw plugins install @openclaw/nextcloud-talk
|
openclaw plugins install @openclaw/nextcloud-talk
|
||||||
```
|
```
|
||||||
|
|
||||||
Local checkout (when running from a git repo):
|
Local checkout (when running from a git repo):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
openclaw plugins install ./extensions/nextcloud-talk
|
openclaw plugins install ./extensions/nextcloud-talk
|
||||||
```
|
```
|
||||||
@@ -26,18 +31,20 @@ OpenClaw will offer the local install path automatically.
|
|||||||
Details: [Plugins](/plugin)
|
Details: [Plugins](/plugin)
|
||||||
|
|
||||||
## Quick setup (beginner)
|
## Quick setup (beginner)
|
||||||
1) Install the Nextcloud Talk plugin.
|
|
||||||
2) On your Nextcloud server, create a bot:
|
1. Install the Nextcloud Talk plugin.
|
||||||
|
2. On your Nextcloud server, create a bot:
|
||||||
```bash
|
```bash
|
||||||
./occ talk:bot:install "OpenClaw" "<shared-secret>" "<webhook-url>" --feature reaction
|
./occ talk:bot:install "OpenClaw" "<shared-secret>" "<webhook-url>" --feature reaction
|
||||||
```
|
```
|
||||||
3) Enable the bot in the target room settings.
|
3. Enable the bot in the target room settings.
|
||||||
4) Configure OpenClaw:
|
4. Configure OpenClaw:
|
||||||
- Config: `channels.nextcloud-talk.baseUrl` + `channels.nextcloud-talk.botSecret`
|
- Config: `channels.nextcloud-talk.baseUrl` + `channels.nextcloud-talk.botSecret`
|
||||||
- Or env: `NEXTCLOUD_TALK_BOT_SECRET` (default account only)
|
- Or env: `NEXTCLOUD_TALK_BOT_SECRET` (default account only)
|
||||||
5) Restart the gateway (or finish onboarding).
|
5. Restart the gateway (or finish onboarding).
|
||||||
|
|
||||||
Minimal config:
|
Minimal config:
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
channels: {
|
channels: {
|
||||||
@@ -45,19 +52,21 @@ Minimal config:
|
|||||||
enabled: true,
|
enabled: true,
|
||||||
baseUrl: "https://cloud.example.com",
|
baseUrl: "https://cloud.example.com",
|
||||||
botSecret: "shared-secret",
|
botSecret: "shared-secret",
|
||||||
dmPolicy: "pairing"
|
dmPolicy: "pairing",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- Bots cannot initiate DMs. The user must message the bot first.
|
- Bots cannot initiate DMs. The user must message the bot first.
|
||||||
- Webhook URL must be reachable by the Gateway; set `webhookPublicUrl` if behind a proxy.
|
- Webhook URL must be reachable by the Gateway; set `webhookPublicUrl` if behind a proxy.
|
||||||
- Media uploads are not supported by the bot API; media is sent as URLs.
|
- Media uploads are not supported by the bot API; media is sent as URLs.
|
||||||
- The webhook payload does not distinguish DMs vs rooms; set `apiUser` + `apiPassword` to enable room-type lookups (otherwise DMs are treated as rooms).
|
- The webhook payload does not distinguish DMs vs rooms; set `apiUser` + `apiPassword` to enable room-type lookups (otherwise DMs are treated as rooms).
|
||||||
|
|
||||||
## Access control (DMs)
|
## Access control (DMs)
|
||||||
|
|
||||||
- Default: `channels.nextcloud-talk.dmPolicy = "pairing"`. Unknown senders get a pairing code.
|
- Default: `channels.nextcloud-talk.dmPolicy = "pairing"`. Unknown senders get a pairing code.
|
||||||
- Approve via:
|
- Approve via:
|
||||||
- `openclaw pairing list nextcloud-talk`
|
- `openclaw pairing list nextcloud-talk`
|
||||||
@@ -65,35 +74,41 @@ Minimal config:
|
|||||||
- Public DMs: `channels.nextcloud-talk.dmPolicy="open"` plus `channels.nextcloud-talk.allowFrom=["*"]`.
|
- Public DMs: `channels.nextcloud-talk.dmPolicy="open"` plus `channels.nextcloud-talk.allowFrom=["*"]`.
|
||||||
|
|
||||||
## Rooms (groups)
|
## Rooms (groups)
|
||||||
|
|
||||||
- Default: `channels.nextcloud-talk.groupPolicy = "allowlist"` (mention-gated).
|
- Default: `channels.nextcloud-talk.groupPolicy = "allowlist"` (mention-gated).
|
||||||
- Allowlist rooms with `channels.nextcloud-talk.rooms`:
|
- Allowlist rooms with `channels.nextcloud-talk.rooms`:
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
channels: {
|
channels: {
|
||||||
"nextcloud-talk": {
|
"nextcloud-talk": {
|
||||||
rooms: {
|
rooms: {
|
||||||
"room-token": { requireMention: true }
|
"room-token": { requireMention: true },
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
- To allow no rooms, keep the allowlist empty or set `channels.nextcloud-talk.groupPolicy="disabled"`.
|
- To allow no rooms, keep the allowlist empty or set `channels.nextcloud-talk.groupPolicy="disabled"`.
|
||||||
|
|
||||||
## Capabilities
|
## Capabilities
|
||||||
| Feature | Status |
|
|
||||||
|---------|--------|
|
| Feature | Status |
|
||||||
| Direct messages | Supported |
|
| --------------- | ------------- |
|
||||||
| Rooms | Supported |
|
| Direct messages | Supported |
|
||||||
| Threads | Not supported |
|
| Rooms | Supported |
|
||||||
| Media | URL-only |
|
| Threads | Not supported |
|
||||||
| Reactions | Supported |
|
| Media | URL-only |
|
||||||
|
| Reactions | Supported |
|
||||||
| Native commands | Not supported |
|
| Native commands | Not supported |
|
||||||
|
|
||||||
## Configuration reference (Nextcloud Talk)
|
## Configuration reference (Nextcloud Talk)
|
||||||
|
|
||||||
Full configuration: [Configuration](/gateway/configuration)
|
Full configuration: [Configuration](/gateway/configuration)
|
||||||
|
|
||||||
Provider options:
|
Provider options:
|
||||||
|
|
||||||
- `channels.nextcloud-talk.enabled`: enable/disable channel startup.
|
- `channels.nextcloud-talk.enabled`: enable/disable channel startup.
|
||||||
- `channels.nextcloud-talk.baseUrl`: Nextcloud instance URL.
|
- `channels.nextcloud-talk.baseUrl`: Nextcloud instance URL.
|
||||||
- `channels.nextcloud-talk.botSecret`: bot shared secret.
|
- `channels.nextcloud-talk.botSecret`: bot shared secret.
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ summary: "Nostr DM channel via NIP-04 encrypted messages"
|
|||||||
read_when:
|
read_when:
|
||||||
- You want OpenClaw to receive DMs via Nostr
|
- You want OpenClaw to receive DMs via Nostr
|
||||||
- You're setting up decentralized messaging
|
- You're setting up decentralized messaging
|
||||||
|
title: "Nostr"
|
||||||
---
|
---
|
||||||
|
|
||||||
# Nostr
|
# Nostr
|
||||||
|
|
||||||
**Status:** Optional plugin (disabled by default).
|
**Status:** Optional plugin (disabled by default).
|
||||||
@@ -40,14 +42,14 @@ Restart the Gateway after installing or enabling plugins.
|
|||||||
|
|
||||||
## Quick setup
|
## Quick setup
|
||||||
|
|
||||||
1) Generate a Nostr keypair (if needed):
|
1. Generate a Nostr keypair (if needed):
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Using nak
|
# Using nak
|
||||||
nak key generate
|
nak key generate
|
||||||
```
|
```
|
||||||
|
|
||||||
2) Add to config:
|
2. Add to config:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@@ -59,25 +61,25 @@ nak key generate
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
3) Export the key:
|
3. Export the key:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
export NOSTR_PRIVATE_KEY="nsec1..."
|
export NOSTR_PRIVATE_KEY="nsec1..."
|
||||||
```
|
```
|
||||||
|
|
||||||
4) Restart the Gateway.
|
4. Restart the Gateway.
|
||||||
|
|
||||||
## Configuration reference
|
## Configuration reference
|
||||||
|
|
||||||
| Key | Type | Default | Description |
|
| Key | Type | Default | Description |
|
||||||
| --- | --- | --- | --- |
|
| ------------ | -------- | ------------------------------------------- | ----------------------------------- |
|
||||||
| `privateKey` | string | required | Private key in `nsec` or hex format |
|
| `privateKey` | string | required | Private key in `nsec` or hex format |
|
||||||
| `relays` | string[] | `['wss://relay.damus.io', 'wss://nos.lol']` | Relay URLs (WebSocket) |
|
| `relays` | string[] | `['wss://relay.damus.io', 'wss://nos.lol']` | Relay URLs (WebSocket) |
|
||||||
| `dmPolicy` | string | `pairing` | DM access policy |
|
| `dmPolicy` | string | `pairing` | DM access policy |
|
||||||
| `allowFrom` | string[] | `[]` | Allowed sender pubkeys |
|
| `allowFrom` | string[] | `[]` | Allowed sender pubkeys |
|
||||||
| `enabled` | boolean | `true` | Enable/disable channel |
|
| `enabled` | boolean | `true` | Enable/disable channel |
|
||||||
| `name` | string | - | Display name |
|
| `name` | string | - | Display name |
|
||||||
| `profile` | object | - | NIP-01 profile metadata |
|
| `profile` | object | - | NIP-01 profile metadata |
|
||||||
|
|
||||||
## Profile metadata
|
## Profile metadata
|
||||||
|
|
||||||
@@ -149,11 +151,7 @@ Defaults: `relay.damus.io` and `nos.lol`.
|
|||||||
"channels": {
|
"channels": {
|
||||||
"nostr": {
|
"nostr": {
|
||||||
"privateKey": "${NOSTR_PRIVATE_KEY}",
|
"privateKey": "${NOSTR_PRIVATE_KEY}",
|
||||||
"relays": [
|
"relays": ["wss://relay.damus.io", "wss://relay.primal.net", "wss://nostr.wine"]
|
||||||
"wss://relay.damus.io",
|
|
||||||
"wss://relay.primal.net",
|
|
||||||
"wss://nostr.wine"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -168,12 +166,12 @@ Tips:
|
|||||||
|
|
||||||
## Protocol support
|
## Protocol support
|
||||||
|
|
||||||
| NIP | Status | Description |
|
| NIP | Status | Description |
|
||||||
| --- | --- | --- |
|
| ------ | --------- | ------------------------------------- |
|
||||||
| NIP-01 | Supported | Basic event format + profile metadata |
|
| NIP-01 | Supported | Basic event format + profile metadata |
|
||||||
| NIP-04 | Supported | Encrypted DMs (`kind:4`) |
|
| NIP-04 | Supported | Encrypted DMs (`kind:4`) |
|
||||||
| NIP-17 | Planned | Gift-wrapped DMs |
|
| NIP-17 | Planned | Gift-wrapped DMs |
|
||||||
| NIP-44 | Planned | Versioned encryption |
|
| NIP-44 | Planned | Versioned encryption |
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
@@ -197,10 +195,10 @@ docker run -p 7777:7777 ghcr.io/hoytech/strfry
|
|||||||
|
|
||||||
### Manual test
|
### Manual test
|
||||||
|
|
||||||
1) Note the bot pubkey (npub) from logs.
|
1. Note the bot pubkey (npub) from logs.
|
||||||
2) Open a Nostr client (Damus, Amethyst, etc.).
|
2. Open a Nostr client (Damus, Amethyst, etc.).
|
||||||
3) DM the bot pubkey.
|
3. DM the bot pubkey.
|
||||||
4) Verify the response.
|
4. Verify the response.
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
|
|||||||
@@ -3,20 +3,23 @@ summary: "Signal support via signal-cli (JSON-RPC + SSE), setup, and number mode
|
|||||||
read_when:
|
read_when:
|
||||||
- Setting up Signal support
|
- Setting up Signal support
|
||||||
- Debugging Signal send/receive
|
- Debugging Signal send/receive
|
||||||
|
title: "Signal"
|
||||||
---
|
---
|
||||||
# Signal (signal-cli)
|
|
||||||
|
|
||||||
|
# Signal (signal-cli)
|
||||||
|
|
||||||
Status: external CLI integration. Gateway talks to `signal-cli` over HTTP JSON-RPC + SSE.
|
Status: external CLI integration. Gateway talks to `signal-cli` over HTTP JSON-RPC + SSE.
|
||||||
|
|
||||||
## Quick setup (beginner)
|
## Quick setup (beginner)
|
||||||
1) Use a **separate Signal number** for the bot (recommended).
|
|
||||||
2) Install `signal-cli` (Java required).
|
1. Use a **separate Signal number** for the bot (recommended).
|
||||||
3) Link the bot device and start the daemon:
|
2. Install `signal-cli` (Java required).
|
||||||
|
3. Link the bot device and start the daemon:
|
||||||
- `signal-cli link -n "OpenClaw"`
|
- `signal-cli link -n "OpenClaw"`
|
||||||
4) Configure OpenClaw and start the gateway.
|
4. Configure OpenClaw and start the gateway.
|
||||||
|
|
||||||
Minimal config:
|
Minimal config:
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
channels: {
|
channels: {
|
||||||
@@ -25,39 +28,45 @@ Minimal config:
|
|||||||
account: "+15551234567",
|
account: "+15551234567",
|
||||||
cliPath: "signal-cli",
|
cliPath: "signal-cli",
|
||||||
dmPolicy: "pairing",
|
dmPolicy: "pairing",
|
||||||
allowFrom: ["+15557654321"]
|
allowFrom: ["+15557654321"],
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## What it is
|
## What it is
|
||||||
|
|
||||||
- Signal channel via `signal-cli` (not embedded libsignal).
|
- Signal channel via `signal-cli` (not embedded libsignal).
|
||||||
- Deterministic routing: replies always go back to Signal.
|
- Deterministic routing: replies always go back to Signal.
|
||||||
- DMs share the agent's main session; groups are isolated (`agent:<agentId>:signal:group:<groupId>`).
|
- DMs share the agent's main session; groups are isolated (`agent:<agentId>:signal:group:<groupId>`).
|
||||||
|
|
||||||
## Config writes
|
## Config writes
|
||||||
|
|
||||||
By default, Signal is allowed to write config updates triggered by `/config set|unset` (requires `commands.config: true`).
|
By default, Signal is allowed to write config updates triggered by `/config set|unset` (requires `commands.config: true`).
|
||||||
|
|
||||||
Disable with:
|
Disable with:
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
channels: { signal: { configWrites: false } }
|
channels: { signal: { configWrites: false } },
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## The number model (important)
|
## The number model (important)
|
||||||
|
|
||||||
- The gateway connects to a **Signal device** (the `signal-cli` account).
|
- The gateway connects to a **Signal device** (the `signal-cli` account).
|
||||||
- If you run the bot on **your personal Signal account**, it will ignore your own messages (loop protection).
|
- If you run the bot on **your personal Signal account**, it will ignore your own messages (loop protection).
|
||||||
- For "I text the bot and it replies," use a **separate bot number**.
|
- For "I text the bot and it replies," use a **separate bot number**.
|
||||||
|
|
||||||
## Setup (fast path)
|
## Setup (fast path)
|
||||||
1) Install `signal-cli` (Java required).
|
|
||||||
2) Link a bot account:
|
1. Install `signal-cli` (Java required).
|
||||||
|
2. Link a bot account:
|
||||||
- `signal-cli link -n "OpenClaw"` then scan the QR in Signal.
|
- `signal-cli link -n "OpenClaw"` then scan the QR in Signal.
|
||||||
3) Configure Signal and start the gateway.
|
3. Configure Signal and start the gateway.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
channels: {
|
channels: {
|
||||||
@@ -66,15 +75,16 @@ Example:
|
|||||||
account: "+15551234567",
|
account: "+15551234567",
|
||||||
cliPath: "signal-cli",
|
cliPath: "signal-cli",
|
||||||
dmPolicy: "pairing",
|
dmPolicy: "pairing",
|
||||||
allowFrom: ["+15557654321"]
|
allowFrom: ["+15557654321"],
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Multi-account support: use `channels.signal.accounts` with per-account config and optional `name`. See [`gateway/configuration`](/gateway/configuration#telegramaccounts--discordaccounts--slackaccounts--signalaccounts--imessageaccounts) for the shared pattern.
|
Multi-account support: use `channels.signal.accounts` with per-account config and optional `name`. See [`gateway/configuration`](/gateway/configuration#telegramaccounts--discordaccounts--slackaccounts--signalaccounts--imessageaccounts) for the shared pattern.
|
||||||
|
|
||||||
## External daemon mode (httpUrl)
|
## External daemon mode (httpUrl)
|
||||||
|
|
||||||
If you want to manage `signal-cli` yourself (slow JVM cold starts, container init, or shared CPUs), run the daemon separately and point OpenClaw at it:
|
If you want to manage `signal-cli` yourself (slow JVM cold starts, container init, or shared CPUs), run the daemon separately and point OpenClaw at it:
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
@@ -82,16 +92,18 @@ If you want to manage `signal-cli` yourself (slow JVM cold starts, container ini
|
|||||||
channels: {
|
channels: {
|
||||||
signal: {
|
signal: {
|
||||||
httpUrl: "http://127.0.0.1:8080",
|
httpUrl: "http://127.0.0.1:8080",
|
||||||
autoStart: false
|
autoStart: false,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
This skips auto-spawn and the startup wait inside OpenClaw. For slow starts when auto-spawning, set `channels.signal.startupTimeoutMs`.
|
This skips auto-spawn and the startup wait inside OpenClaw. For slow starts when auto-spawning, set `channels.signal.startupTimeoutMs`.
|
||||||
|
|
||||||
## Access control (DMs + groups)
|
## Access control (DMs + groups)
|
||||||
|
|
||||||
DMs:
|
DMs:
|
||||||
|
|
||||||
- Default: `channels.signal.dmPolicy = "pairing"`.
|
- Default: `channels.signal.dmPolicy = "pairing"`.
|
||||||
- Unknown senders receive a pairing code; messages are ignored until approved (codes expire after 1 hour).
|
- Unknown senders receive a pairing code; messages are ignored until approved (codes expire after 1 hour).
|
||||||
- Approve via:
|
- Approve via:
|
||||||
@@ -101,15 +113,18 @@ DMs:
|
|||||||
- UUID-only senders (from `sourceUuid`) are stored as `uuid:<id>` in `channels.signal.allowFrom`.
|
- UUID-only senders (from `sourceUuid`) are stored as `uuid:<id>` in `channels.signal.allowFrom`.
|
||||||
|
|
||||||
Groups:
|
Groups:
|
||||||
|
|
||||||
- `channels.signal.groupPolicy = open | allowlist | disabled`.
|
- `channels.signal.groupPolicy = open | allowlist | disabled`.
|
||||||
- `channels.signal.groupAllowFrom` controls who can trigger in groups when `allowlist` is set.
|
- `channels.signal.groupAllowFrom` controls who can trigger in groups when `allowlist` is set.
|
||||||
|
|
||||||
## How it works (behavior)
|
## How it works (behavior)
|
||||||
|
|
||||||
- `signal-cli` runs as a daemon; the gateway reads events via SSE.
|
- `signal-cli` runs as a daemon; the gateway reads events via SSE.
|
||||||
- Inbound messages are normalized into the shared channel envelope.
|
- Inbound messages are normalized into the shared channel envelope.
|
||||||
- Replies always route back to the same number or group.
|
- Replies always route back to the same number or group.
|
||||||
|
|
||||||
## Media + limits
|
## Media + limits
|
||||||
|
|
||||||
- Outbound text is chunked to `channels.signal.textChunkLimit` (default 4000).
|
- Outbound text is chunked to `channels.signal.textChunkLimit` (default 4000).
|
||||||
- Optional newline chunking: set `channels.signal.chunkMode="newline"` to split on blank lines (paragraph boundaries) before length chunking.
|
- Optional newline chunking: set `channels.signal.chunkMode="newline"` to split on blank lines (paragraph boundaries) before length chunking.
|
||||||
- Attachments supported (base64 fetched from `signal-cli`).
|
- Attachments supported (base64 fetched from `signal-cli`).
|
||||||
@@ -118,17 +133,20 @@ Groups:
|
|||||||
- Group history context uses `channels.signal.historyLimit` (or `channels.signal.accounts.*.historyLimit`), falling back to `messages.groupChat.historyLimit`. Set `0` to disable (default 50).
|
- Group history context uses `channels.signal.historyLimit` (or `channels.signal.accounts.*.historyLimit`), falling back to `messages.groupChat.historyLimit`. Set `0` to disable (default 50).
|
||||||
|
|
||||||
## Typing + read receipts
|
## Typing + read receipts
|
||||||
|
|
||||||
- **Typing indicators**: OpenClaw sends typing signals via `signal-cli sendTyping` and refreshes them while a reply is running.
|
- **Typing indicators**: OpenClaw sends typing signals via `signal-cli sendTyping` and refreshes them while a reply is running.
|
||||||
- **Read receipts**: when `channels.signal.sendReadReceipts` is true, OpenClaw forwards read receipts for allowed DMs.
|
- **Read receipts**: when `channels.signal.sendReadReceipts` is true, OpenClaw forwards read receipts for allowed DMs.
|
||||||
- Signal-cli does not expose read receipts for groups.
|
- Signal-cli does not expose read receipts for groups.
|
||||||
|
|
||||||
## Reactions (message tool)
|
## Reactions (message tool)
|
||||||
|
|
||||||
- Use `message action=react` with `channel=signal`.
|
- Use `message action=react` with `channel=signal`.
|
||||||
- Targets: sender E.164 or UUID (use `uuid:<id>` from pairing output; bare UUID works too).
|
- Targets: sender E.164 or UUID (use `uuid:<id>` from pairing output; bare UUID works too).
|
||||||
- `messageId` is the Signal timestamp for the message you’re reacting to.
|
- `messageId` is the Signal timestamp for the message you’re reacting to.
|
||||||
- Group reactions require `targetAuthor` or `targetAuthorUuid`.
|
- Group reactions require `targetAuthor` or `targetAuthorUuid`.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
```
|
```
|
||||||
message action=react channel=signal target=uuid:123e4567-e89b-12d3-a456-426614174000 messageId=1737630212345 emoji=🔥
|
message action=react channel=signal target=uuid:123e4567-e89b-12d3-a456-426614174000 messageId=1737630212345 emoji=🔥
|
||||||
message action=react channel=signal target=+15551234567 messageId=1737630212345 emoji=🔥 remove=true
|
message action=react channel=signal target=+15551234567 messageId=1737630212345 emoji=🔥 remove=true
|
||||||
@@ -136,6 +154,7 @@ message action=react channel=signal target=signal:group:<groupId> targetAuthor=u
|
|||||||
```
|
```
|
||||||
|
|
||||||
Config:
|
Config:
|
||||||
|
|
||||||
- `channels.signal.actions.reactions`: enable/disable reaction actions (default true).
|
- `channels.signal.actions.reactions`: enable/disable reaction actions (default true).
|
||||||
- `channels.signal.reactionLevel`: `off | ack | minimal | extensive`.
|
- `channels.signal.reactionLevel`: `off | ack | minimal | extensive`.
|
||||||
- `off`/`ack` disables agent reactions (message tool `react` will error).
|
- `off`/`ack` disables agent reactions (message tool `react` will error).
|
||||||
@@ -143,15 +162,18 @@ Config:
|
|||||||
- Per-account overrides: `channels.signal.accounts.<id>.actions.reactions`, `channels.signal.accounts.<id>.reactionLevel`.
|
- Per-account overrides: `channels.signal.accounts.<id>.actions.reactions`, `channels.signal.accounts.<id>.reactionLevel`.
|
||||||
|
|
||||||
## Delivery targets (CLI/cron)
|
## Delivery targets (CLI/cron)
|
||||||
|
|
||||||
- DMs: `signal:+15551234567` (or plain E.164).
|
- DMs: `signal:+15551234567` (or plain E.164).
|
||||||
- UUID DMs: `uuid:<id>` (or bare UUID).
|
- UUID DMs: `uuid:<id>` (or bare UUID).
|
||||||
- Groups: `signal:group:<groupId>`.
|
- Groups: `signal:group:<groupId>`.
|
||||||
- Usernames: `username:<name>` (if supported by your Signal account).
|
- Usernames: `username:<name>` (if supported by your Signal account).
|
||||||
|
|
||||||
## Configuration reference (Signal)
|
## Configuration reference (Signal)
|
||||||
|
|
||||||
Full configuration: [Configuration](/gateway/configuration)
|
Full configuration: [Configuration](/gateway/configuration)
|
||||||
|
|
||||||
Provider options:
|
Provider options:
|
||||||
|
|
||||||
- `channels.signal.enabled`: enable/disable channel startup.
|
- `channels.signal.enabled`: enable/disable channel startup.
|
||||||
- `channels.signal.account`: E.164 for the bot account.
|
- `channels.signal.account`: E.164 for the bot account.
|
||||||
- `channels.signal.cliPath`: path to `signal-cli`.
|
- `channels.signal.cliPath`: path to `signal-cli`.
|
||||||
@@ -174,6 +196,7 @@ Provider options:
|
|||||||
- `channels.signal.mediaMaxMb`: inbound/outbound media cap (MB).
|
- `channels.signal.mediaMaxMb`: inbound/outbound media cap (MB).
|
||||||
|
|
||||||
Related global options:
|
Related global options:
|
||||||
|
|
||||||
- `agents.list[].groupChat.mentionPatterns` (Signal does not support native mentions).
|
- `agents.list[].groupChat.mentionPatterns` (Signal does not support native mentions).
|
||||||
- `messages.groupChat.mentionPatterns` (global fallback).
|
- `messages.groupChat.mentionPatterns` (global fallback).
|
||||||
- `messages.responsePrefix`.
|
- `messages.responsePrefix`.
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
summary: "Slack setup for socket or HTTP webhook mode"
|
summary: "Slack setup for socket or HTTP webhook mode"
|
||||||
read_when: "Setting up Slack or debugging Slack socket/HTTP mode"
|
read_when: "Setting up Slack or debugging Slack socket/HTTP mode"
|
||||||
|
title: "Slack"
|
||||||
---
|
---
|
||||||
|
|
||||||
# Slack
|
# Slack
|
||||||
@@ -8,38 +9,41 @@ read_when: "Setting up Slack or debugging Slack socket/HTTP mode"
|
|||||||
## Socket mode (default)
|
## Socket mode (default)
|
||||||
|
|
||||||
### Quick setup (beginner)
|
### Quick setup (beginner)
|
||||||
1) Create a Slack app and enable **Socket Mode**.
|
|
||||||
2) Create an **App Token** (`xapp-...`) and **Bot Token** (`xoxb-...`).
|
1. Create a Slack app and enable **Socket Mode**.
|
||||||
3) Set tokens for OpenClaw and start the gateway.
|
2. Create an **App Token** (`xapp-...`) and **Bot Token** (`xoxb-...`).
|
||||||
|
3. Set tokens for OpenClaw and start the gateway.
|
||||||
|
|
||||||
Minimal config:
|
Minimal config:
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
channels: {
|
channels: {
|
||||||
slack: {
|
slack: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
appToken: "xapp-...",
|
appToken: "xapp-...",
|
||||||
botToken: "xoxb-..."
|
botToken: "xoxb-...",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Setup
|
### Setup
|
||||||
1) Create a Slack app (From scratch) in https://api.slack.com/apps.
|
|
||||||
2) **Socket Mode** → toggle on. Then go to **Basic Information** → **App-Level Tokens** → **Generate Token and Scopes** with scope `connections:write`. Copy the **App Token** (`xapp-...`).
|
1. Create a Slack app (From scratch) in https://api.slack.com/apps.
|
||||||
3) **OAuth & Permissions** → add bot token scopes (use the manifest below). Click **Install to Workspace**. Copy the **Bot User OAuth Token** (`xoxb-...`).
|
2. **Socket Mode** → toggle on. Then go to **Basic Information** → **App-Level Tokens** → **Generate Token and Scopes** with scope `connections:write`. Copy the **App Token** (`xapp-...`).
|
||||||
4) Optional: **OAuth & Permissions** → add **User Token Scopes** (see the read-only list below). Reinstall the app and copy the **User OAuth Token** (`xoxp-...`).
|
3. **OAuth & Permissions** → add bot token scopes (use the manifest below). Click **Install to Workspace**. Copy the **Bot User OAuth Token** (`xoxb-...`).
|
||||||
5) **Event Subscriptions** → enable events and subscribe to:
|
4. Optional: **OAuth & Permissions** → add **User Token Scopes** (see the read-only list below). Reinstall the app and copy the **User OAuth Token** (`xoxp-...`).
|
||||||
|
5. **Event Subscriptions** → enable events and subscribe to:
|
||||||
- `message.*` (includes edits/deletes/thread broadcasts)
|
- `message.*` (includes edits/deletes/thread broadcasts)
|
||||||
- `app_mention`
|
- `app_mention`
|
||||||
- `reaction_added`, `reaction_removed`
|
- `reaction_added`, `reaction_removed`
|
||||||
- `member_joined_channel`, `member_left_channel`
|
- `member_joined_channel`, `member_left_channel`
|
||||||
- `channel_rename`
|
- `channel_rename`
|
||||||
- `pin_added`, `pin_removed`
|
- `pin_added`, `pin_removed`
|
||||||
6) Invite the bot to channels you want it to read.
|
6. Invite the bot to channels you want it to read.
|
||||||
7) Slash Commands → create `/openclaw` if you use `channels.slack.slashCommand`. If you enable native commands, add one slash command per built-in command (same names as `/help`). Native defaults to off for Slack unless you set `channels.slack.commands.native: true` (global `commands.native` is `"auto"` which leaves Slack off).
|
7. Slash Commands → create `/openclaw` if you use `channels.slack.slashCommand`. If you enable native commands, add one slash command per built-in command (same names as `/help`). Native defaults to off for Slack unless you set `channels.slack.commands.native: true` (global `commands.native` is `"auto"` which leaves Slack off).
|
||||||
8) App Home → enable the **Messages Tab** so users can DM the bot.
|
8. App Home → enable the **Messages Tab** so users can DM the bot.
|
||||||
|
|
||||||
Use the manifest below so scopes and events stay in sync.
|
Use the manifest below so scopes and events stay in sync.
|
||||||
|
|
||||||
@@ -48,6 +52,7 @@ Multi-account support: use `channels.slack.accounts` with per-account tokens and
|
|||||||
### OpenClaw config (minimal)
|
### OpenClaw config (minimal)
|
||||||
|
|
||||||
Set tokens via env vars (recommended):
|
Set tokens via env vars (recommended):
|
||||||
|
|
||||||
- `SLACK_APP_TOKEN=xapp-...`
|
- `SLACK_APP_TOKEN=xapp-...`
|
||||||
- `SLACK_BOT_TOKEN=xoxb-...`
|
- `SLACK_BOT_TOKEN=xoxb-...`
|
||||||
|
|
||||||
@@ -59,13 +64,14 @@ Or via config:
|
|||||||
slack: {
|
slack: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
appToken: "xapp-...",
|
appToken: "xapp-...",
|
||||||
botToken: "xoxb-..."
|
botToken: "xoxb-...",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### User token (optional)
|
### User token (optional)
|
||||||
|
|
||||||
OpenClaw can use a Slack user token (`xoxp-...`) for read operations (history,
|
OpenClaw can use a Slack user token (`xoxp-...`) for read operations (history,
|
||||||
pins, reactions, emoji, member info). By default this stays read-only: reads
|
pins, reactions, emoji, member info). By default this stays read-only: reads
|
||||||
prefer the user token when present, and writes still use the bot token unless
|
prefer the user token when present, and writes still use the bot token unless
|
||||||
@@ -76,20 +82,7 @@ User tokens are configured in the config file (no env var support). For
|
|||||||
multi-account, set `channels.slack.accounts.<id>.userToken`.
|
multi-account, set `channels.slack.accounts.<id>.userToken`.
|
||||||
|
|
||||||
Example with bot + app + user tokens:
|
Example with bot + app + user tokens:
|
||||||
```json5
|
|
||||||
{
|
|
||||||
channels: {
|
|
||||||
slack: {
|
|
||||||
enabled: true,
|
|
||||||
appToken: "xapp-...",
|
|
||||||
botToken: "xoxb-...",
|
|
||||||
userToken: "xoxp-..."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Example with userTokenReadOnly explicitly set (allow user token writes):
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
channels: {
|
channels: {
|
||||||
@@ -98,13 +91,29 @@ Example with userTokenReadOnly explicitly set (allow user token writes):
|
|||||||
appToken: "xapp-...",
|
appToken: "xapp-...",
|
||||||
botToken: "xoxb-...",
|
botToken: "xoxb-...",
|
||||||
userToken: "xoxp-...",
|
userToken: "xoxp-...",
|
||||||
userTokenReadOnly: false
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Example with userTokenReadOnly explicitly set (allow user token writes):
|
||||||
|
|
||||||
|
```json5
|
||||||
|
{
|
||||||
|
channels: {
|
||||||
|
slack: {
|
||||||
|
enabled: true,
|
||||||
|
appToken: "xapp-...",
|
||||||
|
botToken: "xoxb-...",
|
||||||
|
userToken: "xoxp-...",
|
||||||
|
userTokenReadOnly: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Token usage
|
#### Token usage
|
||||||
|
|
||||||
- Read operations (history, reactions list, pins list, emoji list, member info,
|
- Read operations (history, reactions list, pins list, emoji list, member info,
|
||||||
search) prefer the user token when configured, otherwise the bot token.
|
search) prefer the user token when configured, otherwise the bot token.
|
||||||
- Write operations (send/edit/delete messages, add/remove reactions, pin/unpin,
|
- Write operations (send/edit/delete messages, add/remove reactions, pin/unpin,
|
||||||
@@ -112,25 +121,29 @@ Example with userTokenReadOnly explicitly set (allow user token writes):
|
|||||||
no bot token is available, OpenClaw falls back to the user token.
|
no bot token is available, OpenClaw falls back to the user token.
|
||||||
|
|
||||||
### History context
|
### History context
|
||||||
|
|
||||||
- `channels.slack.historyLimit` (or `channels.slack.accounts.*.historyLimit`) controls how many recent channel/group messages are wrapped into the prompt.
|
- `channels.slack.historyLimit` (or `channels.slack.accounts.*.historyLimit`) controls how many recent channel/group messages are wrapped into the prompt.
|
||||||
- Falls back to `messages.groupChat.historyLimit`. Set `0` to disable (default 50).
|
- Falls back to `messages.groupChat.historyLimit`. Set `0` to disable (default 50).
|
||||||
|
|
||||||
## HTTP mode (Events API)
|
## HTTP mode (Events API)
|
||||||
|
|
||||||
Use HTTP webhook mode when your Gateway is reachable by Slack over HTTPS (typical for server deployments).
|
Use HTTP webhook mode when your Gateway is reachable by Slack over HTTPS (typical for server deployments).
|
||||||
HTTP mode uses the Events API + Interactivity + Slash Commands with a shared request URL.
|
HTTP mode uses the Events API + Interactivity + Slash Commands with a shared request URL.
|
||||||
|
|
||||||
### Setup
|
### Setup
|
||||||
1) Create a Slack app and **disable Socket Mode** (optional if you only use HTTP).
|
|
||||||
2) **Basic Information** → copy the **Signing Secret**.
|
1. Create a Slack app and **disable Socket Mode** (optional if you only use HTTP).
|
||||||
3) **OAuth & Permissions** → install the app and copy the **Bot User OAuth Token** (`xoxb-...`).
|
2. **Basic Information** → copy the **Signing Secret**.
|
||||||
4) **Event Subscriptions** → enable events and set the **Request URL** to your gateway webhook path (default `/slack/events`).
|
3. **OAuth & Permissions** → install the app and copy the **Bot User OAuth Token** (`xoxb-...`).
|
||||||
5) **Interactivity & Shortcuts** → enable and set the same **Request URL**.
|
4. **Event Subscriptions** → enable events and set the **Request URL** to your gateway webhook path (default `/slack/events`).
|
||||||
6) **Slash Commands** → set the same **Request URL** for your command(s).
|
5. **Interactivity & Shortcuts** → enable and set the same **Request URL**.
|
||||||
|
6. **Slash Commands** → set the same **Request URL** for your command(s).
|
||||||
|
|
||||||
Example request URL:
|
Example request URL:
|
||||||
`https://gateway-host/slack/events`
|
`https://gateway-host/slack/events`
|
||||||
|
|
||||||
### OpenClaw config (minimal)
|
### OpenClaw config (minimal)
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
channels: {
|
channels: {
|
||||||
@@ -139,9 +152,9 @@ Example request URL:
|
|||||||
mode: "http",
|
mode: "http",
|
||||||
botToken: "xoxb-...",
|
botToken: "xoxb-...",
|
||||||
signingSecret: "your-signing-secret",
|
signingSecret: "your-signing-secret",
|
||||||
webhookPath: "/slack/events"
|
webhookPath: "/slack/events",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -149,6 +162,7 @@ Multi-account HTTP mode: set `channels.slack.accounts.<id>.mode = "http"` and pr
|
|||||||
`webhookPath` per account so each Slack app can point to its own URL.
|
`webhookPath` per account so each Slack app can point to its own URL.
|
||||||
|
|
||||||
### Manifest (optional)
|
### Manifest (optional)
|
||||||
|
|
||||||
Use this Slack app manifest to create the app quickly (adjust the name/command if you want). Include the
|
Use this Slack app manifest to create the app quickly (adjust the name/command if you want). Include the
|
||||||
user scopes if you plan to configure a user token.
|
user scopes if you plan to configure a user token.
|
||||||
|
|
||||||
@@ -243,11 +257,13 @@ user scopes if you plan to configure a user token.
|
|||||||
If you enable native commands, add one `slash_commands` entry per command you want to expose (matching the `/help` list). Override with `channels.slack.commands.native`.
|
If you enable native commands, add one `slash_commands` entry per command you want to expose (matching the `/help` list). Override with `channels.slack.commands.native`.
|
||||||
|
|
||||||
## Scopes (current vs optional)
|
## Scopes (current vs optional)
|
||||||
|
|
||||||
Slack's Conversations API is type-scoped: you only need the scopes for the
|
Slack's Conversations API is type-scoped: you only need the scopes for the
|
||||||
conversation types you actually touch (channels, groups, im, mpim). See
|
conversation types you actually touch (channels, groups, im, mpim). See
|
||||||
https://docs.slack.dev/apis/web-api/using-the-conversations-api/ for the overview.
|
https://docs.slack.dev/apis/web-api/using-the-conversations-api/ for the overview.
|
||||||
|
|
||||||
### Bot token scopes (required)
|
### Bot token scopes (required)
|
||||||
|
|
||||||
- `chat:write` (send/update/delete messages via `chat.postMessage`)
|
- `chat:write` (send/update/delete messages via `chat.postMessage`)
|
||||||
https://docs.slack.dev/reference/methods/chat.postMessage
|
https://docs.slack.dev/reference/methods/chat.postMessage
|
||||||
- `im:write` (open DMs via `conversations.open` for user DMs)
|
- `im:write` (open DMs via `conversations.open` for user DMs)
|
||||||
@@ -270,6 +286,7 @@ https://docs.slack.dev/apis/web-api/using-the-conversations-api/ for the overvie
|
|||||||
https://docs.slack.dev/messaging/working-with-files/#upload
|
https://docs.slack.dev/messaging/working-with-files/#upload
|
||||||
|
|
||||||
### User token scopes (optional, read-only by default)
|
### User token scopes (optional, read-only by default)
|
||||||
|
|
||||||
Add these under **User Token Scopes** if you configure `channels.slack.userToken`.
|
Add these under **User Token Scopes** if you configure `channels.slack.userToken`.
|
||||||
|
|
||||||
- `channels:history`, `groups:history`, `im:history`, `mpim:history`
|
- `channels:history`, `groups:history`, `im:history`, `mpim:history`
|
||||||
@@ -281,6 +298,7 @@ Add these under **User Token Scopes** if you configure `channels.slack.userToken
|
|||||||
- `search:read`
|
- `search:read`
|
||||||
|
|
||||||
### Not needed today (but likely future)
|
### Not needed today (but likely future)
|
||||||
|
|
||||||
- `mpim:write` (only if we add group-DM open/DM start via `conversations.open`)
|
- `mpim:write` (only if we add group-DM open/DM start via `conversations.open`)
|
||||||
- `groups:write` (only if we add private-channel management: create/rename/invite/archive)
|
- `groups:write` (only if we add private-channel management: create/rename/invite/archive)
|
||||||
- `chat:write.public` (only if we want to post to channels the bot isn't in)
|
- `chat:write.public` (only if we want to post to channels the bot isn't in)
|
||||||
@@ -290,6 +308,7 @@ Add these under **User Token Scopes** if you configure `channels.slack.userToken
|
|||||||
- `files:read` (only if we start listing/reading file metadata)
|
- `files:read` (only if we start listing/reading file metadata)
|
||||||
|
|
||||||
## Config
|
## Config
|
||||||
|
|
||||||
Slack uses Socket Mode only (no HTTP webhook server). Provide both tokens:
|
Slack uses Socket Mode only (no HTTP webhook server). Provide both tokens:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
@@ -340,6 +359,7 @@ Slack uses Socket Mode only (no HTTP webhook server). Provide both tokens:
|
|||||||
```
|
```
|
||||||
|
|
||||||
Tokens can also be supplied via env vars:
|
Tokens can also be supplied via env vars:
|
||||||
|
|
||||||
- `SLACK_BOT_TOKEN`
|
- `SLACK_BOT_TOKEN`
|
||||||
- `SLACK_APP_TOKEN`
|
- `SLACK_APP_TOKEN`
|
||||||
|
|
||||||
@@ -348,94 +368,105 @@ Ack reactions are controlled globally via `messages.ackReaction` +
|
|||||||
ack reaction after the bot replies.
|
ack reaction after the bot replies.
|
||||||
|
|
||||||
## Limits
|
## Limits
|
||||||
|
|
||||||
- Outbound text is chunked to `channels.slack.textChunkLimit` (default 4000).
|
- Outbound text is chunked to `channels.slack.textChunkLimit` (default 4000).
|
||||||
- Optional newline chunking: set `channels.slack.chunkMode="newline"` to split on blank lines (paragraph boundaries) before length chunking.
|
- Optional newline chunking: set `channels.slack.chunkMode="newline"` to split on blank lines (paragraph boundaries) before length chunking.
|
||||||
- Media uploads are capped by `channels.slack.mediaMaxMb` (default 20).
|
- Media uploads are capped by `channels.slack.mediaMaxMb` (default 20).
|
||||||
|
|
||||||
## Reply threading
|
## Reply threading
|
||||||
|
|
||||||
By default, OpenClaw replies in the main channel. Use `channels.slack.replyToMode` to control automatic threading:
|
By default, OpenClaw replies in the main channel. Use `channels.slack.replyToMode` to control automatic threading:
|
||||||
|
|
||||||
| Mode | Behavior |
|
| Mode | Behavior |
|
||||||
| --- | --- |
|
| ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `off` | **Default.** Reply in main channel. Only thread if the triggering message was already in a thread. |
|
| `off` | **Default.** Reply in main channel. Only thread if the triggering message was already in a thread. |
|
||||||
| `first` | First reply goes to thread (under the triggering message), subsequent replies go to main channel. Useful for keeping context visible while avoiding thread clutter. |
|
| `first` | First reply goes to thread (under the triggering message), subsequent replies go to main channel. Useful for keeping context visible while avoiding thread clutter. |
|
||||||
| `all` | All replies go to thread. Keeps conversations contained but may reduce visibility. |
|
| `all` | All replies go to thread. Keeps conversations contained but may reduce visibility. |
|
||||||
|
|
||||||
The mode applies to both auto-replies and agent tool calls (`slack sendMessage`).
|
The mode applies to both auto-replies and agent tool calls (`slack sendMessage`).
|
||||||
|
|
||||||
### Per-chat-type threading
|
### Per-chat-type threading
|
||||||
|
|
||||||
You can configure different threading behavior per chat type by setting `channels.slack.replyToModeByChatType`:
|
You can configure different threading behavior per chat type by setting `channels.slack.replyToModeByChatType`:
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
channels: {
|
channels: {
|
||||||
slack: {
|
slack: {
|
||||||
replyToMode: "off", // default for channels
|
replyToMode: "off", // default for channels
|
||||||
replyToModeByChatType: {
|
replyToModeByChatType: {
|
||||||
direct: "all", // DMs always thread
|
direct: "all", // DMs always thread
|
||||||
group: "first" // group DMs/MPIM thread first reply
|
group: "first", // group DMs/MPIM thread first reply
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Supported chat types:
|
Supported chat types:
|
||||||
|
|
||||||
- `direct`: 1:1 DMs (Slack `im`)
|
- `direct`: 1:1 DMs (Slack `im`)
|
||||||
- `group`: group DMs / MPIMs (Slack `mpim`)
|
- `group`: group DMs / MPIMs (Slack `mpim`)
|
||||||
- `channel`: standard channels (public/private)
|
- `channel`: standard channels (public/private)
|
||||||
|
|
||||||
Precedence:
|
Precedence:
|
||||||
1) `replyToModeByChatType.<chatType>`
|
|
||||||
2) `replyToMode`
|
1. `replyToModeByChatType.<chatType>`
|
||||||
3) Provider default (`off`)
|
2. `replyToMode`
|
||||||
|
3. Provider default (`off`)
|
||||||
|
|
||||||
Legacy `channels.slack.dm.replyToMode` is still accepted as a fallback for `direct` when no chat-type override is set.
|
Legacy `channels.slack.dm.replyToMode` is still accepted as a fallback for `direct` when no chat-type override is set.
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
Thread DMs only:
|
Thread DMs only:
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
channels: {
|
channels: {
|
||||||
slack: {
|
slack: {
|
||||||
replyToMode: "off",
|
replyToMode: "off",
|
||||||
replyToModeByChatType: { direct: "all" }
|
replyToModeByChatType: { direct: "all" },
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Thread group DMs but keep channels in the root:
|
Thread group DMs but keep channels in the root:
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
channels: {
|
channels: {
|
||||||
slack: {
|
slack: {
|
||||||
replyToMode: "off",
|
replyToMode: "off",
|
||||||
replyToModeByChatType: { group: "first" }
|
replyToModeByChatType: { group: "first" },
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Make channels thread, keep DMs in the root:
|
Make channels thread, keep DMs in the root:
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
channels: {
|
channels: {
|
||||||
slack: {
|
slack: {
|
||||||
replyToMode: "first",
|
replyToMode: "first",
|
||||||
replyToModeByChatType: { direct: "off", group: "off" }
|
replyToModeByChatType: { direct: "off", group: "off" },
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Manual threading tags
|
### Manual threading tags
|
||||||
|
|
||||||
For fine-grained control, use these tags in agent responses:
|
For fine-grained control, use these tags in agent responses:
|
||||||
|
|
||||||
- `[[reply_to_current]]` — reply to the triggering message (start/continue thread).
|
- `[[reply_to_current]]` — reply to the triggering message (start/continue thread).
|
||||||
- `[[reply_to:<id>]]` — reply to a specific message id.
|
- `[[reply_to:<id>]]` — reply to a specific message id.
|
||||||
|
|
||||||
## Sessions + routing
|
## Sessions + routing
|
||||||
|
|
||||||
- DMs share the `main` session (like WhatsApp/Telegram).
|
- DMs share the `main` session (like WhatsApp/Telegram).
|
||||||
- Channels map to `agent:<agentId>:slack:channel:<channelId>` sessions.
|
- Channels map to `agent:<agentId>:slack:channel:<channelId>` sessions.
|
||||||
- Slash commands use `agent:<agentId>:slack:slash:<userId>` sessions (prefix configurable via `channels.slack.slashCommand.sessionPrefix`).
|
- Slash commands use `agent:<agentId>:slack:slash:<userId>` sessions (prefix configurable via `channels.slack.slashCommand.sessionPrefix`).
|
||||||
@@ -444,24 +475,27 @@ For fine-grained control, use these tags in agent responses:
|
|||||||
- Full command list + config: [Slash commands](/tools/slash-commands)
|
- Full command list + config: [Slash commands](/tools/slash-commands)
|
||||||
|
|
||||||
## DM security (pairing)
|
## DM security (pairing)
|
||||||
|
|
||||||
- Default: `channels.slack.dm.policy="pairing"` — unknown DM senders get a pairing code (expires after 1 hour).
|
- Default: `channels.slack.dm.policy="pairing"` — unknown DM senders get a pairing code (expires after 1 hour).
|
||||||
- Approve via: `openclaw pairing approve slack <code>`.
|
- Approve via: `openclaw pairing approve slack <code>`.
|
||||||
- To allow anyone: set `channels.slack.dm.policy="open"` and `channels.slack.dm.allowFrom=["*"]`.
|
- To allow anyone: set `channels.slack.dm.policy="open"` and `channels.slack.dm.allowFrom=["*"]`.
|
||||||
- `channels.slack.dm.allowFrom` accepts user IDs, @handles, or emails (resolved at startup when tokens allow). The wizard accepts usernames and resolves them to ids during setup when tokens allow.
|
- `channels.slack.dm.allowFrom` accepts user IDs, @handles, or emails (resolved at startup when tokens allow). The wizard accepts usernames and resolves them to ids during setup when tokens allow.
|
||||||
|
|
||||||
## Group policy
|
## Group policy
|
||||||
|
|
||||||
- `channels.slack.groupPolicy` controls channel handling (`open|disabled|allowlist`).
|
- `channels.slack.groupPolicy` controls channel handling (`open|disabled|allowlist`).
|
||||||
- `allowlist` requires channels to be listed in `channels.slack.channels`.
|
- `allowlist` requires channels to be listed in `channels.slack.channels`.
|
||||||
- If you only set `SLACK_BOT_TOKEN`/`SLACK_APP_TOKEN` and never create a `channels.slack` section,
|
- If you only set `SLACK_BOT_TOKEN`/`SLACK_APP_TOKEN` and never create a `channels.slack` section,
|
||||||
the runtime defaults `groupPolicy` to `open`. Add `channels.slack.groupPolicy`,
|
the runtime defaults `groupPolicy` to `open`. Add `channels.slack.groupPolicy`,
|
||||||
`channels.defaults.groupPolicy`, or a channel allowlist to lock it down.
|
`channels.defaults.groupPolicy`, or a channel allowlist to lock it down.
|
||||||
- The configure wizard accepts `#channel` names and resolves them to IDs when possible
|
- The configure wizard accepts `#channel` names and resolves them to IDs when possible
|
||||||
(public + private); if multiple matches exist, it prefers the active channel.
|
(public + private); if multiple matches exist, it prefers the active channel.
|
||||||
- On startup, OpenClaw resolves channel/user names in allowlists to IDs (when tokens allow)
|
- On startup, OpenClaw resolves channel/user names in allowlists to IDs (when tokens allow)
|
||||||
and logs the mapping; unresolved entries are kept as typed.
|
and logs the mapping; unresolved entries are kept as typed.
|
||||||
- To allow **no channels**, set `channels.slack.groupPolicy: "disabled"` (or keep an empty allowlist).
|
- To allow **no channels**, set `channels.slack.groupPolicy: "disabled"` (or keep an empty allowlist).
|
||||||
|
|
||||||
Channel options (`channels.slack.channels.<id>` or `channels.slack.channels.<name>`):
|
Channel options (`channels.slack.channels.<id>` or `channels.slack.channels.<name>`):
|
||||||
|
|
||||||
- `allow`: allow/deny the channel when `groupPolicy="allowlist"`.
|
- `allow`: allow/deny the channel when `groupPolicy="allowlist"`.
|
||||||
- `requireMention`: mention gating for the channel.
|
- `requireMention`: mention gating for the channel.
|
||||||
- `tools`: optional per-channel tool policy overrides (`allow`/`deny`/`alsoAllow`).
|
- `tools`: optional per-channel tool policy overrides (`allow`/`deny`/`alsoAllow`).
|
||||||
@@ -473,22 +507,26 @@ Channel options (`channels.slack.channels.<id>` or `channels.slack.channels.<nam
|
|||||||
- `enabled`: set `false` to disable the channel.
|
- `enabled`: set `false` to disable the channel.
|
||||||
|
|
||||||
## Delivery targets
|
## Delivery targets
|
||||||
|
|
||||||
Use these with cron/CLI sends:
|
Use these with cron/CLI sends:
|
||||||
|
|
||||||
- `user:<id>` for DMs
|
- `user:<id>` for DMs
|
||||||
- `channel:<id>` for channels
|
- `channel:<id>` for channels
|
||||||
|
|
||||||
## Tool actions
|
## Tool actions
|
||||||
|
|
||||||
Slack tool actions can be gated with `channels.slack.actions.*`:
|
Slack tool actions can be gated with `channels.slack.actions.*`:
|
||||||
|
|
||||||
| Action group | Default | Notes |
|
| Action group | Default | Notes |
|
||||||
| --- | --- | --- |
|
| ------------ | ------- | ---------------------- |
|
||||||
| reactions | enabled | React + list reactions |
|
| reactions | enabled | React + list reactions |
|
||||||
| messages | enabled | Read/send/edit/delete |
|
| messages | enabled | Read/send/edit/delete |
|
||||||
| pins | enabled | Pin/unpin/list |
|
| pins | enabled | Pin/unpin/list |
|
||||||
| memberInfo | enabled | Member info |
|
| memberInfo | enabled | Member info |
|
||||||
| emojiList | enabled | Custom emoji list |
|
| emojiList | enabled | Custom emoji list |
|
||||||
|
|
||||||
## Security notes
|
## Security notes
|
||||||
|
|
||||||
- Writes default to the bot token so state-changing actions stay scoped to the
|
- Writes default to the bot token so state-changing actions stay scoped to the
|
||||||
app's bot permissions and identity.
|
app's bot permissions and identity.
|
||||||
- Setting `userTokenReadOnly: false` allows the user token to be used for write
|
- Setting `userTokenReadOnly: false` allows the user token to be used for write
|
||||||
@@ -500,6 +538,7 @@ Slack tool actions can be gated with `channels.slack.actions.*`:
|
|||||||
`files:write`) or those operations will fail.
|
`files:write`) or those operations will fail.
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- Mention gating is controlled via `channels.slack.channels` (set `requireMention` to `true`); `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`) also count as mentions.
|
- Mention gating is controlled via `channels.slack.channels` (set `requireMention` to `true`); `agents.list[].groupChat.mentionPatterns` (or `messages.groupChat.mentionPatterns`) also count as mentions.
|
||||||
- Multi-agent override: set per-agent patterns on `agents.list[].groupChat.mentionPatterns`.
|
- Multi-agent override: set per-agent patterns on `agents.list[].groupChat.mentionPatterns`.
|
||||||
- Reaction notifications follow `channels.slack.reactionNotifications` (use `reactionAllowlist` with mode `allowlist`).
|
- Reaction notifications follow `channels.slack.reactionNotifications` (use `reactionAllowlist` with mode `allowlist`).
|
||||||
|
|||||||
@@ -2,50 +2,58 @@
|
|||||||
summary: "Telegram bot support status, capabilities, and configuration"
|
summary: "Telegram bot support status, capabilities, and configuration"
|
||||||
read_when:
|
read_when:
|
||||||
- Working on Telegram features or webhooks
|
- Working on Telegram features or webhooks
|
||||||
|
title: "Telegram"
|
||||||
---
|
---
|
||||||
# Telegram (Bot API)
|
|
||||||
|
|
||||||
|
# Telegram (Bot API)
|
||||||
|
|
||||||
Status: production-ready for bot DMs + groups via grammY. Long-polling by default; webhook optional.
|
Status: production-ready for bot DMs + groups via grammY. Long-polling by default; webhook optional.
|
||||||
|
|
||||||
## Quick setup (beginner)
|
## Quick setup (beginner)
|
||||||
1) Create a bot with **@BotFather** and copy the token.
|
|
||||||
2) Set the token:
|
1. Create a bot with **@BotFather** ([direct link](https://t.me/BotFather)). Confirm the handle is exactly `@BotFather`, then copy the token.
|
||||||
|
2. Set the token:
|
||||||
- Env: `TELEGRAM_BOT_TOKEN=...`
|
- Env: `TELEGRAM_BOT_TOKEN=...`
|
||||||
- Or config: `channels.telegram.botToken: "..."`.
|
- Or config: `channels.telegram.botToken: "..."`.
|
||||||
- If both are set, config takes precedence (env fallback is default-account only).
|
- If both are set, config takes precedence (env fallback is default-account only).
|
||||||
3) Start the gateway.
|
3. Start the gateway.
|
||||||
4) DM access is pairing by default; approve the pairing code on first contact.
|
4. DM access is pairing by default; approve the pairing code on first contact.
|
||||||
|
|
||||||
Minimal config:
|
Minimal config:
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
channels: {
|
channels: {
|
||||||
telegram: {
|
telegram: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
botToken: "123:abc",
|
botToken: "123:abc",
|
||||||
dmPolicy: "pairing"
|
dmPolicy: "pairing",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## What it is
|
## What it is
|
||||||
|
|
||||||
- A Telegram Bot API channel owned by the Gateway.
|
- A Telegram Bot API channel owned by the Gateway.
|
||||||
- Deterministic routing: replies go back to Telegram; the model never chooses channels.
|
- Deterministic routing: replies go back to Telegram; the model never chooses channels.
|
||||||
- DMs share the agent's main session; groups stay isolated (`agent:<agentId>:telegram:group:<chatId>`).
|
- DMs share the agent's main session; groups stay isolated (`agent:<agentId>:telegram:group:<chatId>`).
|
||||||
|
|
||||||
## Setup (fast path)
|
## Setup (fast path)
|
||||||
|
|
||||||
### 1) Create a bot token (BotFather)
|
### 1) Create a bot token (BotFather)
|
||||||
1) Open Telegram and chat with **@BotFather**.
|
|
||||||
2) Run `/newbot`, then follow the prompts (name + username ending in `bot`).
|
1. Open Telegram and chat with **@BotFather** ([direct link](https://t.me/BotFather)). Confirm the handle is exactly `@BotFather`.
|
||||||
3) Copy the token and store it safely.
|
2. Run `/newbot`, then follow the prompts (name + username ending in `bot`).
|
||||||
|
3. Copy the token and store it safely.
|
||||||
|
|
||||||
Optional BotFather settings:
|
Optional BotFather settings:
|
||||||
|
|
||||||
- `/setjoingroups` — allow/deny adding the bot to groups.
|
- `/setjoingroups` — allow/deny adding the bot to groups.
|
||||||
- `/setprivacy` — control whether the bot sees all group messages.
|
- `/setprivacy` — control whether the bot sees all group messages.
|
||||||
|
|
||||||
### 2) Configure the token (env or config)
|
### 2) Configure the token (env or config)
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
@@ -55,9 +63,9 @@ Example:
|
|||||||
enabled: true,
|
enabled: true,
|
||||||
botToken: "123:abc",
|
botToken: "123:abc",
|
||||||
dmPolicy: "pairing",
|
dmPolicy: "pairing",
|
||||||
groups: { "*": { requireMention: true } }
|
groups: { "*": { requireMention: true } },
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -66,19 +74,22 @@ If both env and config are set, config takes precedence.
|
|||||||
|
|
||||||
Multi-account support: use `channels.telegram.accounts` with per-account tokens and optional `name`. See [`gateway/configuration`](/gateway/configuration#telegramaccounts--discordaccounts--slackaccounts--signalaccounts--imessageaccounts) for the shared pattern.
|
Multi-account support: use `channels.telegram.accounts` with per-account tokens and optional `name`. See [`gateway/configuration`](/gateway/configuration#telegramaccounts--discordaccounts--slackaccounts--signalaccounts--imessageaccounts) for the shared pattern.
|
||||||
|
|
||||||
3) Start the gateway. Telegram starts when a token is resolved (config first, env fallback).
|
3. Start the gateway. Telegram starts when a token is resolved (config first, env fallback).
|
||||||
4) DM access defaults to pairing. Approve the code when the bot is first contacted.
|
4. DM access defaults to pairing. Approve the code when the bot is first contacted.
|
||||||
5) For groups: add the bot, decide privacy/admin behavior (below), then set `channels.telegram.groups` to control mention gating + allowlists.
|
5. For groups: add the bot, decide privacy/admin behavior (below), then set `channels.telegram.groups` to control mention gating + allowlists.
|
||||||
|
|
||||||
## Token + privacy + permissions (Telegram side)
|
## Token + privacy + permissions (Telegram side)
|
||||||
|
|
||||||
### Token creation (BotFather)
|
### Token creation (BotFather)
|
||||||
|
|
||||||
- `/newbot` creates the bot and returns the token (keep it secret).
|
- `/newbot` creates the bot and returns the token (keep it secret).
|
||||||
- If a token leaks, revoke/regenerate it via @BotFather and update your config.
|
- If a token leaks, revoke/regenerate it via @BotFather and update your config.
|
||||||
|
|
||||||
### Group message visibility (Privacy Mode)
|
### Group message visibility (Privacy Mode)
|
||||||
|
|
||||||
Telegram bots default to **Privacy Mode**, which limits which group messages they receive.
|
Telegram bots default to **Privacy Mode**, which limits which group messages they receive.
|
||||||
If your bot must see *all* group messages, you have two options:
|
If your bot must see _all_ group messages, you have two options:
|
||||||
|
|
||||||
- Disable privacy mode with `/setprivacy` **or**
|
- Disable privacy mode with `/setprivacy` **or**
|
||||||
- Add the bot as a group **admin** (admin bots receive all messages).
|
- Add the bot as a group **admin** (admin bots receive all messages).
|
||||||
|
|
||||||
@@ -86,10 +97,12 @@ If your bot must see *all* group messages, you have two options:
|
|||||||
to each group for the change to take effect.
|
to each group for the change to take effect.
|
||||||
|
|
||||||
### Group permissions (admin rights)
|
### Group permissions (admin rights)
|
||||||
|
|
||||||
Admin status is set inside the group (Telegram UI). Admin bots always receive all
|
Admin status is set inside the group (Telegram UI). Admin bots always receive all
|
||||||
group messages, so use admin if you need full visibility.
|
group messages, so use admin if you need full visibility.
|
||||||
|
|
||||||
## How it works (behavior)
|
## How it works (behavior)
|
||||||
|
|
||||||
- Inbound messages are normalized into the shared channel envelope with reply context and media placeholders.
|
- Inbound messages are normalized into the shared channel envelope with reply context and media placeholders.
|
||||||
- Group replies require a mention by default (native @mention or `agents.list[].groupChat.mentionPatterns` / `messages.groupChat.mentionPatterns`).
|
- Group replies require a mention by default (native @mention or `agents.list[].groupChat.mentionPatterns` / `messages.groupChat.mentionPatterns`).
|
||||||
- Multi-agent override: set per-agent patterns on `agents.list[].groupChat.mentionPatterns`.
|
- Multi-agent override: set per-agent patterns on `agents.list[].groupChat.mentionPatterns`.
|
||||||
@@ -97,13 +110,27 @@ group messages, so use admin if you need full visibility.
|
|||||||
- Long-polling uses grammY runner with per-chat sequencing; overall concurrency is capped by `agents.defaults.maxConcurrent`.
|
- Long-polling uses grammY runner with per-chat sequencing; overall concurrency is capped by `agents.defaults.maxConcurrent`.
|
||||||
- Telegram Bot API does not support read receipts; there is no `sendReadReceipts` option.
|
- Telegram Bot API does not support read receipts; there is no `sendReadReceipts` option.
|
||||||
|
|
||||||
|
## Draft streaming
|
||||||
|
|
||||||
|
OpenClaw can stream partial replies in Telegram DMs using `sendMessageDraft`.
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
|
||||||
|
- Threaded Mode enabled for the bot in @BotFather (forum topic mode).
|
||||||
|
- Private chat threads only (Telegram includes `message_thread_id` on inbound messages).
|
||||||
|
- `channels.telegram.streamMode` not set to `"off"` (default: `"partial"`, `"block"` enables chunked draft updates).
|
||||||
|
|
||||||
|
Draft streaming is DM-only; Telegram does not support it in groups or channels.
|
||||||
|
|
||||||
## Formatting (Telegram HTML)
|
## Formatting (Telegram HTML)
|
||||||
|
|
||||||
- Outbound Telegram text uses `parse_mode: "HTML"` (Telegram’s supported tag subset).
|
- Outbound Telegram text uses `parse_mode: "HTML"` (Telegram’s supported tag subset).
|
||||||
- Markdown-ish input is rendered into **Telegram-safe HTML** (bold/italic/strike/code/links); block elements are flattened to text with newlines/bullets.
|
- Markdown-ish input is rendered into **Telegram-safe HTML** (bold/italic/strike/code/links); block elements are flattened to text with newlines/bullets.
|
||||||
- Raw HTML from models is escaped to avoid Telegram parse errors.
|
- Raw HTML from models is escaped to avoid Telegram parse errors.
|
||||||
- If Telegram rejects the HTML payload, OpenClaw retries the same message as plain text.
|
- If Telegram rejects the HTML payload, OpenClaw retries the same message as plain text.
|
||||||
|
|
||||||
## Commands (native + custom)
|
## Commands (native + custom)
|
||||||
|
|
||||||
OpenClaw registers native commands (like `/status`, `/reset`, `/model`) with Telegram’s bot menu on startup.
|
OpenClaw registers native commands (like `/status`, `/reset`, `/model`) with Telegram’s bot menu on startup.
|
||||||
You can add custom commands to the menu via config:
|
You can add custom commands to the menu via config:
|
||||||
|
|
||||||
@@ -113,10 +140,10 @@ You can add custom commands to the menu via config:
|
|||||||
telegram: {
|
telegram: {
|
||||||
customCommands: [
|
customCommands: [
|
||||||
{ command: "backup", description: "Git backup" },
|
{ command: "backup", description: "Git backup" },
|
||||||
{ command: "generate", description: "Create an image" }
|
{ command: "generate", description: "Create an image" },
|
||||||
]
|
],
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -128,12 +155,14 @@ You can add custom commands to the menu via config:
|
|||||||
More help: [Channel troubleshooting](/channels/troubleshooting).
|
More help: [Channel troubleshooting](/channels/troubleshooting).
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
- Custom commands are **menu entries only**; OpenClaw does not implement them unless you handle them elsewhere.
|
- Custom commands are **menu entries only**; OpenClaw does not implement them unless you handle them elsewhere.
|
||||||
- Command names are normalized (leading `/` stripped, lowercased) and must match `a-z`, `0-9`, `_` (1–32 chars).
|
- Command names are normalized (leading `/` stripped, lowercased) and must match `a-z`, `0-9`, `_` (1–32 chars).
|
||||||
- Custom commands **cannot override native commands**. Conflicts are ignored and logged.
|
- Custom commands **cannot override native commands**. Conflicts are ignored and logged.
|
||||||
- If `commands.native` is disabled, only custom commands are registered (or cleared if none).
|
- If `commands.native` is disabled, only custom commands are registered (or cleared if none).
|
||||||
|
|
||||||
## Limits
|
## Limits
|
||||||
|
|
||||||
- Outbound text is chunked to `channels.telegram.textChunkLimit` (default 4000).
|
- Outbound text is chunked to `channels.telegram.textChunkLimit` (default 4000).
|
||||||
- Optional newline chunking: set `channels.telegram.chunkMode="newline"` to split on blank lines (paragraph boundaries) before length chunking.
|
- Optional newline chunking: set `channels.telegram.chunkMode="newline"` to split on blank lines (paragraph boundaries) before length chunking.
|
||||||
- Media downloads/uploads are capped by `channels.telegram.mediaMaxMb` (default 5).
|
- Media downloads/uploads are capped by `channels.telegram.mediaMaxMb` (default 5).
|
||||||
@@ -152,10 +181,10 @@ By default, the bot only responds to mentions in groups (`@botname` or patterns
|
|||||||
channels: {
|
channels: {
|
||||||
telegram: {
|
telegram: {
|
||||||
groups: {
|
groups: {
|
||||||
"-1001234567890": { requireMention: false } // always respond in this group
|
"-1001234567890": { requireMention: false }, // always respond in this group
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -163,34 +192,37 @@ By default, the bot only responds to mentions in groups (`@botname` or patterns
|
|||||||
Forum topics inherit their parent group config (allowFrom, requireMention, skills, prompts) unless you add per-topic overrides under `channels.telegram.groups.<groupId>.topics.<topicId>`.
|
Forum topics inherit their parent group config (allowFrom, requireMention, skills, prompts) unless you add per-topic overrides under `channels.telegram.groups.<groupId>.topics.<topicId>`.
|
||||||
|
|
||||||
To allow all groups with always-respond:
|
To allow all groups with always-respond:
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
channels: {
|
channels: {
|
||||||
telegram: {
|
telegram: {
|
||||||
groups: {
|
groups: {
|
||||||
"*": { requireMention: false } // all groups, always respond
|
"*": { requireMention: false }, // all groups, always respond
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
To keep mention-only for all groups (default behavior):
|
To keep mention-only for all groups (default behavior):
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
channels: {
|
channels: {
|
||||||
telegram: {
|
telegram: {
|
||||||
groups: {
|
groups: {
|
||||||
"*": { requireMention: true } // or omit groups entirely
|
"*": { requireMention: true }, // or omit groups entirely
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Via command (session-level)
|
### Via command (session-level)
|
||||||
|
|
||||||
Send in the group:
|
Send in the group:
|
||||||
|
|
||||||
- `/activation always` - respond to all messages
|
- `/activation always` - respond to all messages
|
||||||
- `/activation mention` - require mentions (default)
|
- `/activation mention` - require mentions (default)
|
||||||
|
|
||||||
@@ -205,21 +237,26 @@ Forward any message from the group to `@userinfobot` or `@getidsbot` on Telegram
|
|||||||
**Privacy note:** `@userinfobot` is a third-party bot. If you prefer, add the bot to the group, send a message, and use `openclaw logs --follow` to read `chat.id`, or use the Bot API `getUpdates`.
|
**Privacy note:** `@userinfobot` is a third-party bot. If you prefer, add the bot to the group, send a message, and use `openclaw logs --follow` to read `chat.id`, or use the Bot API `getUpdates`.
|
||||||
|
|
||||||
## Config writes
|
## Config writes
|
||||||
|
|
||||||
By default, Telegram is allowed to write config updates triggered by channel events or `/config set|unset`.
|
By default, Telegram is allowed to write config updates triggered by channel events or `/config set|unset`.
|
||||||
|
|
||||||
This happens when:
|
This happens when:
|
||||||
|
|
||||||
- A group is upgraded to a supergroup and Telegram emits `migrate_to_chat_id` (chat ID changes). OpenClaw can migrate `channels.telegram.groups` automatically.
|
- A group is upgraded to a supergroup and Telegram emits `migrate_to_chat_id` (chat ID changes). OpenClaw can migrate `channels.telegram.groups` automatically.
|
||||||
- You run `/config set` or `/config unset` in a Telegram chat (requires `commands.config: true`).
|
- You run `/config set` or `/config unset` in a Telegram chat (requires `commands.config: true`).
|
||||||
|
|
||||||
Disable with:
|
Disable with:
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
channels: { telegram: { configWrites: false } }
|
channels: { telegram: { configWrites: false } },
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Topics (forum supergroups)
|
## Topics (forum supergroups)
|
||||||
|
|
||||||
Telegram forum topics include a `message_thread_id` per message. OpenClaw:
|
Telegram forum topics include a `message_thread_id` per message. OpenClaw:
|
||||||
|
|
||||||
- Appends `:topic:<threadId>` to the Telegram group session key so each topic is isolated.
|
- Appends `:topic:<threadId>` to the Telegram group session key so each topic is isolated.
|
||||||
- Sends typing indicators and replies with `message_thread_id` so responses stay in the topic.
|
- Sends typing indicators and replies with `message_thread_id` so responses stay in the topic.
|
||||||
- General topic (thread id `1`) is special: message sends omit `message_thread_id` (Telegram rejects it), but typing indicators still include it.
|
- General topic (thread id `1`) is special: message sends omit `message_thread_id` (Telegram rejects it), but typing indicators still include it.
|
||||||
@@ -235,34 +272,36 @@ Telegram supports inline keyboards with callback buttons.
|
|||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
"channels": {
|
channels: {
|
||||||
"telegram": {
|
telegram: {
|
||||||
"capabilities": {
|
capabilities: {
|
||||||
"inlineButtons": "allowlist"
|
inlineButtons: "allowlist",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
For per-account configuration:
|
For per-account configuration:
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
"channels": {
|
channels: {
|
||||||
"telegram": {
|
telegram: {
|
||||||
"accounts": {
|
accounts: {
|
||||||
"main": {
|
main: {
|
||||||
"capabilities": {
|
capabilities: {
|
||||||
"inlineButtons": "allowlist"
|
inlineButtons: "allowlist",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Scopes:
|
Scopes:
|
||||||
|
|
||||||
- `off` — inline buttons disabled
|
- `off` — inline buttons disabled
|
||||||
- `dm` — only DMs (group targets blocked)
|
- `dm` — only DMs (group targets blocked)
|
||||||
- `group` — only groups (DM targets blocked)
|
- `group` — only groups (DM targets blocked)
|
||||||
@@ -278,19 +317,17 @@ Use the message tool with the `buttons` parameter:
|
|||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
"action": "send",
|
action: "send",
|
||||||
"channel": "telegram",
|
channel: "telegram",
|
||||||
"to": "123456789",
|
to: "123456789",
|
||||||
"message": "Choose an option:",
|
message: "Choose an option:",
|
||||||
"buttons": [
|
buttons: [
|
||||||
[
|
[
|
||||||
{"text": "Yes", "callback_data": "yes"},
|
{ text: "Yes", callback_data: "yes" },
|
||||||
{"text": "No", "callback_data": "no"}
|
{ text: "No", callback_data: "no" },
|
||||||
],
|
],
|
||||||
[
|
[{ text: "Cancel", callback_data: "cancel" }],
|
||||||
{"text": "Cancel", "callback_data": "cancel"}
|
],
|
||||||
]
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -305,9 +342,11 @@ Telegram capabilities can be configured at two levels (object form shown above;
|
|||||||
- `channels.telegram.accounts.<account>.capabilities`: Per-account capabilities that override the global defaults for that specific account.
|
- `channels.telegram.accounts.<account>.capabilities`: Per-account capabilities that override the global defaults for that specific account.
|
||||||
|
|
||||||
Use the global setting when all Telegram bots/accounts should behave the same. Use per-account configuration when different bots need different behaviors (for example, one account only handles DMs while another is allowed in groups).
|
Use the global setting when all Telegram bots/accounts should behave the same. Use per-account configuration when different bots need different behaviors (for example, one account only handles DMs while another is allowed in groups).
|
||||||
|
|
||||||
## Access control (DMs + groups)
|
## Access control (DMs + groups)
|
||||||
|
|
||||||
### DM access
|
### DM access
|
||||||
|
|
||||||
- Default: `channels.telegram.dmPolicy = "pairing"`. Unknown senders receive a pairing code; messages are ignored until approved (codes expire after 1 hour).
|
- Default: `channels.telegram.dmPolicy = "pairing"`. Unknown senders receive a pairing code; messages are ignored until approved (codes expire after 1 hour).
|
||||||
- Approve via:
|
- Approve via:
|
||||||
- `openclaw pairing list telegram`
|
- `openclaw pairing list telegram`
|
||||||
@@ -316,18 +355,22 @@ Use the global setting when all Telegram bots/accounts should behave the same. U
|
|||||||
- `channels.telegram.allowFrom` accepts numeric user IDs (recommended) or `@username` entries. It is **not** the bot username; use the human sender’s ID. The wizard accepts `@username` and resolves it to the numeric ID when possible.
|
- `channels.telegram.allowFrom` accepts numeric user IDs (recommended) or `@username` entries. It is **not** the bot username; use the human sender’s ID. The wizard accepts `@username` and resolves it to the numeric ID when possible.
|
||||||
|
|
||||||
#### Finding your Telegram user ID
|
#### Finding your Telegram user ID
|
||||||
|
|
||||||
Safer (no third-party bot):
|
Safer (no third-party bot):
|
||||||
1) Start the gateway and DM your bot.
|
|
||||||
2) Run `openclaw logs --follow` and look for `from.id`.
|
1. Start the gateway and DM your bot.
|
||||||
|
2. Run `openclaw logs --follow` and look for `from.id`.
|
||||||
|
|
||||||
Alternate (official Bot API):
|
Alternate (official Bot API):
|
||||||
1) DM your bot.
|
|
||||||
2) Fetch updates with your bot token and read `message.from.id`:
|
1. DM your bot.
|
||||||
|
2. Fetch updates with your bot token and read `message.from.id`:
|
||||||
```bash
|
```bash
|
||||||
curl "https://api.telegram.org/bot<bot_token>/getUpdates"
|
curl "https://api.telegram.org/bot<bot_token>/getUpdates"
|
||||||
```
|
```
|
||||||
|
|
||||||
Third-party (less private):
|
Third-party (less private):
|
||||||
|
|
||||||
- DM `@userinfobot` or `@getidsbot` and use the returned user id.
|
- DM `@userinfobot` or `@getidsbot` and use the returned user id.
|
||||||
|
|
||||||
### Group access
|
### Group access
|
||||||
@@ -335,37 +378,45 @@ Third-party (less private):
|
|||||||
Two independent controls:
|
Two independent controls:
|
||||||
|
|
||||||
**1. Which groups are allowed** (group allowlist via `channels.telegram.groups`):
|
**1. Which groups are allowed** (group allowlist via `channels.telegram.groups`):
|
||||||
|
|
||||||
- No `groups` config = all groups allowed
|
- No `groups` config = all groups allowed
|
||||||
- With `groups` config = only listed groups or `"*"` are allowed
|
- With `groups` config = only listed groups or `"*"` are allowed
|
||||||
- Example: `"groups": { "-1001234567890": {}, "*": {} }` allows all groups
|
- Example: `"groups": { "-1001234567890": {}, "*": {} }` allows all groups
|
||||||
|
|
||||||
**2. Which senders are allowed** (sender filtering via `channels.telegram.groupPolicy`):
|
**2. Which senders are allowed** (sender filtering via `channels.telegram.groupPolicy`):
|
||||||
|
|
||||||
- `"open"` = all senders in allowed groups can message
|
- `"open"` = all senders in allowed groups can message
|
||||||
- `"allowlist"` = only senders in `channels.telegram.groupAllowFrom` can message
|
- `"allowlist"` = only senders in `channels.telegram.groupAllowFrom` can message
|
||||||
- `"disabled"` = no group messages accepted at all
|
- `"disabled"` = no group messages accepted at all
|
||||||
Default is `groupPolicy: "allowlist"` (blocked unless you add `groupAllowFrom`).
|
Default is `groupPolicy: "allowlist"` (blocked unless you add `groupAllowFrom`).
|
||||||
|
|
||||||
Most users want: `groupPolicy: "allowlist"` + `groupAllowFrom` + specific groups listed in `channels.telegram.groups`
|
Most users want: `groupPolicy: "allowlist"` + `groupAllowFrom` + specific groups listed in `channels.telegram.groups`
|
||||||
|
|
||||||
## Long-polling vs webhook
|
## Long-polling vs webhook
|
||||||
|
|
||||||
- Default: long-polling (no public URL required).
|
- Default: long-polling (no public URL required).
|
||||||
- Webhook mode: set `channels.telegram.webhookUrl` (optionally `channels.telegram.webhookSecret` + `channels.telegram.webhookPath`).
|
- Webhook mode: set `channels.telegram.webhookUrl` and `channels.telegram.webhookSecret` (optionally `channels.telegram.webhookPath`).
|
||||||
- The local listener binds to `0.0.0.0:8787` and serves `POST /telegram-webhook` by default.
|
- The local listener binds to `0.0.0.0:8787` and serves `POST /telegram-webhook` by default.
|
||||||
- If your public URL is different, use a reverse proxy and point `channels.telegram.webhookUrl` at the public endpoint.
|
- If your public URL is different, use a reverse proxy and point `channels.telegram.webhookUrl` at the public endpoint.
|
||||||
|
|
||||||
## Reply threading
|
## Reply threading
|
||||||
|
|
||||||
Telegram supports optional threaded replies via tags:
|
Telegram supports optional threaded replies via tags:
|
||||||
|
|
||||||
- `[[reply_to_current]]` -- reply to the triggering message.
|
- `[[reply_to_current]]` -- reply to the triggering message.
|
||||||
- `[[reply_to:<id>]]` -- reply to a specific message id.
|
- `[[reply_to:<id>]]` -- reply to a specific message id.
|
||||||
|
|
||||||
Controlled by `channels.telegram.replyToMode`:
|
Controlled by `channels.telegram.replyToMode`:
|
||||||
|
|
||||||
- `first` (default), `all`, `off`.
|
- `first` (default), `all`, `off`.
|
||||||
|
|
||||||
## Audio messages (voice vs file)
|
## Audio messages (voice vs file)
|
||||||
|
|
||||||
Telegram distinguishes **voice notes** (round bubble) from **audio files** (metadata card).
|
Telegram distinguishes **voice notes** (round bubble) from **audio files** (metadata card).
|
||||||
OpenClaw defaults to audio files for backward compatibility.
|
OpenClaw defaults to audio files for backward compatibility.
|
||||||
|
|
||||||
To force a voice note bubble in agent replies, include this tag anywhere in the reply:
|
To force a voice note bubble in agent replies, include this tag anywhere in the reply:
|
||||||
|
|
||||||
- `[[audio_as_voice]]` — send audio as a voice note instead of a file.
|
- `[[audio_as_voice]]` — send audio as a voice note instead of a file.
|
||||||
|
|
||||||
The tag is stripped from the delivered text. Other channels ignore this tag.
|
The tag is stripped from the delivered text. Other channels ignore this tag.
|
||||||
@@ -375,11 +426,11 @@ For message tool sends, set `asVoice: true` with a voice-compatible audio `media
|
|||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
"action": "send",
|
action: "send",
|
||||||
"channel": "telegram",
|
channel: "telegram",
|
||||||
"to": "123456789",
|
to: "123456789",
|
||||||
"media": "https://example.com/voice.ogg",
|
media: "https://example.com/voice.ogg",
|
||||||
"asVoice": true
|
asVoice: true,
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -396,6 +447,7 @@ When a user sends a sticker, OpenClaw handles it based on the sticker type:
|
|||||||
- **Video stickers (WEBM):** Skipped (video format not supported for processing).
|
- **Video stickers (WEBM):** Skipped (video format not supported for processing).
|
||||||
|
|
||||||
Template context field available when receiving stickers:
|
Template context field available when receiving stickers:
|
||||||
|
|
||||||
- `Sticker` — object with:
|
- `Sticker` — object with:
|
||||||
- `emoji` — emoji associated with the sticker
|
- `emoji` — emoji associated with the sticker
|
||||||
- `setName` — name of the sticker set
|
- `setName` — name of the sticker set
|
||||||
@@ -416,6 +468,7 @@ Stickers are processed through the AI's vision capabilities to generate descript
|
|||||||
**Cache location:** `~/.openclaw/telegram/sticker-cache.json`
|
**Cache location:** `~/.openclaw/telegram/sticker-cache.json`
|
||||||
|
|
||||||
**Cache entry format:**
|
**Cache entry format:**
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"fileId": "CAACAgIAAxkBAAI...",
|
"fileId": "CAACAgIAAxkBAAI...",
|
||||||
@@ -428,6 +481,7 @@ Stickers are processed through the AI's vision capabilities to generate descript
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Benefits:**
|
**Benefits:**
|
||||||
|
|
||||||
- Reduces API costs by avoiding repeated vision calls for the same sticker
|
- Reduces API costs by avoiding repeated vision calls for the same sticker
|
||||||
- Faster response times for cached stickers (no vision processing delay)
|
- Faster response times for cached stickers (no vision processing delay)
|
||||||
- Enables sticker search functionality based on cached descriptions
|
- Enables sticker search functionality based on cached descriptions
|
||||||
@@ -443,10 +497,10 @@ The agent can send and search stickers using the `sticker` and `sticker-search`
|
|||||||
channels: {
|
channels: {
|
||||||
telegram: {
|
telegram: {
|
||||||
actions: {
|
actions: {
|
||||||
sticker: true
|
sticker: true,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -454,14 +508,15 @@ The agent can send and search stickers using the `sticker` and `sticker-search`
|
|||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
"action": "sticker",
|
action: "sticker",
|
||||||
"channel": "telegram",
|
channel: "telegram",
|
||||||
"to": "123456789",
|
to: "123456789",
|
||||||
"fileId": "CAACAgIAAxkBAAI..."
|
fileId: "CAACAgIAAxkBAAI...",
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Parameters:
|
Parameters:
|
||||||
|
|
||||||
- `fileId` (required) — the Telegram file ID of the sticker. Obtain this from `Sticker.fileId` when receiving a sticker, or from a `sticker-search` result.
|
- `fileId` (required) — the Telegram file ID of the sticker. Obtain this from `Sticker.fileId` when receiving a sticker, or from a `sticker-search` result.
|
||||||
- `replyTo` (optional) — message ID to reply to.
|
- `replyTo` (optional) — message ID to reply to.
|
||||||
- `threadId` (optional) — message thread ID for forum topics.
|
- `threadId` (optional) — message thread ID for forum topics.
|
||||||
@@ -472,26 +527,27 @@ The agent can search cached stickers by description, emoji, or set name:
|
|||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
"action": "sticker-search",
|
action: "sticker-search",
|
||||||
"channel": "telegram",
|
channel: "telegram",
|
||||||
"query": "cat waving",
|
query: "cat waving",
|
||||||
"limit": 5
|
limit: 5,
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Returns matching stickers from the cache:
|
Returns matching stickers from the cache:
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
"ok": true,
|
ok: true,
|
||||||
"count": 2,
|
count: 2,
|
||||||
"stickers": [
|
stickers: [
|
||||||
{
|
{
|
||||||
"fileId": "CAACAgIAAxkBAAI...",
|
fileId: "CAACAgIAAxkBAAI...",
|
||||||
"emoji": "👋",
|
emoji: "👋",
|
||||||
"description": "A cartoon cat waving enthusiastically",
|
description: "A cartoon cat waving enthusiastically",
|
||||||
"setName": "CoolCats"
|
setName: "CoolCats",
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -501,26 +557,29 @@ The search uses fuzzy matching across description text, emoji characters, and se
|
|||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
"action": "sticker",
|
action: "sticker",
|
||||||
"channel": "telegram",
|
channel: "telegram",
|
||||||
"to": "-1001234567890",
|
to: "-1001234567890",
|
||||||
"fileId": "CAACAgIAAxkBAAI...",
|
fileId: "CAACAgIAAxkBAAI...",
|
||||||
"replyTo": 42,
|
replyTo: 42,
|
||||||
"threadId": 123
|
threadId: 123,
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Streaming (drafts)
|
## Streaming (drafts)
|
||||||
|
|
||||||
Telegram can stream **draft bubbles** while the agent is generating a response.
|
Telegram can stream **draft bubbles** while the agent is generating a response.
|
||||||
OpenClaw uses Bot API `sendMessageDraft` (not real messages) and then sends the
|
OpenClaw uses Bot API `sendMessageDraft` (not real messages) and then sends the
|
||||||
final reply as a normal message.
|
final reply as a normal message.
|
||||||
|
|
||||||
Requirements (Telegram Bot API 9.3+):
|
Requirements (Telegram Bot API 9.3+):
|
||||||
|
|
||||||
- **Private chats with topics enabled** (forum topic mode for the bot).
|
- **Private chats with topics enabled** (forum topic mode for the bot).
|
||||||
- Incoming messages must include `message_thread_id` (private topic thread).
|
- Incoming messages must include `message_thread_id` (private topic thread).
|
||||||
- Streaming is ignored for groups/supergroups/channels.
|
- Streaming is ignored for groups/supergroups/channels.
|
||||||
|
|
||||||
Config:
|
Config:
|
||||||
|
|
||||||
- `channels.telegram.streamMode: "off" | "partial" | "block"` (default: `partial`)
|
- `channels.telegram.streamMode: "off" | "partial" | "block"` (default: `partial`)
|
||||||
- `partial`: update the draft bubble with the latest streaming text.
|
- `partial`: update the draft bubble with the latest streaming text.
|
||||||
- `block`: update the draft bubble in larger blocks (chunked).
|
- `block`: update the draft bubble in larger blocks (chunked).
|
||||||
@@ -534,15 +593,18 @@ Block streaming is off by default and requires `channels.telegram.blockStreaming
|
|||||||
if you want early Telegram messages instead of draft updates.
|
if you want early Telegram messages instead of draft updates.
|
||||||
|
|
||||||
Reasoning stream (Telegram only):
|
Reasoning stream (Telegram only):
|
||||||
|
|
||||||
- `/reasoning stream` streams reasoning into the draft bubble while the reply is
|
- `/reasoning stream` streams reasoning into the draft bubble while the reply is
|
||||||
generating, then sends the final answer without reasoning.
|
generating, then sends the final answer without reasoning.
|
||||||
- If `channels.telegram.streamMode` is `off`, reasoning stream is disabled.
|
- If `channels.telegram.streamMode` is `off`, reasoning stream is disabled.
|
||||||
More context: [Streaming + chunking](/concepts/streaming).
|
More context: [Streaming + chunking](/concepts/streaming).
|
||||||
|
|
||||||
## Retry policy
|
## Retry policy
|
||||||
|
|
||||||
Outbound Telegram API calls retry on transient network/429 errors with exponential backoff and jitter. Configure via `channels.telegram.retry`. See [Retry policy](/concepts/retry).
|
Outbound Telegram API calls retry on transient network/429 errors with exponential backoff and jitter. Configure via `channels.telegram.retry`. See [Retry policy](/concepts/retry).
|
||||||
|
|
||||||
## Agent tool (messages + reactions)
|
## Agent tool (messages + reactions)
|
||||||
|
|
||||||
- Tool: `telegram` with `sendMessage` action (`to`, `content`, optional `mediaUrl`, `replyToMessageId`, `messageThreadId`).
|
- Tool: `telegram` with `sendMessage` action (`to`, `content`, optional `mediaUrl`, `replyToMessageId`, `messageThreadId`).
|
||||||
- Tool: `telegram` with `react` action (`chatId`, `messageId`, `emoji`).
|
- Tool: `telegram` with `react` action (`chatId`, `messageId`, `emoji`).
|
||||||
- Tool: `telegram` with `deleteMessage` action (`chatId`, `messageId`).
|
- Tool: `telegram` with `deleteMessage` action (`chatId`, `messageId`).
|
||||||
@@ -562,6 +624,7 @@ Telegram reactions arrive as **separate `message_reaction` events**, not as prop
|
|||||||
The agent sees reactions as **system notifications** in the conversation history, not as message metadata.
|
The agent sees reactions as **system notifications** in the conversation history, not as message metadata.
|
||||||
|
|
||||||
**Configuration:**
|
**Configuration:**
|
||||||
|
|
||||||
- `channels.telegram.reactionNotifications`: Controls which reactions trigger notifications
|
- `channels.telegram.reactionNotifications`: Controls which reactions trigger notifications
|
||||||
- `"off"` — ignore all reactions
|
- `"off"` — ignore all reactions
|
||||||
- `"own"` — notify when users react to bot messages (best-effort; in-memory) (default)
|
- `"own"` — notify when users react to bot messages (best-effort; in-memory) (default)
|
||||||
@@ -576,29 +639,33 @@ The agent sees reactions as **system notifications** in the conversation history
|
|||||||
**Forum groups:** Reactions in forum groups include `message_thread_id` and use session keys like `agent:main:telegram:group:{chatId}:topic:{threadId}`. This ensures reactions and messages in the same topic stay together.
|
**Forum groups:** Reactions in forum groups include `message_thread_id` and use session keys like `agent:main:telegram:group:{chatId}:topic:{threadId}`. This ensures reactions and messages in the same topic stay together.
|
||||||
|
|
||||||
**Example config:**
|
**Example config:**
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
channels: {
|
channels: {
|
||||||
telegram: {
|
telegram: {
|
||||||
reactionNotifications: "all", // See all reactions
|
reactionNotifications: "all", // See all reactions
|
||||||
reactionLevel: "minimal" // Agent can react sparingly
|
reactionLevel: "minimal", // Agent can react sparingly
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Requirements:**
|
**Requirements:**
|
||||||
|
|
||||||
- Telegram bots must explicitly request `message_reaction` in `allowed_updates` (configured automatically by OpenClaw)
|
- Telegram bots must explicitly request `message_reaction` in `allowed_updates` (configured automatically by OpenClaw)
|
||||||
- For webhook mode, reactions are included in the webhook `allowed_updates`
|
- For webhook mode, reactions are included in the webhook `allowed_updates`
|
||||||
- For polling mode, reactions are included in the `getUpdates` `allowed_updates`
|
- For polling mode, reactions are included in the `getUpdates` `allowed_updates`
|
||||||
|
|
||||||
## Delivery targets (CLI/cron)
|
## Delivery targets (CLI/cron)
|
||||||
|
|
||||||
- Use a chat id (`123456789`) or a username (`@name`) as the target.
|
- Use a chat id (`123456789`) or a username (`@name`) as the target.
|
||||||
- Example: `openclaw message send --channel telegram --target 123456789 --message "hi"`.
|
- Example: `openclaw message send --channel telegram --target 123456789 --message "hi"`.
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
**Bot doesn’t respond to non-mention messages in a group:**
|
**Bot doesn’t respond to non-mention messages in a group:**
|
||||||
|
|
||||||
- If you set `channels.telegram.groups.*.requireMention=false`, Telegram’s Bot API **privacy mode** must be disabled.
|
- If you set `channels.telegram.groups.*.requireMention=false`, Telegram’s Bot API **privacy mode** must be disabled.
|
||||||
- BotFather: `/setprivacy` → **Disable** (then remove + re-add the bot to the group)
|
- BotFather: `/setprivacy` → **Disable** (then remove + re-add the bot to the group)
|
||||||
- `openclaw channels status` shows a warning when config expects unmentioned group messages.
|
- `openclaw channels status` shows a warning when config expects unmentioned group messages.
|
||||||
@@ -606,32 +673,39 @@ The agent sees reactions as **system notifications** in the conversation history
|
|||||||
- Quick test: `/activation always` (session-only; use config for persistence)
|
- Quick test: `/activation always` (session-only; use config for persistence)
|
||||||
|
|
||||||
**Bot not seeing group messages at all:**
|
**Bot not seeing group messages at all:**
|
||||||
|
|
||||||
- If `channels.telegram.groups` is set, the group must be listed or use `"*"`
|
- If `channels.telegram.groups` is set, the group must be listed or use `"*"`
|
||||||
- Check Privacy Settings in @BotFather → "Group Privacy" should be **OFF**
|
- Check Privacy Settings in @BotFather → "Group Privacy" should be **OFF**
|
||||||
- Verify bot is actually a member (not just an admin with no read access)
|
- Verify bot is actually a member (not just an admin with no read access)
|
||||||
- Check gateway logs: `openclaw logs --follow` (look for "skipping group message")
|
- Check gateway logs: `openclaw logs --follow` (look for "skipping group message")
|
||||||
|
|
||||||
**Bot responds to mentions but not `/activation always`:**
|
**Bot responds to mentions but not `/activation always`:**
|
||||||
|
|
||||||
- The `/activation` command updates session state but doesn't persist to config
|
- The `/activation` command updates session state but doesn't persist to config
|
||||||
- For persistent behavior, add group to `channels.telegram.groups` with `requireMention: false`
|
- For persistent behavior, add group to `channels.telegram.groups` with `requireMention: false`
|
||||||
|
|
||||||
**Commands like `/status` don't work:**
|
**Commands like `/status` don't work:**
|
||||||
|
|
||||||
- Make sure your Telegram user ID is authorized (via pairing or `channels.telegram.allowFrom`)
|
- Make sure your Telegram user ID is authorized (via pairing or `channels.telegram.allowFrom`)
|
||||||
- Commands require authorization even in groups with `groupPolicy: "open"`
|
- Commands require authorization even in groups with `groupPolicy: "open"`
|
||||||
|
|
||||||
**Long-polling aborts immediately on Node 22+ (often with proxies/custom fetch):**
|
**Long-polling aborts immediately on Node 22+ (often with proxies/custom fetch):**
|
||||||
|
|
||||||
- Node 22+ is stricter about `AbortSignal` instances; foreign signals can abort `fetch` calls right away.
|
- Node 22+ is stricter about `AbortSignal` instances; foreign signals can abort `fetch` calls right away.
|
||||||
- Upgrade to a OpenClaw build that normalizes abort signals, or run the gateway on Node 20 until you can upgrade.
|
- Upgrade to a OpenClaw build that normalizes abort signals, or run the gateway on Node 20 until you can upgrade.
|
||||||
|
|
||||||
**Bot starts, then silently stops responding (or logs `HttpError: Network request ... failed`):**
|
**Bot starts, then silently stops responding (or logs `HttpError: Network request ... failed`):**
|
||||||
|
|
||||||
- Some hosts resolve `api.telegram.org` to IPv6 first. If your server does not have working IPv6 egress, grammY can get stuck on IPv6-only requests.
|
- Some hosts resolve `api.telegram.org` to IPv6 first. If your server does not have working IPv6 egress, grammY can get stuck on IPv6-only requests.
|
||||||
- Fix by enabling IPv6 egress **or** forcing IPv4 resolution for `api.telegram.org` (for example, add an `/etc/hosts` entry using the IPv4 A record, or prefer IPv4 in your OS DNS stack), then restart the gateway.
|
- Fix by enabling IPv6 egress **or** forcing IPv4 resolution for `api.telegram.org` (for example, add an `/etc/hosts` entry using the IPv4 A record, or prefer IPv4 in your OS DNS stack), then restart the gateway.
|
||||||
- Quick check: `dig +short api.telegram.org A` and `dig +short api.telegram.org AAAA` to confirm what DNS returns.
|
- Quick check: `dig +short api.telegram.org A` and `dig +short api.telegram.org AAAA` to confirm what DNS returns.
|
||||||
|
|
||||||
## Configuration reference (Telegram)
|
## Configuration reference (Telegram)
|
||||||
|
|
||||||
Full configuration: [Configuration](/gateway/configuration)
|
Full configuration: [Configuration](/gateway/configuration)
|
||||||
|
|
||||||
Provider options:
|
Provider options:
|
||||||
|
|
||||||
- `channels.telegram.enabled`: enable/disable channel startup.
|
- `channels.telegram.enabled`: enable/disable channel startup.
|
||||||
- `channels.telegram.botToken`: bot token (BotFather).
|
- `channels.telegram.botToken`: bot token (BotFather).
|
||||||
- `channels.telegram.tokenFile`: read token from file path.
|
- `channels.telegram.tokenFile`: read token from file path.
|
||||||
@@ -658,8 +732,8 @@ Provider options:
|
|||||||
- `channels.telegram.retry`: retry policy for outbound Telegram API calls (attempts, minDelayMs, maxDelayMs, jitter).
|
- `channels.telegram.retry`: retry policy for outbound Telegram API calls (attempts, minDelayMs, maxDelayMs, jitter).
|
||||||
- `channels.telegram.network.autoSelectFamily`: override Node autoSelectFamily (true=enable, false=disable). Defaults to disabled on Node 22 to avoid Happy Eyeballs timeouts.
|
- `channels.telegram.network.autoSelectFamily`: override Node autoSelectFamily (true=enable, false=disable). Defaults to disabled on Node 22 to avoid Happy Eyeballs timeouts.
|
||||||
- `channels.telegram.proxy`: proxy URL for Bot API calls (SOCKS/HTTP).
|
- `channels.telegram.proxy`: proxy URL for Bot API calls (SOCKS/HTTP).
|
||||||
- `channels.telegram.webhookUrl`: enable webhook mode.
|
- `channels.telegram.webhookUrl`: enable webhook mode (requires `channels.telegram.webhookSecret`).
|
||||||
- `channels.telegram.webhookSecret`: webhook secret (optional).
|
- `channels.telegram.webhookSecret`: webhook secret (required when webhookUrl is set).
|
||||||
- `channels.telegram.webhookPath`: local webhook path (default `/telegram-webhook`).
|
- `channels.telegram.webhookPath`: local webhook path (default `/telegram-webhook`).
|
||||||
- `channels.telegram.actions.reactions`: gate Telegram tool reactions.
|
- `channels.telegram.actions.reactions`: gate Telegram tool reactions.
|
||||||
- `channels.telegram.actions.sendMessage`: gate Telegram tool message sends.
|
- `channels.telegram.actions.sendMessage`: gate Telegram tool message sends.
|
||||||
@@ -669,6 +743,7 @@ Provider options:
|
|||||||
- `channels.telegram.reactionLevel`: `off | ack | minimal | extensive` — control agent's reaction capability (default: `minimal` when not set).
|
- `channels.telegram.reactionLevel`: `off | ack | minimal | extensive` — control agent's reaction capability (default: `minimal` when not set).
|
||||||
|
|
||||||
Related global options:
|
Related global options:
|
||||||
|
|
||||||
- `agents.list[].groupChat.mentionPatterns` (mention gating patterns).
|
- `agents.list[].groupChat.mentionPatterns` (mention gating patterns).
|
||||||
- `messages.groupChat.mentionPatterns` (global fallback).
|
- `messages.groupChat.mentionPatterns` (global fallback).
|
||||||
- `commands.native` (defaults to `"auto"` → on for Telegram/Discord, off for Slack), `commands.text`, `commands.useAccessGroups` (command behavior). Override with `channels.telegram.commands.native`.
|
- `commands.native` (defaults to `"auto"` → on for Telegram/Discord, off for Slack), `commands.text`, `commands.useAccessGroups` (command behavior). Override with `channels.telegram.commands.native`.
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
summary: "Tlon/Urbit support status, capabilities, and configuration"
|
summary: "Tlon/Urbit support status, capabilities, and configuration"
|
||||||
read_when:
|
read_when:
|
||||||
- Working on Tlon/Urbit channel features
|
- Working on Tlon/Urbit channel features
|
||||||
|
title: "Tlon"
|
||||||
---
|
---
|
||||||
|
|
||||||
# Tlon (plugin)
|
# Tlon (plugin)
|
||||||
|
|
||||||
Tlon is a decentralized messenger built on Urbit. OpenClaw connects to your Urbit ship and can
|
Tlon is a decentralized messenger built on Urbit. OpenClaw connects to your Urbit ship and can
|
||||||
@@ -32,11 +34,11 @@ Details: [Plugins](/plugin)
|
|||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
1) Install the Tlon plugin.
|
1. Install the Tlon plugin.
|
||||||
2) Gather your ship URL and login code.
|
2. Gather your ship URL and login code.
|
||||||
3) Configure `channels.tlon`.
|
3. Configure `channels.tlon`.
|
||||||
4) Restart the gateway.
|
4. Restart the gateway.
|
||||||
5) DM the bot or mention it in a group channel.
|
5. DM the bot or mention it in a group channel.
|
||||||
|
|
||||||
Minimal config (single account):
|
Minimal config (single account):
|
||||||
|
|
||||||
@@ -47,9 +49,9 @@ Minimal config (single account):
|
|||||||
enabled: true,
|
enabled: true,
|
||||||
ship: "~sampel-palnet",
|
ship: "~sampel-palnet",
|
||||||
url: "https://your-ship-host",
|
url: "https://your-ship-host",
|
||||||
code: "lidlut-tabwed-pillex-ridrup"
|
code: "lidlut-tabwed-pillex-ridrup",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -61,12 +63,9 @@ Auto-discovery is enabled by default. You can also pin channels manually:
|
|||||||
{
|
{
|
||||||
channels: {
|
channels: {
|
||||||
tlon: {
|
tlon: {
|
||||||
groupChannels: [
|
groupChannels: ["chat/~host-ship/general", "chat/~host-ship/support"],
|
||||||
"chat/~host-ship/general",
|
},
|
||||||
"chat/~host-ship/support"
|
},
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -76,9 +75,9 @@ Disable auto-discovery:
|
|||||||
{
|
{
|
||||||
channels: {
|
channels: {
|
||||||
tlon: {
|
tlon: {
|
||||||
autoDiscoverChannels: false
|
autoDiscoverChannels: false,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -90,9 +89,9 @@ DM allowlist (empty = allow all):
|
|||||||
{
|
{
|
||||||
channels: {
|
channels: {
|
||||||
tlon: {
|
tlon: {
|
||||||
dmAllowlist: ["~zod", "~nec"]
|
dmAllowlist: ["~zod", "~nec"],
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -107,15 +106,15 @@ Group authorization (restricted by default):
|
|||||||
channelRules: {
|
channelRules: {
|
||||||
"chat/~host-ship/general": {
|
"chat/~host-ship/general": {
|
||||||
mode: "restricted",
|
mode: "restricted",
|
||||||
allowedShips: ["~zod", "~nec"]
|
allowedShips: ["~zod", "~nec"],
|
||||||
},
|
},
|
||||||
"chat/~host-ship/announcements": {
|
"chat/~host-ship/announcements": {
|
||||||
mode: "open"
|
mode: "open",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ summary: "Channel-specific troubleshooting shortcuts (Discord/Telegram/WhatsApp)
|
|||||||
read_when:
|
read_when:
|
||||||
- A channel connects but messages don’t flow
|
- A channel connects but messages don’t flow
|
||||||
- Investigating channel misconfiguration (intents, permissions, privacy mode)
|
- Investigating channel misconfiguration (intents, permissions, privacy mode)
|
||||||
|
title: "Channel Troubleshooting"
|
||||||
---
|
---
|
||||||
|
|
||||||
# Channel troubleshooting
|
# Channel troubleshooting
|
||||||
|
|
||||||
Start with:
|
Start with:
|
||||||
@@ -16,10 +18,12 @@ openclaw channels status --probe
|
|||||||
`channels status --probe` prints warnings when it can detect common channel misconfigurations, and includes small live checks (credentials, some permissions/membership).
|
`channels status --probe` prints warnings when it can detect common channel misconfigurations, and includes small live checks (credentials, some permissions/membership).
|
||||||
|
|
||||||
## Channels
|
## Channels
|
||||||
|
|
||||||
- Discord: [/channels/discord#troubleshooting](/channels/discord#troubleshooting)
|
- Discord: [/channels/discord#troubleshooting](/channels/discord#troubleshooting)
|
||||||
- Telegram: [/channels/telegram#troubleshooting](/channels/telegram#troubleshooting)
|
- Telegram: [/channels/telegram#troubleshooting](/channels/telegram#troubleshooting)
|
||||||
- WhatsApp: [/channels/whatsapp#troubleshooting-quick](/channels/whatsapp#troubleshooting-quick)
|
- WhatsApp: [/channels/whatsapp#troubleshooting-quick](/channels/whatsapp#troubleshooting-quick)
|
||||||
|
|
||||||
## Telegram quick fixes
|
## Telegram quick fixes
|
||||||
|
|
||||||
- Logs show `HttpError: Network request for 'sendMessage' failed` or `sendChatAction` → check IPv6 DNS. If `api.telegram.org` resolves to IPv6 first and the host lacks IPv6 egress, force IPv4 or enable IPv6. See [/channels/telegram#troubleshooting](/channels/telegram#troubleshooting).
|
- Logs show `HttpError: Network request for 'sendMessage' failed` or `sendChatAction` → check IPv6 DNS. If `api.telegram.org` resolves to IPv6 first and the host lacks IPv6 egress, force IPv4 or enable IPv6. See [/channels/telegram#troubleshooting](/channels/telegram#troubleshooting).
|
||||||
- Logs show `setMyCommands failed` → check outbound HTTPS and DNS reachability to `api.telegram.org` (common on locked-down VPS or proxies).
|
- Logs show `setMyCommands failed` → check outbound HTTPS and DNS reachability to `api.telegram.org` (common on locked-down VPS or proxies).
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
summary: "Twitch chat bot configuration and setup"
|
summary: "Twitch chat bot configuration and setup"
|
||||||
read_when:
|
read_when:
|
||||||
- Setting up Twitch chat integration for OpenClaw
|
- Setting up Twitch chat integration for OpenClaw
|
||||||
|
title: "Twitch"
|
||||||
---
|
---
|
||||||
|
|
||||||
# Twitch (plugin)
|
# Twitch (plugin)
|
||||||
|
|
||||||
Twitch chat support via IRC connection. OpenClaw connects as a Twitch user (bot account) to receive and send messages in channels.
|
Twitch chat support via IRC connection. OpenClaw connects as a Twitch user (bot account) to receive and send messages in channels.
|
||||||
@@ -27,17 +29,17 @@ Details: [Plugins](/plugin)
|
|||||||
|
|
||||||
## Quick setup (beginner)
|
## Quick setup (beginner)
|
||||||
|
|
||||||
1) Create a dedicated Twitch account for the bot (or use an existing account).
|
1. Create a dedicated Twitch account for the bot (or use an existing account).
|
||||||
2) Generate credentials: [Twitch Token Generator](https://twitchtokengenerator.com/)
|
2. Generate credentials: [Twitch Token Generator](https://twitchtokengenerator.com/)
|
||||||
- Select **Bot Token**
|
- Select **Bot Token**
|
||||||
- Verify scopes `chat:read` and `chat:write` are selected
|
- Verify scopes `chat:read` and `chat:write` are selected
|
||||||
- Copy the **Client ID** and **Access Token**
|
- Copy the **Client ID** and **Access Token**
|
||||||
3) Find your Twitch user ID: https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/
|
3. Find your Twitch user ID: https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/
|
||||||
4) Configure the token:
|
4. Configure the token:
|
||||||
- Env: `OPENCLAW_TWITCH_ACCESS_TOKEN=...` (default account only)
|
- Env: `OPENCLAW_TWITCH_ACCESS_TOKEN=...` (default account only)
|
||||||
- Or config: `channels.twitch.accessToken`
|
- Or config: `channels.twitch.accessToken`
|
||||||
- If both are set, config takes precedence (env fallback is default-account only).
|
- If both are set, config takes precedence (env fallback is default-account only).
|
||||||
5) Start the gateway.
|
5. Start the gateway.
|
||||||
|
|
||||||
**⚠️ Important:** Add access control (`allowFrom` or `allowedRoles`) to prevent unauthorized users from triggering the bot. `requireMention` defaults to `true`.
|
**⚠️ Important:** Add access control (`allowFrom` or `allowedRoles`) to prevent unauthorized users from triggering the bot. `requireMention` defaults to `true`.
|
||||||
|
|
||||||
@@ -48,13 +50,13 @@ Minimal config:
|
|||||||
channels: {
|
channels: {
|
||||||
twitch: {
|
twitch: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
username: "openclaw", // Bot's Twitch account
|
username: "openclaw", // Bot's Twitch account
|
||||||
accessToken: "oauth:abc123...", // OAuth Access Token (or use OPENCLAW_TWITCH_ACCESS_TOKEN env var)
|
accessToken: "oauth:abc123...", // OAuth Access Token (or use OPENCLAW_TWITCH_ACCESS_TOKEN env var)
|
||||||
clientId: "xyz789...", // Client ID from Token Generator
|
clientId: "xyz789...", // Client ID from Token Generator
|
||||||
channel: "vevisk", // Which Twitch channel's chat to join (required)
|
channel: "vevisk", // Which Twitch channel's chat to join (required)
|
||||||
allowFrom: ["123456789"] // (recommended) Your Twitch user ID only - get it from https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/
|
allowFrom: ["123456789"], // (recommended) Your Twitch user ID only - get it from https://www.streamweasels.com/tools/convert-twitch-username-to-user-id/
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -70,6 +72,7 @@ Minimal config:
|
|||||||
### Generate credentials
|
### Generate credentials
|
||||||
|
|
||||||
Use [Twitch Token Generator](https://twitchtokengenerator.com/):
|
Use [Twitch Token Generator](https://twitchtokengenerator.com/):
|
||||||
|
|
||||||
- Select **Bot Token**
|
- Select **Bot Token**
|
||||||
- Verify scopes `chat:read` and `chat:write` are selected
|
- Verify scopes `chat:read` and `chat:write` are selected
|
||||||
- Copy the **Client ID** and **Access Token**
|
- Copy the **Client ID** and **Access Token**
|
||||||
@@ -79,11 +82,13 @@ No manual app registration needed. Tokens expire after several hours.
|
|||||||
### Configure the bot
|
### Configure the bot
|
||||||
|
|
||||||
**Env var (default account only):**
|
**Env var (default account only):**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
OPENCLAW_TWITCH_ACCESS_TOKEN=oauth:abc123...
|
OPENCLAW_TWITCH_ACCESS_TOKEN=oauth:abc123...
|
||||||
```
|
```
|
||||||
|
|
||||||
**Or config:**
|
**Or config:**
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
channels: {
|
channels: {
|
||||||
@@ -92,9 +97,9 @@ OPENCLAW_TWITCH_ACCESS_TOKEN=oauth:abc123...
|
|||||||
username: "openclaw",
|
username: "openclaw",
|
||||||
accessToken: "oauth:abc123...",
|
accessToken: "oauth:abc123...",
|
||||||
clientId: "xyz789...",
|
clientId: "xyz789...",
|
||||||
channel: "vevisk"
|
channel: "vevisk",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -106,13 +111,14 @@ If both env and config are set, config takes precedence.
|
|||||||
{
|
{
|
||||||
channels: {
|
channels: {
|
||||||
twitch: {
|
twitch: {
|
||||||
allowFrom: ["123456789"], // (recommended) Your Twitch user ID only
|
allowFrom: ["123456789"], // (recommended) Your Twitch user ID only
|
||||||
allowedRoles: ["moderator"] // Or restrict to roles
|
},
|
||||||
}
|
},
|
||||||
}
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Prefer `allowFrom` for a hard allowlist. Use `allowedRoles` instead if you want role-based access.
|
||||||
|
|
||||||
**Available roles:** `"moderator"`, `"owner"`, `"vip"`, `"subscriber"`, `"all"`.
|
**Available roles:** `"moderator"`, `"owner"`, `"vip"`, `"subscriber"`, `"all"`.
|
||||||
|
|
||||||
**Why user IDs?** Usernames can change, allowing impersonation. User IDs are permanent.
|
**Why user IDs?** Usernames can change, allowing impersonation. User IDs are permanent.
|
||||||
@@ -130,9 +136,9 @@ For automatic token refresh, create your own Twitch application at [Twitch Devel
|
|||||||
channels: {
|
channels: {
|
||||||
twitch: {
|
twitch: {
|
||||||
clientSecret: "your_client_secret",
|
clientSecret: "your_client_secret",
|
||||||
refreshToken: "your_refresh_token"
|
refreshToken: "your_refresh_token",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -153,17 +159,17 @@ Example (one bot account in two channels):
|
|||||||
username: "openclaw",
|
username: "openclaw",
|
||||||
accessToken: "oauth:abc123...",
|
accessToken: "oauth:abc123...",
|
||||||
clientId: "xyz789...",
|
clientId: "xyz789...",
|
||||||
channel: "vevisk"
|
channel: "vevisk",
|
||||||
},
|
},
|
||||||
channel2: {
|
channel2: {
|
||||||
username: "openclaw",
|
username: "openclaw",
|
||||||
accessToken: "oauth:def456...",
|
accessToken: "oauth:def456...",
|
||||||
clientId: "uvw012...",
|
clientId: "uvw012...",
|
||||||
channel: "secondchannel"
|
channel: "secondchannel",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -179,11 +185,11 @@ Example (one bot account in two channels):
|
|||||||
twitch: {
|
twitch: {
|
||||||
accounts: {
|
accounts: {
|
||||||
default: {
|
default: {
|
||||||
allowedRoles: ["moderator", "vip"]
|
allowedRoles: ["moderator", "vip"],
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -195,17 +201,18 @@ Example (one bot account in two channels):
|
|||||||
twitch: {
|
twitch: {
|
||||||
accounts: {
|
accounts: {
|
||||||
default: {
|
default: {
|
||||||
allowFrom: ["123456789", "987654321"]
|
allowFrom: ["123456789", "987654321"],
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Combined allowlist + roles
|
### Role-based access (alternative)
|
||||||
|
|
||||||
Users in `allowFrom` bypass role checks:
|
`allowFrom` is a hard allowlist. When set, only those user IDs are allowed.
|
||||||
|
If you want role-based access, leave `allowFrom` unset and configure `allowedRoles` instead:
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
@@ -213,12 +220,11 @@ Users in `allowFrom` bypass role checks:
|
|||||||
twitch: {
|
twitch: {
|
||||||
accounts: {
|
accounts: {
|
||||||
default: {
|
default: {
|
||||||
allowFrom: ["123456789"],
|
allowedRoles: ["moderator"],
|
||||||
allowedRoles: ["moderator"]
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -232,11 +238,11 @@ By default, `requireMention` is `true`. To disable and respond to all messages:
|
|||||||
twitch: {
|
twitch: {
|
||||||
accounts: {
|
accounts: {
|
||||||
default: {
|
default: {
|
||||||
requireMention: false
|
requireMention: false,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -251,13 +257,15 @@ openclaw channels status --probe
|
|||||||
|
|
||||||
### Bot doesn't respond to messages
|
### Bot doesn't respond to messages
|
||||||
|
|
||||||
**Check access control:** Temporarily set `allowedRoles: ["all"]` to test.
|
**Check access control:** Ensure your user ID is in `allowFrom`, or temporarily remove
|
||||||
|
`allowFrom` and set `allowedRoles: ["all"]` to test.
|
||||||
|
|
||||||
**Check the bot is in the channel:** The bot must join the channel specified in `channel`.
|
**Check the bot is in the channel:** The bot must join the channel specified in `channel`.
|
||||||
|
|
||||||
### Token issues
|
### Token issues
|
||||||
|
|
||||||
**"Failed to connect" or authentication errors:**
|
**"Failed to connect" or authentication errors:**
|
||||||
|
|
||||||
- Verify `accessToken` is the OAuth access token value (typically starts with `oauth:` prefix)
|
- Verify `accessToken` is the OAuth access token value (typically starts with `oauth:` prefix)
|
||||||
- Check token has `chat:read` and `chat:write` scopes
|
- Check token has `chat:read` and `chat:write` scopes
|
||||||
- If using token refresh, verify `clientSecret` and `refreshToken` are set
|
- If using token refresh, verify `clientSecret` and `refreshToken` are set
|
||||||
@@ -265,18 +273,21 @@ openclaw channels status --probe
|
|||||||
### Token refresh not working
|
### Token refresh not working
|
||||||
|
|
||||||
**Check logs for refresh events:**
|
**Check logs for refresh events:**
|
||||||
|
|
||||||
```
|
```
|
||||||
Using env token source for mybot
|
Using env token source for mybot
|
||||||
Access token refreshed for user 123456 (expires in 14400s)
|
Access token refreshed for user 123456 (expires in 14400s)
|
||||||
```
|
```
|
||||||
|
|
||||||
If you see "token refresh disabled (no refresh token)":
|
If you see "token refresh disabled (no refresh token)":
|
||||||
|
|
||||||
- Ensure `clientSecret` is provided
|
- Ensure `clientSecret` is provided
|
||||||
- Ensure `refreshToken` is provided
|
- Ensure `refreshToken` is provided
|
||||||
|
|
||||||
## Config
|
## Config
|
||||||
|
|
||||||
**Account config:**
|
**Account config:**
|
||||||
|
|
||||||
- `username` - Bot username
|
- `username` - Bot username
|
||||||
- `accessToken` - OAuth access token with `chat:read` and `chat:write`
|
- `accessToken` - OAuth access token with `chat:read` and `chat:write`
|
||||||
- `clientId` - Twitch Client ID (from Token Generator or your app)
|
- `clientId` - Twitch Client ID (from Token Generator or your app)
|
||||||
@@ -291,6 +302,7 @@ If you see "token refresh disabled (no refresh token)":
|
|||||||
- `requireMention` - Require @mention (default: `true`)
|
- `requireMention` - Require @mention (default: `true`)
|
||||||
|
|
||||||
**Provider options:**
|
**Provider options:**
|
||||||
|
|
||||||
- `channels.twitch.enabled` - Enable/disable channel startup
|
- `channels.twitch.enabled` - Enable/disable channel startup
|
||||||
- `channels.twitch.username` - Bot username (simplified single-account config)
|
- `channels.twitch.username` - Bot username (simplified single-account config)
|
||||||
- `channels.twitch.accessToken` - OAuth access token (simplified single-account config)
|
- `channels.twitch.accessToken` - OAuth access token (simplified single-account config)
|
||||||
@@ -325,28 +337,29 @@ Full example:
|
|||||||
expiresIn: 14400,
|
expiresIn: 14400,
|
||||||
obtainmentTimestamp: 1706092800000,
|
obtainmentTimestamp: 1706092800000,
|
||||||
allowFrom: ["123456789", "987654321"],
|
allowFrom: ["123456789", "987654321"],
|
||||||
allowedRoles: ["moderator"]
|
allowedRoles: ["moderator"],
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Tool actions
|
## Tool actions
|
||||||
|
|
||||||
The agent can call `twitch` with action:
|
The agent can call `twitch` with action:
|
||||||
|
|
||||||
- `send` - Send a message to a channel
|
- `send` - Send a message to a channel
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
"action": "twitch",
|
action: "twitch",
|
||||||
"params": {
|
params: {
|
||||||
"message": "Hello Twitch!",
|
message: "Hello Twitch!",
|
||||||
"to": "#mychannel"
|
to: "#mychannel",
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -2,46 +2,53 @@
|
|||||||
summary: "WhatsApp (web channel) integration: login, inbox, replies, media, and ops"
|
summary: "WhatsApp (web channel) integration: login, inbox, replies, media, and ops"
|
||||||
read_when:
|
read_when:
|
||||||
- Working on WhatsApp/web channel behavior or inbox routing
|
- Working on WhatsApp/web channel behavior or inbox routing
|
||||||
|
title: "WhatsApp"
|
||||||
---
|
---
|
||||||
# WhatsApp (web channel)
|
|
||||||
|
|
||||||
|
# WhatsApp (web channel)
|
||||||
|
|
||||||
Status: WhatsApp Web via Baileys only. Gateway owns the session(s).
|
Status: WhatsApp Web via Baileys only. Gateway owns the session(s).
|
||||||
|
|
||||||
## Quick setup (beginner)
|
## Quick setup (beginner)
|
||||||
1) Use a **separate phone number** if possible (recommended).
|
|
||||||
2) Configure WhatsApp in `~/.openclaw/openclaw.json`.
|
1. Use a **separate phone number** if possible (recommended).
|
||||||
3) Run `openclaw channels login` to scan the QR code (Linked Devices).
|
2. Configure WhatsApp in `~/.openclaw/openclaw.json`.
|
||||||
4) Start the gateway.
|
3. Run `openclaw channels login` to scan the QR code (Linked Devices).
|
||||||
|
4. Start the gateway.
|
||||||
|
|
||||||
Minimal config:
|
Minimal config:
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
channels: {
|
channels: {
|
||||||
whatsapp: {
|
whatsapp: {
|
||||||
dmPolicy: "allowlist",
|
dmPolicy: "allowlist",
|
||||||
allowFrom: ["+15551234567"]
|
allowFrom: ["+15551234567"],
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Goals
|
## Goals
|
||||||
|
|
||||||
- Multiple WhatsApp accounts (multi-account) in one Gateway process.
|
- Multiple WhatsApp accounts (multi-account) in one Gateway process.
|
||||||
- Deterministic routing: replies return to WhatsApp, no model routing.
|
- Deterministic routing: replies return to WhatsApp, no model routing.
|
||||||
- Model sees enough context to understand quoted replies.
|
- Model sees enough context to understand quoted replies.
|
||||||
|
|
||||||
## Config writes
|
## Config writes
|
||||||
|
|
||||||
By default, WhatsApp is allowed to write config updates triggered by `/config set|unset` (requires `commands.config: true`).
|
By default, WhatsApp is allowed to write config updates triggered by `/config set|unset` (requires `commands.config: true`).
|
||||||
|
|
||||||
Disable with:
|
Disable with:
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
channels: { whatsapp: { configWrites: false } }
|
channels: { whatsapp: { configWrites: false } },
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Architecture (who owns what)
|
## Architecture (who owns what)
|
||||||
|
|
||||||
- **Gateway** owns the Baileys socket and inbox loop.
|
- **Gateway** owns the Baileys socket and inbox loop.
|
||||||
- **CLI / macOS app** talk to the gateway; no direct Baileys use.
|
- **CLI / macOS app** talk to the gateway; no direct Baileys use.
|
||||||
- **Active listener** is required for outbound sends; otherwise send fails fast.
|
- **Active listener** is required for outbound sends; otherwise send fails fast.
|
||||||
@@ -51,19 +58,21 @@ Disable with:
|
|||||||
WhatsApp requires a real mobile number for verification. VoIP and virtual numbers are usually blocked. There are two supported ways to run OpenClaw on WhatsApp:
|
WhatsApp requires a real mobile number for verification. VoIP and virtual numbers are usually blocked. There are two supported ways to run OpenClaw on WhatsApp:
|
||||||
|
|
||||||
### Dedicated number (recommended)
|
### Dedicated number (recommended)
|
||||||
|
|
||||||
Use a **separate phone number** for OpenClaw. Best UX, clean routing, no self-chat quirks. Ideal setup: **spare/old Android phone + eSIM**. Leave it on Wi‑Fi and power, and link it via QR.
|
Use a **separate phone number** for OpenClaw. Best UX, clean routing, no self-chat quirks. Ideal setup: **spare/old Android phone + eSIM**. Leave it on Wi‑Fi and power, and link it via QR.
|
||||||
|
|
||||||
**WhatsApp Business:** You can use WhatsApp Business on the same device with a different number. Great for keeping your personal WhatsApp separate — install WhatsApp Business and register the OpenClaw number there.
|
**WhatsApp Business:** You can use WhatsApp Business on the same device with a different number. Great for keeping your personal WhatsApp separate — install WhatsApp Business and register the OpenClaw number there.
|
||||||
|
|
||||||
**Sample config (dedicated number, single-user allowlist):**
|
**Sample config (dedicated number, single-user allowlist):**
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
channels: {
|
channels: {
|
||||||
whatsapp: {
|
whatsapp: {
|
||||||
dmPolicy: "allowlist",
|
dmPolicy: "allowlist",
|
||||||
allowFrom: ["+15551234567"]
|
allowFrom: ["+15551234567"],
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -72,10 +81,12 @@ If you want pairing instead of allowlist, set `channels.whatsapp.dmPolicy` to `p
|
|||||||
`openclaw pairing approve whatsapp <code>`
|
`openclaw pairing approve whatsapp <code>`
|
||||||
|
|
||||||
### Personal number (fallback)
|
### Personal number (fallback)
|
||||||
|
|
||||||
Quick fallback: run OpenClaw on **your own number**. Message yourself (WhatsApp “Message yourself”) for testing so you don’t spam contacts. Expect to read verification codes on your main phone during setup and experiments. **Must enable self-chat mode.**
|
Quick fallback: run OpenClaw on **your own number**. Message yourself (WhatsApp “Message yourself”) for testing so you don’t spam contacts. Expect to read verification codes on your main phone during setup and experiments. **Must enable self-chat mode.**
|
||||||
When the wizard asks for your personal WhatsApp number, enter the phone you will message from (the owner/sender), not the assistant number.
|
When the wizard asks for your personal WhatsApp number, enter the phone you will message from (the owner/sender), not the assistant number.
|
||||||
|
|
||||||
**Sample config (personal number, self-chat):**
|
**Sample config (personal number, self-chat):**
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"whatsapp": {
|
"whatsapp": {
|
||||||
@@ -91,6 +102,7 @@ if `messages.responsePrefix` is unset. Set it explicitly to customize or disable
|
|||||||
the prefix (use `""` to remove it).
|
the prefix (use `""` to remove it).
|
||||||
|
|
||||||
### Number sourcing tips
|
### Number sourcing tips
|
||||||
|
|
||||||
- **Local eSIM** from your country's mobile carrier (most reliable)
|
- **Local eSIM** from your country's mobile carrier (most reliable)
|
||||||
- Austria: [hot.at](https://www.hot.at)
|
- Austria: [hot.at](https://www.hot.at)
|
||||||
- UK: [giffgaff](https://www.giffgaff.com) — free SIM, no contract
|
- UK: [giffgaff](https://www.giffgaff.com) — free SIM, no contract
|
||||||
@@ -101,6 +113,7 @@ the prefix (use `""` to remove it).
|
|||||||
**Tip:** The number only needs to receive one verification SMS. After that, WhatsApp Web sessions persist via `creds.json`.
|
**Tip:** The number only needs to receive one verification SMS. After that, WhatsApp Web sessions persist via `creds.json`.
|
||||||
|
|
||||||
## Why Not Twilio?
|
## Why Not Twilio?
|
||||||
|
|
||||||
- Early OpenClaw builds supported Twilio’s WhatsApp Business integration.
|
- Early OpenClaw builds supported Twilio’s WhatsApp Business integration.
|
||||||
- WhatsApp Business numbers are a poor fit for a personal assistant.
|
- WhatsApp Business numbers are a poor fit for a personal assistant.
|
||||||
- Meta enforces a 24‑hour reply window; if you haven’t responded in the last 24 hours, the business number can’t initiate new messages.
|
- Meta enforces a 24‑hour reply window; if you haven’t responded in the last 24 hours, the business number can’t initiate new messages.
|
||||||
@@ -108,6 +121,7 @@ the prefix (use `""` to remove it).
|
|||||||
- Result: unreliable delivery and frequent blocks, so support was removed.
|
- Result: unreliable delivery and frequent blocks, so support was removed.
|
||||||
|
|
||||||
## Login + credentials
|
## Login + credentials
|
||||||
|
|
||||||
- Login command: `openclaw channels login` (QR via Linked Devices).
|
- Login command: `openclaw channels login` (QR via Linked Devices).
|
||||||
- Multi-account login: `openclaw channels login --account <id>` (`<id>` = `accountId`).
|
- Multi-account login: `openclaw channels login --account <id>` (`<id>` = `accountId`).
|
||||||
- Default account (when `--account` is omitted): `default` if present, otherwise the first configured account id (sorted).
|
- Default account (when `--account` is omitted): `default` if present, otherwise the first configured account id (sorted).
|
||||||
@@ -118,6 +132,7 @@ the prefix (use `""` to remove it).
|
|||||||
- Logged-out socket => error instructs re-link.
|
- Logged-out socket => error instructs re-link.
|
||||||
|
|
||||||
## Inbound flow (DM + group)
|
## Inbound flow (DM + group)
|
||||||
|
|
||||||
- WhatsApp events come from `messages.upsert` (Baileys).
|
- WhatsApp events come from `messages.upsert` (Baileys).
|
||||||
- Inbox listeners are detached on shutdown to avoid accumulating event handlers in tests/restarts.
|
- Inbox listeners are detached on shutdown to avoid accumulating event handlers in tests/restarts.
|
||||||
- Status/broadcast chats are ignored.
|
- Status/broadcast chats are ignored.
|
||||||
@@ -128,38 +143,44 @@ the prefix (use `""` to remove it).
|
|||||||
- Your linked WhatsApp number is implicitly trusted, so self messages skip `channels.whatsapp.dmPolicy` and `channels.whatsapp.allowFrom` checks.
|
- Your linked WhatsApp number is implicitly trusted, so self messages skip `channels.whatsapp.dmPolicy` and `channels.whatsapp.allowFrom` checks.
|
||||||
|
|
||||||
### Personal-number mode (fallback)
|
### Personal-number mode (fallback)
|
||||||
|
|
||||||
If you run OpenClaw on your **personal WhatsApp number**, enable `channels.whatsapp.selfChatMode` (see sample above).
|
If you run OpenClaw on your **personal WhatsApp number**, enable `channels.whatsapp.selfChatMode` (see sample above).
|
||||||
|
|
||||||
Behavior:
|
Behavior:
|
||||||
|
|
||||||
- Outbound DMs never trigger pairing replies (prevents spamming contacts).
|
- Outbound DMs never trigger pairing replies (prevents spamming contacts).
|
||||||
- Inbound unknown senders still follow `channels.whatsapp.dmPolicy`.
|
- Inbound unknown senders still follow `channels.whatsapp.dmPolicy`.
|
||||||
- Self-chat mode (allowFrom includes your number) avoids auto read receipts and ignores mention JIDs.
|
- Self-chat mode (allowFrom includes your number) avoids auto read receipts and ignores mention JIDs.
|
||||||
- Read receipts sent for non-self-chat DMs.
|
- Read receipts sent for non-self-chat DMs.
|
||||||
|
|
||||||
## Read receipts
|
## Read receipts
|
||||||
|
|
||||||
By default, the gateway marks inbound WhatsApp messages as read (blue ticks) once they are accepted.
|
By default, the gateway marks inbound WhatsApp messages as read (blue ticks) once they are accepted.
|
||||||
|
|
||||||
Disable globally:
|
Disable globally:
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
channels: { whatsapp: { sendReadReceipts: false } }
|
channels: { whatsapp: { sendReadReceipts: false } },
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Disable per account:
|
Disable per account:
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
channels: {
|
channels: {
|
||||||
whatsapp: {
|
whatsapp: {
|
||||||
accounts: {
|
accounts: {
|
||||||
personal: { sendReadReceipts: false }
|
personal: { sendReadReceipts: false },
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
- Self-chat mode always skips read receipts.
|
- Self-chat mode always skips read receipts.
|
||||||
|
|
||||||
## WhatsApp FAQ: sending messages + pairing
|
## WhatsApp FAQ: sending messages + pairing
|
||||||
@@ -169,6 +190,7 @@ No. Default DM policy is **pairing**, so unknown senders only get a pairing code
|
|||||||
|
|
||||||
**How does pairing work on WhatsApp?**
|
**How does pairing work on WhatsApp?**
|
||||||
Pairing is a DM gate for unknown senders:
|
Pairing is a DM gate for unknown senders:
|
||||||
|
|
||||||
- First DM from a new sender returns a short code (message is not processed).
|
- First DM from a new sender returns a short code (message is not processed).
|
||||||
- Approve with: `openclaw pairing approve whatsapp <code>` (list with `openclaw pairing list whatsapp`).
|
- Approve with: `openclaw pairing approve whatsapp <code>` (list with `openclaw pairing list whatsapp`).
|
||||||
- Codes expire after 1 hour; pending requests are capped at 3 per channel.
|
- Codes expire after 1 hour; pending requests are capped at 3 per channel.
|
||||||
@@ -180,6 +202,7 @@ Yes, by routing each sender to a different agent via `bindings` (peer `kind: "dm
|
|||||||
The wizard uses it to set your **allowlist/owner** so your own DMs are permitted. It’s not used for auto-sending. If you run on your personal WhatsApp number, use that same number and enable `channels.whatsapp.selfChatMode`.
|
The wizard uses it to set your **allowlist/owner** so your own DMs are permitted. It’s not used for auto-sending. If you run on your personal WhatsApp number, use that same number and enable `channels.whatsapp.selfChatMode`.
|
||||||
|
|
||||||
## Message normalization (what the model sees)
|
## Message normalization (what the model sees)
|
||||||
|
|
||||||
- `Body` is the current message body with envelope.
|
- `Body` is the current message body with envelope.
|
||||||
- Quoted reply context is **always appended**:
|
- Quoted reply context is **always appended**:
|
||||||
```
|
```
|
||||||
@@ -195,6 +218,7 @@ The wizard uses it to set your **allowlist/owner** so your own DMs are permitted
|
|||||||
- `<media:image|video|audio|document|sticker>`
|
- `<media:image|video|audio|document|sticker>`
|
||||||
|
|
||||||
## Groups
|
## Groups
|
||||||
|
|
||||||
- Groups map to `agent:<agentId>:whatsapp:group:<jid>` sessions.
|
- Groups map to `agent:<agentId>:whatsapp:group:<jid>` sessions.
|
||||||
- Group policy: `channels.whatsapp.groupPolicy = open|disabled|allowlist` (default `allowlist`).
|
- Group policy: `channels.whatsapp.groupPolicy = open|disabled|allowlist` (default `allowlist`).
|
||||||
- Activation modes:
|
- Activation modes:
|
||||||
@@ -203,7 +227,7 @@ The wizard uses it to set your **allowlist/owner** so your own DMs are permitted
|
|||||||
- `/activation mention|always` is owner-only and must be sent as a standalone message.
|
- `/activation mention|always` is owner-only and must be sent as a standalone message.
|
||||||
- Owner = `channels.whatsapp.allowFrom` (or self E.164 if unset).
|
- Owner = `channels.whatsapp.allowFrom` (or self E.164 if unset).
|
||||||
- **History injection** (pending-only):
|
- **History injection** (pending-only):
|
||||||
- Recent *unprocessed* messages (default 50) inserted under:
|
- Recent _unprocessed_ messages (default 50) inserted under:
|
||||||
`[Chat messages since your last reply - for context]` (messages already in the session are not re-injected)
|
`[Chat messages since your last reply - for context]` (messages already in the session are not re-injected)
|
||||||
- Current message under:
|
- Current message under:
|
||||||
`[Current message - respond to this]`
|
`[Current message - respond to this]`
|
||||||
@@ -211,6 +235,7 @@ The wizard uses it to set your **allowlist/owner** so your own DMs are permitted
|
|||||||
- Group metadata cached 5 min (subject + participants).
|
- Group metadata cached 5 min (subject + participants).
|
||||||
|
|
||||||
## Reply delivery (threading)
|
## Reply delivery (threading)
|
||||||
|
|
||||||
- WhatsApp Web sends standard messages (no quoted reply threading in the current gateway).
|
- WhatsApp Web sends standard messages (no quoted reply threading in the current gateway).
|
||||||
- Reply tags are ignored on this channel.
|
- Reply tags are ignored on this channel.
|
||||||
|
|
||||||
@@ -219,6 +244,7 @@ The wizard uses it to set your **allowlist/owner** so your own DMs are permitted
|
|||||||
WhatsApp can automatically send emoji reactions to incoming messages immediately upon receipt, before the bot generates a reply. This provides instant feedback to users that their message was received.
|
WhatsApp can automatically send emoji reactions to incoming messages immediately upon receipt, before the bot generates a reply. This provides instant feedback to users that their message was received.
|
||||||
|
|
||||||
**Configuration:**
|
**Configuration:**
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"whatsapp": {
|
"whatsapp": {
|
||||||
@@ -232,6 +258,7 @@ WhatsApp can automatically send emoji reactions to incoming messages immediately
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Options:**
|
**Options:**
|
||||||
|
|
||||||
- `emoji` (string): Emoji to use for acknowledgment (e.g., "👀", "✅", "📨"). Empty or omitted = feature disabled.
|
- `emoji` (string): Emoji to use for acknowledgment (e.g., "👀", "✅", "📨"). Empty or omitted = feature disabled.
|
||||||
- `direct` (boolean, default: `true`): Send reactions in direct/DM chats.
|
- `direct` (boolean, default: `true`): Send reactions in direct/DM chats.
|
||||||
- `group` (string, default: `"mentions"`): Group chat behavior:
|
- `group` (string, default: `"mentions"`): Group chat behavior:
|
||||||
@@ -240,6 +267,7 @@ WhatsApp can automatically send emoji reactions to incoming messages immediately
|
|||||||
- `"never"`: Never react in groups
|
- `"never"`: Never react in groups
|
||||||
|
|
||||||
**Per-account override:**
|
**Per-account override:**
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"whatsapp": {
|
"whatsapp": {
|
||||||
@@ -257,6 +285,7 @@ WhatsApp can automatically send emoji reactions to incoming messages immediately
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Behavior notes:**
|
**Behavior notes:**
|
||||||
|
|
||||||
- Reactions are sent **immediately** upon message receipt, before typing indicators or bot replies.
|
- Reactions are sent **immediately** upon message receipt, before typing indicators or bot replies.
|
||||||
- In groups with `requireMention: false` (activation: always), `group: "mentions"` will react to all messages (not just @mentions).
|
- In groups with `requireMention: false` (activation: always), `group: "mentions"` will react to all messages (not just @mentions).
|
||||||
- Fire-and-forget: reaction failures are logged but don't prevent the bot from replying.
|
- Fire-and-forget: reaction failures are logged but don't prevent the bot from replying.
|
||||||
@@ -264,18 +293,21 @@ WhatsApp can automatically send emoji reactions to incoming messages immediately
|
|||||||
- WhatsApp ignores `messages.ackReaction`; use `channels.whatsapp.ackReaction` instead.
|
- WhatsApp ignores `messages.ackReaction`; use `channels.whatsapp.ackReaction` instead.
|
||||||
|
|
||||||
## Agent tool (reactions)
|
## Agent tool (reactions)
|
||||||
|
|
||||||
- Tool: `whatsapp` with `react` action (`chatJid`, `messageId`, `emoji`, optional `remove`).
|
- Tool: `whatsapp` with `react` action (`chatJid`, `messageId`, `emoji`, optional `remove`).
|
||||||
- Optional: `participant` (group sender), `fromMe` (reacting to your own message), `accountId` (multi-account).
|
- Optional: `participant` (group sender), `fromMe` (reacting to your own message), `accountId` (multi-account).
|
||||||
- Reaction removal semantics: see [/tools/reactions](/tools/reactions).
|
- Reaction removal semantics: see [/tools/reactions](/tools/reactions).
|
||||||
- Tool gating: `channels.whatsapp.actions.reactions` (default: enabled).
|
- Tool gating: `channels.whatsapp.actions.reactions` (default: enabled).
|
||||||
|
|
||||||
## Limits
|
## Limits
|
||||||
|
|
||||||
- Outbound text is chunked to `channels.whatsapp.textChunkLimit` (default 4000).
|
- Outbound text is chunked to `channels.whatsapp.textChunkLimit` (default 4000).
|
||||||
- Optional newline chunking: set `channels.whatsapp.chunkMode="newline"` to split on blank lines (paragraph boundaries) before length chunking.
|
- Optional newline chunking: set `channels.whatsapp.chunkMode="newline"` to split on blank lines (paragraph boundaries) before length chunking.
|
||||||
- Inbound media saves are capped by `channels.whatsapp.mediaMaxMb` (default 50 MB).
|
- Inbound media saves are capped by `channels.whatsapp.mediaMaxMb` (default 50 MB).
|
||||||
- Outbound media items are capped by `agents.defaults.mediaMaxMb` (default 5 MB).
|
- Outbound media items are capped by `agents.defaults.mediaMaxMb` (default 5 MB).
|
||||||
|
|
||||||
## Outbound send (text + media)
|
## Outbound send (text + media)
|
||||||
|
|
||||||
- Uses active web listener; error if gateway not running.
|
- Uses active web listener; error if gateway not running.
|
||||||
- Text chunking: 4k max per message (configurable via `channels.whatsapp.textChunkLimit`, optional `channels.whatsapp.chunkMode`).
|
- Text chunking: 4k max per message (configurable via `channels.whatsapp.textChunkLimit`, optional `channels.whatsapp.chunkMode`).
|
||||||
- Media:
|
- Media:
|
||||||
@@ -288,17 +320,21 @@ WhatsApp can automatically send emoji reactions to incoming messages immediately
|
|||||||
- Gateway: `send` params include `gifPlayback: true`
|
- Gateway: `send` params include `gifPlayback: true`
|
||||||
|
|
||||||
## Voice notes (PTT audio)
|
## Voice notes (PTT audio)
|
||||||
|
|
||||||
WhatsApp sends audio as **voice notes** (PTT bubble).
|
WhatsApp sends audio as **voice notes** (PTT bubble).
|
||||||
|
|
||||||
- Best results: OGG/Opus. OpenClaw rewrites `audio/ogg` to `audio/ogg; codecs=opus`.
|
- Best results: OGG/Opus. OpenClaw rewrites `audio/ogg` to `audio/ogg; codecs=opus`.
|
||||||
- `[[audio_as_voice]]` is ignored for WhatsApp (audio already ships as voice note).
|
- `[[audio_as_voice]]` is ignored for WhatsApp (audio already ships as voice note).
|
||||||
|
|
||||||
## Media limits + optimization
|
## Media limits + optimization
|
||||||
|
|
||||||
- Default outbound cap: 5 MB (per media item).
|
- Default outbound cap: 5 MB (per media item).
|
||||||
- Override: `agents.defaults.mediaMaxMb`.
|
- Override: `agents.defaults.mediaMaxMb`.
|
||||||
- Images are auto-optimized to JPEG under cap (resize + quality sweep).
|
- Images are auto-optimized to JPEG under cap (resize + quality sweep).
|
||||||
- Oversize media => error; media reply falls back to text warning.
|
- Oversize media => error; media reply falls back to text warning.
|
||||||
|
|
||||||
## Heartbeats
|
## Heartbeats
|
||||||
|
|
||||||
- **Gateway heartbeat** logs connection health (`web.heartbeatSeconds`, default 60s).
|
- **Gateway heartbeat** logs connection health (`web.heartbeatSeconds`, default 60s).
|
||||||
- **Agent heartbeat** can be configured per agent (`agents.list[].heartbeat`) or globally
|
- **Agent heartbeat** can be configured per agent (`agents.list[].heartbeat`) or globally
|
||||||
via `agents.defaults.heartbeat` (fallback when no per-agent entries are set).
|
via `agents.defaults.heartbeat` (fallback when no per-agent entries are set).
|
||||||
@@ -306,12 +342,14 @@ WhatsApp sends audio as **voice notes** (PTT bubble).
|
|||||||
- Delivery defaults to the last used channel (or configured target).
|
- Delivery defaults to the last used channel (or configured target).
|
||||||
|
|
||||||
## Reconnect behavior
|
## Reconnect behavior
|
||||||
|
|
||||||
- Backoff policy: `web.reconnect`:
|
- Backoff policy: `web.reconnect`:
|
||||||
- `initialMs`, `maxMs`, `factor`, `jitter`, `maxAttempts`.
|
- `initialMs`, `maxMs`, `factor`, `jitter`, `maxAttempts`.
|
||||||
- If maxAttempts reached, web monitoring stops (degraded).
|
- If maxAttempts reached, web monitoring stops (degraded).
|
||||||
- Logged-out => stop and require re-link.
|
- Logged-out => stop and require re-link.
|
||||||
|
|
||||||
## Config quick map
|
## Config quick map
|
||||||
|
|
||||||
- `channels.whatsapp.dmPolicy` (DM policy: pairing/allowlist/open/disabled).
|
- `channels.whatsapp.dmPolicy` (DM policy: pairing/allowlist/open/disabled).
|
||||||
- `channels.whatsapp.selfChatMode` (same-phone setup; bot uses your personal WhatsApp number).
|
- `channels.whatsapp.selfChatMode` (same-phone setup; bot uses your personal WhatsApp number).
|
||||||
- `channels.whatsapp.allowFrom` (DM allowlist). WhatsApp uses E.164 phone numbers (no usernames).
|
- `channels.whatsapp.allowFrom` (DM allowlist). WhatsApp uses E.164 phone numbers (no usernames).
|
||||||
@@ -343,6 +381,7 @@ WhatsApp sends audio as **voice notes** (PTT bubble).
|
|||||||
- `web.reconnect.*`
|
- `web.reconnect.*`
|
||||||
|
|
||||||
## Logs + troubleshooting
|
## Logs + troubleshooting
|
||||||
|
|
||||||
- Subsystems: `whatsapp/inbound`, `whatsapp/outbound`, `web-heartbeat`, `web-reconnect`.
|
- Subsystems: `whatsapp/inbound`, `whatsapp/outbound`, `web-heartbeat`, `web-reconnect`.
|
||||||
- Log file: `/tmp/openclaw/openclaw-YYYY-MM-DD.log` (configurable).
|
- Log file: `/tmp/openclaw/openclaw-YYYY-MM-DD.log` (configurable).
|
||||||
- Troubleshooting guide: [Gateway troubleshooting](/gateway/troubleshooting).
|
- Troubleshooting guide: [Gateway troubleshooting](/gateway/troubleshooting).
|
||||||
@@ -350,13 +389,16 @@ WhatsApp sends audio as **voice notes** (PTT bubble).
|
|||||||
## Troubleshooting (quick)
|
## Troubleshooting (quick)
|
||||||
|
|
||||||
**Not linked / QR login required**
|
**Not linked / QR login required**
|
||||||
|
|
||||||
- Symptom: `channels status` shows `linked: false` or warns “Not linked”.
|
- Symptom: `channels status` shows `linked: false` or warns “Not linked”.
|
||||||
- Fix: run `openclaw channels login` on the gateway host and scan the QR (WhatsApp → Settings → Linked Devices).
|
- Fix: run `openclaw channels login` on the gateway host and scan the QR (WhatsApp → Settings → Linked Devices).
|
||||||
|
|
||||||
**Linked but disconnected / reconnect loop**
|
**Linked but disconnected / reconnect loop**
|
||||||
|
|
||||||
- Symptom: `channels status` shows `running, disconnected` or warns “Linked but disconnected”.
|
- Symptom: `channels status` shows `running, disconnected` or warns “Linked but disconnected”.
|
||||||
- Fix: `openclaw doctor` (or restart the gateway). If it persists, relink via `channels login` and inspect `openclaw logs --follow`.
|
- Fix: `openclaw doctor` (or restart the gateway). If it persists, relink via `channels login` and inspect `openclaw logs --follow`.
|
||||||
|
|
||||||
**Bun runtime**
|
**Bun runtime**
|
||||||
|
|
||||||
- Bun is **not recommended**. WhatsApp (Baileys) and Telegram are unreliable on Bun.
|
- Bun is **not recommended**. WhatsApp (Baileys) and Telegram are unreliable on Bun.
|
||||||
Run the gateway with **Node**. (See Getting Started runtime note.)
|
Run the gateway with **Node**. (See Getting Started runtime note.)
|
||||||
|
|||||||
@@ -2,44 +2,52 @@
|
|||||||
summary: "Zalo bot support status, capabilities, and configuration"
|
summary: "Zalo bot support status, capabilities, and configuration"
|
||||||
read_when:
|
read_when:
|
||||||
- Working on Zalo features or webhooks
|
- Working on Zalo features or webhooks
|
||||||
|
title: "Zalo"
|
||||||
---
|
---
|
||||||
|
|
||||||
# Zalo (Bot API)
|
# Zalo (Bot API)
|
||||||
|
|
||||||
Status: experimental. Direct messages only; groups coming soon per Zalo docs.
|
Status: experimental. Direct messages only; groups coming soon per Zalo docs.
|
||||||
|
|
||||||
## Plugin required
|
## Plugin required
|
||||||
|
|
||||||
Zalo ships as a plugin and is not bundled with the core install.
|
Zalo ships as a plugin and is not bundled with the core install.
|
||||||
|
|
||||||
- Install via CLI: `openclaw plugins install @openclaw/zalo`
|
- Install via CLI: `openclaw plugins install @openclaw/zalo`
|
||||||
- Or select **Zalo** during onboarding and confirm the install prompt
|
- Or select **Zalo** during onboarding and confirm the install prompt
|
||||||
- Details: [Plugins](/plugin)
|
- Details: [Plugins](/plugin)
|
||||||
|
|
||||||
## Quick setup (beginner)
|
## Quick setup (beginner)
|
||||||
1) Install the Zalo plugin:
|
|
||||||
|
1. Install the Zalo plugin:
|
||||||
- From a source checkout: `openclaw plugins install ./extensions/zalo`
|
- From a source checkout: `openclaw plugins install ./extensions/zalo`
|
||||||
- From npm (if published): `openclaw plugins install @openclaw/zalo`
|
- From npm (if published): `openclaw plugins install @openclaw/zalo`
|
||||||
- Or pick **Zalo** in onboarding and confirm the install prompt
|
- Or pick **Zalo** in onboarding and confirm the install prompt
|
||||||
2) Set the token:
|
2. Set the token:
|
||||||
- Env: `ZALO_BOT_TOKEN=...`
|
- Env: `ZALO_BOT_TOKEN=...`
|
||||||
- Or config: `channels.zalo.botToken: "..."`.
|
- Or config: `channels.zalo.botToken: "..."`.
|
||||||
3) Restart the gateway (or finish onboarding).
|
3. Restart the gateway (or finish onboarding).
|
||||||
4) DM access is pairing by default; approve the pairing code on first contact.
|
4. DM access is pairing by default; approve the pairing code on first contact.
|
||||||
|
|
||||||
Minimal config:
|
Minimal config:
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
channels: {
|
channels: {
|
||||||
zalo: {
|
zalo: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
botToken: "12345689:abc-xyz",
|
botToken: "12345689:abc-xyz",
|
||||||
dmPolicy: "pairing"
|
dmPolicy: "pairing",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## What it is
|
## What it is
|
||||||
|
|
||||||
Zalo is a Vietnam-focused messaging app; its Bot API lets the Gateway run a bot for 1:1 conversations.
|
Zalo is a Vietnam-focused messaging app; its Bot API lets the Gateway run a bot for 1:1 conversations.
|
||||||
It is a good fit for support or notifications where you want deterministic routing back to Zalo.
|
It is a good fit for support or notifications where you want deterministic routing back to Zalo.
|
||||||
|
|
||||||
- A Zalo Bot API channel owned by the Gateway.
|
- A Zalo Bot API channel owned by the Gateway.
|
||||||
- Deterministic routing: replies go back to Zalo; the model never chooses channels.
|
- Deterministic routing: replies go back to Zalo; the model never chooses channels.
|
||||||
- DMs share the agent's main session.
|
- DMs share the agent's main session.
|
||||||
@@ -48,11 +56,13 @@ It is a good fit for support or notifications where you want deterministic routi
|
|||||||
## Setup (fast path)
|
## Setup (fast path)
|
||||||
|
|
||||||
### 1) Create a bot token (Zalo Bot Platform)
|
### 1) Create a bot token (Zalo Bot Platform)
|
||||||
1) Go to **https://bot.zaloplatforms.com** and sign in.
|
|
||||||
2) Create a new bot and configure its settings.
|
1. Go to **https://bot.zaloplatforms.com** and sign in.
|
||||||
3) Copy the bot token (format: `12345689:abc-xyz`).
|
2. Create a new bot and configure its settings.
|
||||||
|
3. Copy the bot token (format: `12345689:abc-xyz`).
|
||||||
|
|
||||||
### 2) Configure the token (env or config)
|
### 2) Configure the token (env or config)
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
@@ -61,9 +71,9 @@ Example:
|
|||||||
zalo: {
|
zalo: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
botToken: "12345689:abc-xyz",
|
botToken: "12345689:abc-xyz",
|
||||||
dmPolicy: "pairing"
|
dmPolicy: "pairing",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -71,15 +81,17 @@ Env option: `ZALO_BOT_TOKEN=...` (works for the default account only).
|
|||||||
|
|
||||||
Multi-account support: use `channels.zalo.accounts` with per-account tokens and optional `name`.
|
Multi-account support: use `channels.zalo.accounts` with per-account tokens and optional `name`.
|
||||||
|
|
||||||
3) Restart the gateway. Zalo starts when a token is resolved (env or config).
|
3. Restart the gateway. Zalo starts when a token is resolved (env or config).
|
||||||
4) DM access defaults to pairing. Approve the code when the bot is first contacted.
|
4. DM access defaults to pairing. Approve the code when the bot is first contacted.
|
||||||
|
|
||||||
## How it works (behavior)
|
## How it works (behavior)
|
||||||
|
|
||||||
- Inbound messages are normalized into the shared channel envelope with media placeholders.
|
- Inbound messages are normalized into the shared channel envelope with media placeholders.
|
||||||
- Replies always route back to the same Zalo chat.
|
- Replies always route back to the same Zalo chat.
|
||||||
- Long-polling by default; webhook mode available with `channels.zalo.webhookUrl`.
|
- Long-polling by default; webhook mode available with `channels.zalo.webhookUrl`.
|
||||||
|
|
||||||
## Limits
|
## Limits
|
||||||
|
|
||||||
- Outbound text is chunked to 2000 characters (Zalo API limit).
|
- Outbound text is chunked to 2000 characters (Zalo API limit).
|
||||||
- Media downloads/uploads are capped by `channels.zalo.mediaMaxMb` (default 5).
|
- Media downloads/uploads are capped by `channels.zalo.mediaMaxMb` (default 5).
|
||||||
- Streaming is blocked by default due to the 2000 char limit making streaming less useful.
|
- Streaming is blocked by default due to the 2000 char limit making streaming less useful.
|
||||||
@@ -87,6 +99,7 @@ Multi-account support: use `channels.zalo.accounts` with per-account tokens and
|
|||||||
## Access control (DMs)
|
## Access control (DMs)
|
||||||
|
|
||||||
### DM access
|
### DM access
|
||||||
|
|
||||||
- Default: `channels.zalo.dmPolicy = "pairing"`. Unknown senders receive a pairing code; messages are ignored until approved (codes expire after 1 hour).
|
- Default: `channels.zalo.dmPolicy = "pairing"`. Unknown senders receive a pairing code; messages are ignored until approved (codes expire after 1 hour).
|
||||||
- Approve via:
|
- Approve via:
|
||||||
- `openclaw pairing list zalo`
|
- `openclaw pairing list zalo`
|
||||||
@@ -95,6 +108,7 @@ Multi-account support: use `channels.zalo.accounts` with per-account tokens and
|
|||||||
- `channels.zalo.allowFrom` accepts numeric user IDs (no username lookup available).
|
- `channels.zalo.allowFrom` accepts numeric user IDs (no username lookup available).
|
||||||
|
|
||||||
## Long-polling vs webhook
|
## Long-polling vs webhook
|
||||||
|
|
||||||
- Default: long-polling (no public URL required).
|
- Default: long-polling (no public URL required).
|
||||||
- Webhook mode: set `channels.zalo.webhookUrl` and `channels.zalo.webhookSecret`.
|
- Webhook mode: set `channels.zalo.webhookUrl` and `channels.zalo.webhookSecret`.
|
||||||
- The webhook secret must be 8-256 characters.
|
- The webhook secret must be 8-256 characters.
|
||||||
@@ -105,44 +119,51 @@ Multi-account support: use `channels.zalo.accounts` with per-account tokens and
|
|||||||
**Note:** getUpdates (polling) and webhook are mutually exclusive per Zalo API docs.
|
**Note:** getUpdates (polling) and webhook are mutually exclusive per Zalo API docs.
|
||||||
|
|
||||||
## Supported message types
|
## Supported message types
|
||||||
|
|
||||||
- **Text messages**: Full support with 2000 character chunking.
|
- **Text messages**: Full support with 2000 character chunking.
|
||||||
- **Image messages**: Download and process inbound images; send images via `sendPhoto`.
|
- **Image messages**: Download and process inbound images; send images via `sendPhoto`.
|
||||||
- **Stickers**: Logged but not fully processed (no agent response).
|
- **Stickers**: Logged but not fully processed (no agent response).
|
||||||
- **Unsupported types**: Logged (e.g., messages from protected users).
|
- **Unsupported types**: Logged (e.g., messages from protected users).
|
||||||
|
|
||||||
## Capabilities
|
## Capabilities
|
||||||
| Feature | Status |
|
|
||||||
|---------|--------|
|
| Feature | Status |
|
||||||
| Direct messages | ✅ Supported |
|
| --------------- | ------------------------------ |
|
||||||
| Groups | ❌ Coming soon (per Zalo docs) |
|
| Direct messages | ✅ Supported |
|
||||||
| Media (images) | ✅ Supported |
|
| Groups | ❌ Coming soon (per Zalo docs) |
|
||||||
| Reactions | ❌ Not supported |
|
| Media (images) | ✅ Supported |
|
||||||
| Threads | ❌ Not supported |
|
| Reactions | ❌ Not supported |
|
||||||
| Polls | ❌ Not supported |
|
| Threads | ❌ Not supported |
|
||||||
| Native commands | ❌ Not supported |
|
| Polls | ❌ Not supported |
|
||||||
| Streaming | ⚠️ Blocked (2000 char limit) |
|
| Native commands | ❌ Not supported |
|
||||||
|
| Streaming | ⚠️ Blocked (2000 char limit) |
|
||||||
|
|
||||||
## Delivery targets (CLI/cron)
|
## Delivery targets (CLI/cron)
|
||||||
|
|
||||||
- Use a chat id as the target.
|
- Use a chat id as the target.
|
||||||
- Example: `openclaw message send --channel zalo --target 123456789 --message "hi"`.
|
- Example: `openclaw message send --channel zalo --target 123456789 --message "hi"`.
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
**Bot doesn't respond:**
|
**Bot doesn't respond:**
|
||||||
|
|
||||||
- Check that the token is valid: `openclaw channels status --probe`
|
- Check that the token is valid: `openclaw channels status --probe`
|
||||||
- Verify the sender is approved (pairing or allowFrom)
|
- Verify the sender is approved (pairing or allowFrom)
|
||||||
- Check gateway logs: `openclaw logs --follow`
|
- Check gateway logs: `openclaw logs --follow`
|
||||||
|
|
||||||
**Webhook not receiving events:**
|
**Webhook not receiving events:**
|
||||||
|
|
||||||
- Ensure webhook URL uses HTTPS
|
- Ensure webhook URL uses HTTPS
|
||||||
- Verify secret token is 8-256 characters
|
- Verify secret token is 8-256 characters
|
||||||
- Confirm the gateway HTTP endpoint is reachable on the configured path
|
- Confirm the gateway HTTP endpoint is reachable on the configured path
|
||||||
- Check that getUpdates polling is not running (they're mutually exclusive)
|
- Check that getUpdates polling is not running (they're mutually exclusive)
|
||||||
|
|
||||||
## Configuration reference (Zalo)
|
## Configuration reference (Zalo)
|
||||||
|
|
||||||
Full configuration: [Configuration](/gateway/configuration)
|
Full configuration: [Configuration](/gateway/configuration)
|
||||||
|
|
||||||
Provider options:
|
Provider options:
|
||||||
|
|
||||||
- `channels.zalo.enabled`: enable/disable channel startup.
|
- `channels.zalo.enabled`: enable/disable channel startup.
|
||||||
- `channels.zalo.botToken`: bot token from Zalo Bot Platform.
|
- `channels.zalo.botToken`: bot token from Zalo Bot Platform.
|
||||||
- `channels.zalo.tokenFile`: read token from file path.
|
- `channels.zalo.tokenFile`: read token from file path.
|
||||||
@@ -155,6 +176,7 @@ Provider options:
|
|||||||
- `channels.zalo.proxy`: proxy URL for API requests.
|
- `channels.zalo.proxy`: proxy URL for API requests.
|
||||||
|
|
||||||
Multi-account options:
|
Multi-account options:
|
||||||
|
|
||||||
- `channels.zalo.accounts.<id>.botToken`: per-account token.
|
- `channels.zalo.accounts.<id>.botToken`: per-account token.
|
||||||
- `channels.zalo.accounts.<id>.tokenFile`: per-account token file.
|
- `channels.zalo.accounts.<id>.tokenFile`: per-account token file.
|
||||||
- `channels.zalo.accounts.<id>.name`: display name.
|
- `channels.zalo.accounts.<id>.name`: display name.
|
||||||
|
|||||||
@@ -3,7 +3,9 @@ summary: "Zalo personal account support via zca-cli (QR login), capabilities, an
|
|||||||
read_when:
|
read_when:
|
||||||
- Setting up Zalo Personal for OpenClaw
|
- Setting up Zalo Personal for OpenClaw
|
||||||
- Debugging Zalo Personal login or message flow
|
- Debugging Zalo Personal login or message flow
|
||||||
|
title: "Zalo Personal"
|
||||||
---
|
---
|
||||||
|
|
||||||
# Zalo Personal (unofficial)
|
# Zalo Personal (unofficial)
|
||||||
|
|
||||||
Status: experimental. This integration automates a **personal Zalo account** via `zca-cli`.
|
Status: experimental. This integration automates a **personal Zalo account** via `zca-cli`.
|
||||||
@@ -11,47 +13,54 @@ Status: experimental. This integration automates a **personal Zalo account** via
|
|||||||
> **Warning:** This is an unofficial integration and may result in account suspension/ban. Use at your own risk.
|
> **Warning:** This is an unofficial integration and may result in account suspension/ban. Use at your own risk.
|
||||||
|
|
||||||
## Plugin required
|
## Plugin required
|
||||||
|
|
||||||
Zalo Personal ships as a plugin and is not bundled with the core install.
|
Zalo Personal ships as a plugin and is not bundled with the core install.
|
||||||
|
|
||||||
- Install via CLI: `openclaw plugins install @openclaw/zalouser`
|
- Install via CLI: `openclaw plugins install @openclaw/zalouser`
|
||||||
- Or from a source checkout: `openclaw plugins install ./extensions/zalouser`
|
- Or from a source checkout: `openclaw plugins install ./extensions/zalouser`
|
||||||
- Details: [Plugins](/plugin)
|
- Details: [Plugins](/plugin)
|
||||||
|
|
||||||
## Prerequisite: zca-cli
|
## Prerequisite: zca-cli
|
||||||
|
|
||||||
The Gateway machine must have the `zca` binary available in `PATH`.
|
The Gateway machine must have the `zca` binary available in `PATH`.
|
||||||
|
|
||||||
- Verify: `zca --version`
|
- Verify: `zca --version`
|
||||||
- If missing, install zca-cli (see `extensions/zalouser/README.md` or the upstream zca-cli docs).
|
- If missing, install zca-cli (see `extensions/zalouser/README.md` or the upstream zca-cli docs).
|
||||||
|
|
||||||
## Quick setup (beginner)
|
## Quick setup (beginner)
|
||||||
1) Install the plugin (see above).
|
|
||||||
2) Login (QR, on the Gateway machine):
|
1. Install the plugin (see above).
|
||||||
|
2. Login (QR, on the Gateway machine):
|
||||||
- `openclaw channels login --channel zalouser`
|
- `openclaw channels login --channel zalouser`
|
||||||
- Scan the QR code in the terminal with the Zalo mobile app.
|
- Scan the QR code in the terminal with the Zalo mobile app.
|
||||||
3) Enable the channel:
|
3. Enable the channel:
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
channels: {
|
channels: {
|
||||||
zalouser: {
|
zalouser: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
dmPolicy: "pairing"
|
dmPolicy: "pairing",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
4) Restart the Gateway (or finish onboarding).
|
4. Restart the Gateway (or finish onboarding).
|
||||||
5) DM access defaults to pairing; approve the pairing code on first contact.
|
5. DM access defaults to pairing; approve the pairing code on first contact.
|
||||||
|
|
||||||
## What it is
|
## What it is
|
||||||
|
|
||||||
- Uses `zca listen` to receive inbound messages.
|
- Uses `zca listen` to receive inbound messages.
|
||||||
- Uses `zca msg ...` to send replies (text/media/link).
|
- Uses `zca msg ...` to send replies (text/media/link).
|
||||||
- Designed for “personal account” use cases where Zalo Bot API is not available.
|
- Designed for “personal account” use cases where Zalo Bot API is not available.
|
||||||
|
|
||||||
## Naming
|
## Naming
|
||||||
|
|
||||||
Channel id is `zalouser` to make it explicit this automates a **personal Zalo user account** (unofficial). We keep `zalo` reserved for a potential future official Zalo API integration.
|
Channel id is `zalouser` to make it explicit this automates a **personal Zalo user account** (unofficial). We keep `zalo` reserved for a potential future official Zalo API integration.
|
||||||
|
|
||||||
## Finding IDs (directory)
|
## Finding IDs (directory)
|
||||||
|
|
||||||
Use the directory CLI to discover peers/groups and their IDs:
|
Use the directory CLI to discover peers/groups and their IDs:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -61,18 +70,22 @@ openclaw directory groups list --channel zalouser --query "work"
|
|||||||
```
|
```
|
||||||
|
|
||||||
## Limits
|
## Limits
|
||||||
|
|
||||||
- Outbound text is chunked to ~2000 characters (Zalo client limits).
|
- Outbound text is chunked to ~2000 characters (Zalo client limits).
|
||||||
- Streaming is blocked by default.
|
- Streaming is blocked by default.
|
||||||
|
|
||||||
## Access control (DMs)
|
## Access control (DMs)
|
||||||
|
|
||||||
`channels.zalouser.dmPolicy` supports: `pairing | allowlist | open | disabled` (default: `pairing`).
|
`channels.zalouser.dmPolicy` supports: `pairing | allowlist | open | disabled` (default: `pairing`).
|
||||||
`channels.zalouser.allowFrom` accepts user IDs or names. The wizard resolves names to IDs via `zca friend find` when available.
|
`channels.zalouser.allowFrom` accepts user IDs or names. The wizard resolves names to IDs via `zca friend find` when available.
|
||||||
|
|
||||||
Approve via:
|
Approve via:
|
||||||
|
|
||||||
- `openclaw pairing list zalouser`
|
- `openclaw pairing list zalouser`
|
||||||
- `openclaw pairing approve zalouser <code>`
|
- `openclaw pairing approve zalouser <code>`
|
||||||
|
|
||||||
## Group access (optional)
|
## Group access (optional)
|
||||||
|
|
||||||
- Default: `channels.zalouser.groupPolicy = "open"` (groups allowed). Use `channels.defaults.groupPolicy` to override the default when unset.
|
- Default: `channels.zalouser.groupPolicy = "open"` (groups allowed). Use `channels.defaults.groupPolicy` to override the default when unset.
|
||||||
- Restrict to an allowlist with:
|
- Restrict to an allowlist with:
|
||||||
- `channels.zalouser.groupPolicy = "allowlist"`
|
- `channels.zalouser.groupPolicy = "allowlist"`
|
||||||
@@ -82,6 +95,7 @@ Approve via:
|
|||||||
- On startup, OpenClaw resolves group/user names in allowlists to IDs and logs the mapping; unresolved entries are kept as typed.
|
- On startup, OpenClaw resolves group/user names in allowlists to IDs and logs the mapping; unresolved entries are kept as typed.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
channels: {
|
channels: {
|
||||||
@@ -89,14 +103,15 @@ Example:
|
|||||||
groupPolicy: "allowlist",
|
groupPolicy: "allowlist",
|
||||||
groups: {
|
groups: {
|
||||||
"123456789": { allow: true },
|
"123456789": { allow: true },
|
||||||
"Work Chat": { allow: true }
|
"Work Chat": { allow: true },
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Multi-account
|
## Multi-account
|
||||||
|
|
||||||
Accounts map to zca profiles. Example:
|
Accounts map to zca profiles. Example:
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
@@ -106,18 +121,20 @@ Accounts map to zca profiles. Example:
|
|||||||
enabled: true,
|
enabled: true,
|
||||||
defaultAccount: "default",
|
defaultAccount: "default",
|
||||||
accounts: {
|
accounts: {
|
||||||
work: { enabled: true, profile: "work" }
|
work: { enabled: true, profile: "work" },
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
**`zca` not found:**
|
**`zca` not found:**
|
||||||
|
|
||||||
- Install zca-cli and ensure it’s on `PATH` for the Gateway process.
|
- Install zca-cli and ensure it’s on `PATH` for the Gateway process.
|
||||||
|
|
||||||
**Login doesn’t stick:**
|
**Login doesn’t stick:**
|
||||||
|
|
||||||
- `openclaw channels status --probe`
|
- `openclaw channels status --probe`
|
||||||
- Re-login: `openclaw channels logout --channel zalouser && openclaw channels login --channel zalouser`
|
- Re-login: `openclaw channels logout --channel zalouser && openclaw channels login --channel zalouser`
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ summary: "Run the ACP bridge for IDE integrations"
|
|||||||
read_when:
|
read_when:
|
||||||
- Setting up ACP-based IDE integrations
|
- Setting up ACP-based IDE integrations
|
||||||
- Debugging ACP session routing to the Gateway
|
- Debugging ACP session routing to the Gateway
|
||||||
|
title: "acp"
|
||||||
---
|
---
|
||||||
|
|
||||||
# acp
|
# acp
|
||||||
@@ -110,9 +111,12 @@ To target a specific Gateway or agent:
|
|||||||
"command": "openclaw",
|
"command": "openclaw",
|
||||||
"args": [
|
"args": [
|
||||||
"acp",
|
"acp",
|
||||||
"--url", "wss://gateway-host:18789",
|
"--url",
|
||||||
"--token", "<token>",
|
"wss://gateway-host:18789",
|
||||||
"--session", "agent:design:main"
|
"--token",
|
||||||
|
"<token>",
|
||||||
|
"--session",
|
||||||
|
"agent:design:main"
|
||||||
],
|
],
|
||||||
"env": {}
|
"env": {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
summary: "CLI reference for `openclaw agent` (send one agent turn via the Gateway)"
|
summary: "CLI reference for `openclaw agent` (send one agent turn via the Gateway)"
|
||||||
read_when:
|
read_when:
|
||||||
- You want to run one agent turn from scripts (optionally deliver reply)
|
- You want to run one agent turn from scripts (optionally deliver reply)
|
||||||
|
title: "agent"
|
||||||
---
|
---
|
||||||
|
|
||||||
# `openclaw agent`
|
# `openclaw agent`
|
||||||
@@ -10,6 +11,7 @@ Run an agent turn via the Gateway (use `--local` for embedded).
|
|||||||
Use `--agent <id>` to target a configured agent directly.
|
Use `--agent <id>` to target a configured agent directly.
|
||||||
|
|
||||||
Related:
|
Related:
|
||||||
|
|
||||||
- Agent send tool: [Agent send](/tools/agent-send)
|
- Agent send tool: [Agent send](/tools/agent-send)
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
summary: "CLI reference for `openclaw agents` (list/add/delete/set identity)"
|
summary: "CLI reference for `openclaw agents` (list/add/delete/set identity)"
|
||||||
read_when:
|
read_when:
|
||||||
- You want multiple isolated agents (workspaces + routing + auth)
|
- You want multiple isolated agents (workspaces + routing + auth)
|
||||||
|
title: "agents"
|
||||||
---
|
---
|
||||||
|
|
||||||
# `openclaw agents`
|
# `openclaw agents`
|
||||||
@@ -9,6 +10,7 @@ read_when:
|
|||||||
Manage isolated agents (workspaces + auth + routing).
|
Manage isolated agents (workspaces + auth + routing).
|
||||||
|
|
||||||
Related:
|
Related:
|
||||||
|
|
||||||
- Multi-agent routing: [Multi-Agent Routing](/concepts/multi-agent)
|
- Multi-agent routing: [Multi-Agent Routing](/concepts/multi-agent)
|
||||||
- Agent workspace: [Agent workspace](/concepts/agent-workspace)
|
- Agent workspace: [Agent workspace](/concepts/agent-workspace)
|
||||||
|
|
||||||
@@ -25,6 +27,7 @@ openclaw agents delete work
|
|||||||
## Identity files
|
## Identity files
|
||||||
|
|
||||||
Each agent workspace can include an `IDENTITY.md` at the workspace root:
|
Each agent workspace can include an `IDENTITY.md` at the workspace root:
|
||||||
|
|
||||||
- Example path: `~/.openclaw/workspace/IDENTITY.md`
|
- Example path: `~/.openclaw/workspace/IDENTITY.md`
|
||||||
- `set-identity --from-identity` reads from the workspace root (or an explicit `--identity-file`)
|
- `set-identity --from-identity` reads from the workspace root (or an explicit `--identity-file`)
|
||||||
|
|
||||||
@@ -33,6 +36,7 @@ Avatar paths resolve relative to the workspace root.
|
|||||||
## Set identity
|
## Set identity
|
||||||
|
|
||||||
`set-identity` writes fields into `agents.list[].identity`:
|
`set-identity` writes fields into `agents.list[].identity`:
|
||||||
|
|
||||||
- `name`
|
- `name`
|
||||||
- `theme`
|
- `theme`
|
||||||
- `emoji`
|
- `emoji`
|
||||||
@@ -62,10 +66,10 @@ Config sample:
|
|||||||
name: "OpenClaw",
|
name: "OpenClaw",
|
||||||
theme: "space lobster",
|
theme: "space lobster",
|
||||||
emoji: "🦞",
|
emoji: "🦞",
|
||||||
avatar: "avatars/openclaw.png"
|
avatar: "avatars/openclaw.png",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ summary: "CLI reference for `openclaw approvals` (exec approvals for gateway or
|
|||||||
read_when:
|
read_when:
|
||||||
- You want to edit exec approvals from the CLI
|
- You want to edit exec approvals from the CLI
|
||||||
- You need to manage allowlists on gateway or node hosts
|
- You need to manage allowlists on gateway or node hosts
|
||||||
|
title: "approvals"
|
||||||
---
|
---
|
||||||
|
|
||||||
# `openclaw approvals`
|
# `openclaw approvals`
|
||||||
@@ -11,6 +12,7 @@ Manage exec approvals for the **local host**, **gateway host**, or a **node host
|
|||||||
By default, commands target the local approvals file on disk. Use `--gateway` to target the gateway, or `--node` to target a specific node.
|
By default, commands target the local approvals file on disk. Use `--gateway` to target the gateway, or `--node` to target a specific node.
|
||||||
|
|
||||||
Related:
|
Related:
|
||||||
|
|
||||||
- Exec approvals: [Exec approvals](/tools/exec-approvals)
|
- Exec approvals: [Exec approvals](/tools/exec-approvals)
|
||||||
- Nodes: [Nodes](/nodes)
|
- Nodes: [Nodes](/nodes)
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ read_when:
|
|||||||
- You use `openclaw browser` and want examples for common tasks
|
- You use `openclaw browser` and want examples for common tasks
|
||||||
- You want to control a browser running on another machine via a node host
|
- You want to control a browser running on another machine via a node host
|
||||||
- You want to use the Chrome extension relay (attach/detach via toolbar button)
|
- You want to use the Chrome extension relay (attach/detach via toolbar button)
|
||||||
|
title: "browser"
|
||||||
---
|
---
|
||||||
|
|
||||||
# `openclaw browser`
|
# `openclaw browser`
|
||||||
@@ -11,6 +12,7 @@ read_when:
|
|||||||
Manage OpenClaw’s browser control server and run browser actions (tabs, snapshots, screenshots, navigation, clicks, typing).
|
Manage OpenClaw’s browser control server and run browser actions (tabs, snapshots, screenshots, navigation, clicks, typing).
|
||||||
|
|
||||||
Related:
|
Related:
|
||||||
|
|
||||||
- Browser tool + API: [Browser tool](/tools/browser)
|
- Browser tool + API: [Browser tool](/tools/browser)
|
||||||
- Chrome extension relay: [Chrome extension](/tools/chrome-extension)
|
- Chrome extension relay: [Chrome extension](/tools/chrome-extension)
|
||||||
|
|
||||||
@@ -34,6 +36,7 @@ openclaw browser --browser-profile openclaw snapshot
|
|||||||
## Profiles
|
## Profiles
|
||||||
|
|
||||||
Profiles are named browser routing configs. In practice:
|
Profiles are named browser routing configs. In practice:
|
||||||
|
|
||||||
- `openclaw`: launches/attaches to a dedicated OpenClaw-managed Chrome instance (isolated user data dir).
|
- `openclaw`: launches/attaches to a dedicated OpenClaw-managed Chrome instance (isolated user data dir).
|
||||||
- `chrome`: controls your existing Chrome tab(s) via the Chrome extension relay.
|
- `chrome`: controls your existing Chrome tab(s) via the Chrome extension relay.
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ summary: "CLI reference for `openclaw channels` (accounts, status, login/logout,
|
|||||||
read_when:
|
read_when:
|
||||||
- You want to add/remove channel accounts (WhatsApp/Telegram/Discord/Google Chat/Slack/Mattermost (plugin)/Signal/iMessage)
|
- You want to add/remove channel accounts (WhatsApp/Telegram/Discord/Google Chat/Slack/Mattermost (plugin)/Signal/iMessage)
|
||||||
- You want to check channel status or tail channel logs
|
- You want to check channel status or tail channel logs
|
||||||
|
title: "channels"
|
||||||
---
|
---
|
||||||
|
|
||||||
# `openclaw channels`
|
# `openclaw channels`
|
||||||
@@ -10,6 +11,7 @@ read_when:
|
|||||||
Manage chat channel accounts and their runtime status on the Gateway.
|
Manage chat channel accounts and their runtime status on the Gateway.
|
||||||
|
|
||||||
Related docs:
|
Related docs:
|
||||||
|
|
||||||
- Channel guides: [Channels](/channels/index)
|
- Channel guides: [Channels](/channels/index)
|
||||||
- Gateway configuration: [Configuration](/gateway/configuration)
|
- Gateway configuration: [Configuration](/gateway/configuration)
|
||||||
|
|
||||||
@@ -56,6 +58,7 @@ openclaw channels capabilities --channel discord --target channel:123
|
|||||||
```
|
```
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
- `--channel` is optional; omit it to list every channel (including extensions).
|
- `--channel` is optional; omit it to list every channel (including extensions).
|
||||||
- `--target` accepts `channel:<id>` or a raw numeric channel id and only applies to Discord.
|
- `--target` accepts `channel:<id>` or a raw numeric channel id and only applies to Discord.
|
||||||
- Probes are provider-specific: Discord intents + optional channel permissions; Slack bot + user scopes; Telegram bot flags + webhook; Signal daemon version; MS Teams app token + Graph roles/scopes (annotated where known). Channels without probes report `Probe: unavailable`.
|
- Probes are provider-specific: Discord intents + optional channel permissions; Slack bot + user scopes; Telegram bot flags + webhook; Signal daemon version; MS Teams app token + Graph roles/scopes (annotated where known). Channels without probes report `Probe: unavailable`.
|
||||||
@@ -71,5 +74,6 @@ openclaw channels resolve --channel matrix "Project Room"
|
|||||||
```
|
```
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
- Use `--kind user|group|auto` to force the target type.
|
- Use `--kind user|group|auto` to force the target type.
|
||||||
- Resolution prefers active matches when multiple entries share the same name.
|
- Resolution prefers active matches when multiple entries share the same name.
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
summary: "CLI reference for `openclaw config` (get/set/unset config values)"
|
summary: "CLI reference for `openclaw config` (get/set/unset config values)"
|
||||||
read_when:
|
read_when:
|
||||||
- You want to read or edit config non-interactively
|
- You want to read or edit config non-interactively
|
||||||
|
title: "config"
|
||||||
---
|
---
|
||||||
|
|
||||||
# `openclaw config`
|
# `openclaw config`
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
summary: "CLI reference for `openclaw configure` (interactive configuration prompts)"
|
summary: "CLI reference for `openclaw configure` (interactive configuration prompts)"
|
||||||
read_when:
|
read_when:
|
||||||
- You want to tweak credentials, devices, or agent defaults interactively
|
- You want to tweak credentials, devices, or agent defaults interactively
|
||||||
|
title: "configure"
|
||||||
---
|
---
|
||||||
|
|
||||||
# `openclaw configure`
|
# `openclaw configure`
|
||||||
@@ -15,10 +16,12 @@ Tip: `openclaw config` without a subcommand opens the same wizard. Use
|
|||||||
`openclaw config get|set|unset` for non-interactive edits.
|
`openclaw config get|set|unset` for non-interactive edits.
|
||||||
|
|
||||||
Related:
|
Related:
|
||||||
|
|
||||||
- Gateway configuration reference: [Configuration](/gateway/configuration)
|
- Gateway configuration reference: [Configuration](/gateway/configuration)
|
||||||
- Config CLI: [Config](/cli/config)
|
- Config CLI: [Config](/cli/config)
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
- Choosing where the Gateway runs always updates `gateway.mode`. You can select "Continue" without other sections if that is all you need.
|
- Choosing where the Gateway runs always updates `gateway.mode`. You can select "Continue" without other sections if that is all you need.
|
||||||
- Channel-oriented services (Slack/Discord/Matrix/Microsoft Teams) prompt for channel/room allowlists during setup. You can enter names or IDs; the wizard resolves names to IDs when possible.
|
- Channel-oriented services (Slack/Discord/Matrix/Microsoft Teams) prompt for channel/room allowlists during setup. You can enter names or IDs; the wizard resolves names to IDs when possible.
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ summary: "CLI reference for `openclaw cron` (schedule and run background jobs)"
|
|||||||
read_when:
|
read_when:
|
||||||
- You want scheduled jobs and wakeups
|
- You want scheduled jobs and wakeups
|
||||||
- You’re debugging cron execution and logs
|
- You’re debugging cron execution and logs
|
||||||
|
title: "cron"
|
||||||
---
|
---
|
||||||
|
|
||||||
# `openclaw cron`
|
# `openclaw cron`
|
||||||
@@ -10,6 +11,7 @@ read_when:
|
|||||||
Manage cron jobs for the Gateway scheduler.
|
Manage cron jobs for the Gateway scheduler.
|
||||||
|
|
||||||
Related:
|
Related:
|
||||||
|
|
||||||
- Cron jobs: [Cron jobs](/automation/cron-jobs)
|
- Cron jobs: [Cron jobs](/automation/cron-jobs)
|
||||||
|
|
||||||
Tip: run `openclaw cron --help` for the full command surface.
|
Tip: run `openclaw cron --help` for the full command surface.
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ summary: "CLI reference for `openclaw dashboard` (open the Control UI)"
|
|||||||
read_when:
|
read_when:
|
||||||
- You want to open the Control UI with your current token
|
- You want to open the Control UI with your current token
|
||||||
- You want to print the URL without launching a browser
|
- You want to print the URL without launching a browser
|
||||||
|
title: "dashboard"
|
||||||
---
|
---
|
||||||
|
|
||||||
# `openclaw dashboard`
|
# `openclaw dashboard`
|
||||||
@@ -13,4 +14,3 @@ Open the Control UI using your current auth.
|
|||||||
openclaw dashboard
|
openclaw dashboard
|
||||||
openclaw dashboard --no-open
|
openclaw dashboard --no-open
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ summary: "CLI reference for `openclaw devices` (device pairing + token rotation/
|
|||||||
read_when:
|
read_when:
|
||||||
- You are approving device pairing requests
|
- You are approving device pairing requests
|
||||||
- You need to rotate or revoke device tokens
|
- You need to rotate or revoke device tokens
|
||||||
|
title: "devices"
|
||||||
---
|
---
|
||||||
|
|
||||||
# `openclaw devices`
|
# `openclaw devices`
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ summary: "CLI reference for `openclaw directory` (self, peers, groups)"
|
|||||||
read_when:
|
read_when:
|
||||||
- You want to look up contacts/groups/self ids for a channel
|
- You want to look up contacts/groups/self ids for a channel
|
||||||
- You are developing a channel directory adapter
|
- You are developing a channel directory adapter
|
||||||
|
title: "directory"
|
||||||
---
|
---
|
||||||
|
|
||||||
# `openclaw directory`
|
# `openclaw directory`
|
||||||
@@ -10,11 +11,13 @@ read_when:
|
|||||||
Directory lookups for channels that support it (contacts/peers, groups, and “me”).
|
Directory lookups for channels that support it (contacts/peers, groups, and “me”).
|
||||||
|
|
||||||
## Common flags
|
## Common flags
|
||||||
|
|
||||||
- `--channel <name>`: channel id/alias (required when multiple channels are configured; auto when only one is configured)
|
- `--channel <name>`: channel id/alias (required when multiple channels are configured; auto when only one is configured)
|
||||||
- `--account <id>`: account id (default: channel default)
|
- `--account <id>`: account id (default: channel default)
|
||||||
- `--json`: output JSON
|
- `--json`: output JSON
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- `directory` is meant to help you find IDs you can paste into other commands (especially `openclaw message send --target ...`).
|
- `directory` is meant to help you find IDs you can paste into other commands (especially `openclaw message send --target ...`).
|
||||||
- For many channels, results are config-backed (allowlists / configured groups) rather than a live provider directory.
|
- For many channels, results are config-backed (allowlists / configured groups) rather than a live provider directory.
|
||||||
- Default output is `id` (and sometimes `name`) separated by a tab; use `--json` for scripting.
|
- Default output is `id` (and sometimes `name`) separated by a tab; use `--json` for scripting.
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ summary: "CLI reference for `openclaw dns` (wide-area discovery helpers)"
|
|||||||
read_when:
|
read_when:
|
||||||
- You want wide-area discovery (DNS-SD) via Tailscale + CoreDNS
|
- You want wide-area discovery (DNS-SD) via Tailscale + CoreDNS
|
||||||
- You’re setting up split DNS for a custom discovery domain (example: openclaw.internal)
|
- You’re setting up split DNS for a custom discovery domain (example: openclaw.internal)
|
||||||
|
title: "dns"
|
||||||
---
|
---
|
||||||
|
|
||||||
# `openclaw dns`
|
# `openclaw dns`
|
||||||
@@ -10,6 +11,7 @@ read_when:
|
|||||||
DNS helpers for wide-area discovery (Tailscale + CoreDNS). Currently focused on macOS + Homebrew CoreDNS.
|
DNS helpers for wide-area discovery (Tailscale + CoreDNS). Currently focused on macOS + Homebrew CoreDNS.
|
||||||
|
|
||||||
Related:
|
Related:
|
||||||
|
|
||||||
- Gateway discovery: [Discovery](/gateway/discovery)
|
- Gateway discovery: [Discovery](/gateway/discovery)
|
||||||
- Wide-area discovery config: [Configuration](/gateway/configuration)
|
- Wide-area discovery config: [Configuration](/gateway/configuration)
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
summary: "CLI reference for `openclaw docs` (search the live docs index)"
|
summary: "CLI reference for `openclaw docs` (search the live docs index)"
|
||||||
read_when:
|
read_when:
|
||||||
- You want to search the live OpenClaw docs from the terminal
|
- You want to search the live OpenClaw docs from the terminal
|
||||||
|
title: "docs"
|
||||||
---
|
---
|
||||||
|
|
||||||
# `openclaw docs`
|
# `openclaw docs`
|
||||||
@@ -12,4 +13,3 @@ Search the live docs index.
|
|||||||
openclaw docs browser extension
|
openclaw docs browser extension
|
||||||
openclaw docs sandbox allowHostControl
|
openclaw docs sandbox allowHostControl
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ summary: "CLI reference for `openclaw doctor` (health checks + guided repairs)"
|
|||||||
read_when:
|
read_when:
|
||||||
- You have connectivity/auth issues and want guided fixes
|
- You have connectivity/auth issues and want guided fixes
|
||||||
- You updated and want a sanity check
|
- You updated and want a sanity check
|
||||||
|
title: "doctor"
|
||||||
---
|
---
|
||||||
|
|
||||||
# `openclaw doctor`
|
# `openclaw doctor`
|
||||||
@@ -10,6 +11,7 @@ read_when:
|
|||||||
Health checks + quick fixes for the gateway and channels.
|
Health checks + quick fixes for the gateway and channels.
|
||||||
|
|
||||||
Related:
|
Related:
|
||||||
|
|
||||||
- Troubleshooting: [Troubleshooting](/gateway/troubleshooting)
|
- Troubleshooting: [Troubleshooting](/gateway/troubleshooting)
|
||||||
- Security audit: [Security](/gateway/security)
|
- Security audit: [Security](/gateway/security)
|
||||||
|
|
||||||
@@ -22,6 +24,7 @@ openclaw doctor --deep
|
|||||||
```
|
```
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
- Interactive prompts (like keychain/OAuth fixes) only run when stdin is a TTY and `--non-interactive` is **not** set. Headless runs (cron, Telegram, no terminal) will skip prompts.
|
- Interactive prompts (like keychain/OAuth fixes) only run when stdin is a TTY and `--non-interactive` is **not** set. Headless runs (cron, Telegram, no terminal) will skip prompts.
|
||||||
- `--fix` (alias for `--repair`) writes a backup to `~/.openclaw/openclaw.json.bak` and drops unknown config keys, listing each removal.
|
- `--fix` (alias for `--repair`) writes a backup to `~/.openclaw/openclaw.json.bak` and drops unknown config keys, listing each removal.
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ read_when:
|
|||||||
- Running the Gateway from the CLI (dev or servers)
|
- Running the Gateway from the CLI (dev or servers)
|
||||||
- Debugging Gateway auth, bind modes, and connectivity
|
- Debugging Gateway auth, bind modes, and connectivity
|
||||||
- Discovering gateways via Bonjour (LAN + tailnet)
|
- Discovering gateways via Bonjour (LAN + tailnet)
|
||||||
|
title: "gateway"
|
||||||
---
|
---
|
||||||
|
|
||||||
# Gateway CLI
|
# Gateway CLI
|
||||||
@@ -13,6 +14,7 @@ The Gateway is OpenClaw’s WebSocket server (channels, nodes, sessions, hooks).
|
|||||||
Subcommands in this page live under `openclaw gateway …`.
|
Subcommands in this page live under `openclaw gateway …`.
|
||||||
|
|
||||||
Related docs:
|
Related docs:
|
||||||
|
|
||||||
- [/gateway/bonjour](/gateway/bonjour)
|
- [/gateway/bonjour](/gateway/bonjour)
|
||||||
- [/gateway/discovery](/gateway/discovery)
|
- [/gateway/discovery](/gateway/discovery)
|
||||||
- [/gateway/configuration](/gateway/configuration)
|
- [/gateway/configuration](/gateway/configuration)
|
||||||
@@ -32,6 +34,7 @@ openclaw gateway run
|
|||||||
```
|
```
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
- By default, the Gateway refuses to start unless `gateway.mode=local` is set in `~/.openclaw/openclaw.json`. Use `--allow-unconfigured` for ad-hoc/dev runs.
|
- By default, the Gateway refuses to start unless `gateway.mode=local` is set in `~/.openclaw/openclaw.json`. Use `--allow-unconfigured` for ad-hoc/dev runs.
|
||||||
- Binding beyond loopback without auth is blocked (safety guardrail).
|
- Binding beyond loopback without auth is blocked (safety guardrail).
|
||||||
- `SIGUSR1` triggers an in-process restart when authorized (enable `commands.restart` or use the gateway tool/config apply/update).
|
- `SIGUSR1` triggers an in-process restart when authorized (enable `commands.restart` or use the gateway tool/config apply/update).
|
||||||
@@ -62,11 +65,13 @@ Notes:
|
|||||||
All query commands use WebSocket RPC.
|
All query commands use WebSocket RPC.
|
||||||
|
|
||||||
Output modes:
|
Output modes:
|
||||||
|
|
||||||
- Default: human-readable (colored in TTY).
|
- Default: human-readable (colored in TTY).
|
||||||
- `--json`: machine-readable JSON (no styling/spinner).
|
- `--json`: machine-readable JSON (no styling/spinner).
|
||||||
- `--no-color` (or `NO_COLOR=1`): disable ANSI while keeping human layout.
|
- `--no-color` (or `NO_COLOR=1`): disable ANSI while keeping human layout.
|
||||||
|
|
||||||
Shared options (where supported):
|
Shared options (where supported):
|
||||||
|
|
||||||
- `--url <url>`: Gateway WebSocket URL.
|
- `--url <url>`: Gateway WebSocket URL.
|
||||||
- `--token <token>`: Gateway token.
|
- `--token <token>`: Gateway token.
|
||||||
- `--password <password>`: Gateway password.
|
- `--password <password>`: Gateway password.
|
||||||
@@ -89,6 +94,7 @@ openclaw gateway status --json
|
|||||||
```
|
```
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|
||||||
- `--url <url>`: override the probe URL.
|
- `--url <url>`: override the probe URL.
|
||||||
- `--token <token>`: token auth for the probe.
|
- `--token <token>`: token auth for the probe.
|
||||||
- `--password <password>`: password auth for the probe.
|
- `--password <password>`: password auth for the probe.
|
||||||
@@ -99,6 +105,7 @@ Options:
|
|||||||
### `gateway probe`
|
### `gateway probe`
|
||||||
|
|
||||||
`gateway probe` is the “debug everything” command. It always probes:
|
`gateway probe` is the “debug everything” command. It always probes:
|
||||||
|
|
||||||
- your configured remote gateway (if set), and
|
- your configured remote gateway (if set), and
|
||||||
- localhost (loopback) **even if remote is configured**.
|
- localhost (loopback) **even if remote is configured**.
|
||||||
|
|
||||||
@@ -120,11 +127,13 @@ openclaw gateway probe --ssh user@gateway-host
|
|||||||
```
|
```
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|
||||||
- `--ssh <target>`: `user@host` or `user@host:port` (port defaults to `22`).
|
- `--ssh <target>`: `user@host` or `user@host:port` (port defaults to `22`).
|
||||||
- `--ssh-identity <path>`: identity file.
|
- `--ssh-identity <path>`: identity file.
|
||||||
- `--ssh-auto`: pick the first discovered gateway host as SSH target (LAN/WAB only).
|
- `--ssh-auto`: pick the first discovered gateway host as SSH target (LAN/WAB only).
|
||||||
|
|
||||||
Config (optional, used as defaults):
|
Config (optional, used as defaults):
|
||||||
|
|
||||||
- `gateway.remote.sshTarget`
|
- `gateway.remote.sshTarget`
|
||||||
- `gateway.remote.sshIdentity`
|
- `gateway.remote.sshIdentity`
|
||||||
|
|
||||||
@@ -148,6 +157,7 @@ openclaw gateway uninstall
|
|||||||
```
|
```
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
- `gateway install` supports `--port`, `--runtime`, `--token`, `--force`, `--json`.
|
- `gateway install` supports `--port`, `--runtime`, `--token`, `--force`, `--json`.
|
||||||
- Lifecycle commands accept `--json` for scripting.
|
- Lifecycle commands accept `--json` for scripting.
|
||||||
|
|
||||||
@@ -161,6 +171,7 @@ Notes:
|
|||||||
Only gateways with Bonjour discovery enabled (default) advertise the beacon.
|
Only gateways with Bonjour discovery enabled (default) advertise the beacon.
|
||||||
|
|
||||||
Wide-Area discovery records include (TXT):
|
Wide-Area discovery records include (TXT):
|
||||||
|
|
||||||
- `role` (gateway role hint)
|
- `role` (gateway role hint)
|
||||||
- `transport` (transport hint, e.g. `gateway`)
|
- `transport` (transport hint, e.g. `gateway`)
|
||||||
- `gatewayPort` (WebSocket port, usually `18789`)
|
- `gatewayPort` (WebSocket port, usually `18789`)
|
||||||
@@ -176,6 +187,7 @@ openclaw gateway discover
|
|||||||
```
|
```
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|
||||||
- `--timeout <ms>`: per-command timeout (browse/resolve); default `2000`.
|
- `--timeout <ms>`: per-command timeout (browse/resolve); default `2000`.
|
||||||
- `--json`: machine-readable output (also disables styling/spinner).
|
- `--json`: machine-readable output (also disables styling/spinner).
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
summary: "CLI reference for `openclaw health` (gateway health endpoint via RPC)"
|
summary: "CLI reference for `openclaw health` (gateway health endpoint via RPC)"
|
||||||
read_when:
|
read_when:
|
||||||
- You want to quickly check the running Gateway’s health
|
- You want to quickly check the running Gateway’s health
|
||||||
|
title: "health"
|
||||||
---
|
---
|
||||||
|
|
||||||
# `openclaw health`
|
# `openclaw health`
|
||||||
@@ -15,5 +16,6 @@ openclaw health --verbose
|
|||||||
```
|
```
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
- `--verbose` runs live probes and prints per-account timings when multiple accounts are configured.
|
- `--verbose` runs live probes and prints per-account timings when multiple accounts are configured.
|
||||||
- Output includes per-agent session stores when multiple agents are configured.
|
- Output includes per-agent session stores when multiple agents are configured.
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ summary: "CLI reference for `openclaw hooks` (agent hooks)"
|
|||||||
read_when:
|
read_when:
|
||||||
- You want to manage agent hooks
|
- You want to manage agent hooks
|
||||||
- You want to install or update hooks
|
- You want to install or update hooks
|
||||||
|
title: "hooks"
|
||||||
---
|
---
|
||||||
|
|
||||||
# `openclaw hooks`
|
# `openclaw hooks`
|
||||||
@@ -10,6 +11,7 @@ read_when:
|
|||||||
Manage agent hooks (event-driven automations for commands like `/new`, `/reset`, and gateway startup).
|
Manage agent hooks (event-driven automations for commands like `/new`, `/reset`, and gateway startup).
|
||||||
|
|
||||||
Related:
|
Related:
|
||||||
|
|
||||||
- Hooks: [Hooks](/hooks)
|
- Hooks: [Hooks](/hooks)
|
||||||
- Plugin hooks: [Plugins](/plugin#plugin-hooks)
|
- Plugin hooks: [Plugins](/plugin#plugin-hooks)
|
||||||
|
|
||||||
@@ -22,6 +24,7 @@ openclaw hooks list
|
|||||||
List all discovered hooks from workspace, managed, and bundled directories.
|
List all discovered hooks from workspace, managed, and bundled directories.
|
||||||
|
|
||||||
**Options:**
|
**Options:**
|
||||||
|
|
||||||
- `--eligible`: Show only eligible hooks (requirements met)
|
- `--eligible`: Show only eligible hooks (requirements met)
|
||||||
- `--json`: Output as JSON
|
- `--json`: Output as JSON
|
||||||
- `-v, --verbose`: Show detailed information including missing requirements
|
- `-v, --verbose`: Show detailed information including missing requirements
|
||||||
@@ -63,9 +66,11 @@ openclaw hooks info <name>
|
|||||||
Show detailed information about a specific hook.
|
Show detailed information about a specific hook.
|
||||||
|
|
||||||
**Arguments:**
|
**Arguments:**
|
||||||
|
|
||||||
- `<name>`: Hook name (e.g., `session-memory`)
|
- `<name>`: Hook name (e.g., `session-memory`)
|
||||||
|
|
||||||
**Options:**
|
**Options:**
|
||||||
|
|
||||||
- `--json`: Output as JSON
|
- `--json`: Output as JSON
|
||||||
|
|
||||||
**Example:**
|
**Example:**
|
||||||
@@ -101,6 +106,7 @@ openclaw hooks check
|
|||||||
Show summary of hook eligibility status (how many are ready vs. not ready).
|
Show summary of hook eligibility status (how many are ready vs. not ready).
|
||||||
|
|
||||||
**Options:**
|
**Options:**
|
||||||
|
|
||||||
- `--json`: Output as JSON
|
- `--json`: Output as JSON
|
||||||
|
|
||||||
**Example output:**
|
**Example output:**
|
||||||
@@ -125,6 +131,7 @@ Enable a specific hook by adding it to your config (`~/.openclaw/config.json`).
|
|||||||
can’t be enabled/disabled here. Enable/disable the plugin instead.
|
can’t be enabled/disabled here. Enable/disable the plugin instead.
|
||||||
|
|
||||||
**Arguments:**
|
**Arguments:**
|
||||||
|
|
||||||
- `<name>`: Hook name (e.g., `session-memory`)
|
- `<name>`: Hook name (e.g., `session-memory`)
|
||||||
|
|
||||||
**Example:**
|
**Example:**
|
||||||
@@ -140,11 +147,13 @@ openclaw hooks enable session-memory
|
|||||||
```
|
```
|
||||||
|
|
||||||
**What it does:**
|
**What it does:**
|
||||||
|
|
||||||
- Checks if hook exists and is eligible
|
- Checks if hook exists and is eligible
|
||||||
- Updates `hooks.internal.entries.<name>.enabled = true` in your config
|
- Updates `hooks.internal.entries.<name>.enabled = true` in your config
|
||||||
- Saves config to disk
|
- Saves config to disk
|
||||||
|
|
||||||
**After enabling:**
|
**After enabling:**
|
||||||
|
|
||||||
- Restart the gateway so hooks reload (menu bar app restart on macOS, or restart your gateway process in dev).
|
- Restart the gateway so hooks reload (menu bar app restart on macOS, or restart your gateway process in dev).
|
||||||
|
|
||||||
## Disable a Hook
|
## Disable a Hook
|
||||||
@@ -156,6 +165,7 @@ openclaw hooks disable <name>
|
|||||||
Disable a specific hook by updating your config.
|
Disable a specific hook by updating your config.
|
||||||
|
|
||||||
**Arguments:**
|
**Arguments:**
|
||||||
|
|
||||||
- `<name>`: Hook name (e.g., `command-logger`)
|
- `<name>`: Hook name (e.g., `command-logger`)
|
||||||
|
|
||||||
**Example:**
|
**Example:**
|
||||||
@@ -171,6 +181,7 @@ openclaw hooks disable command-logger
|
|||||||
```
|
```
|
||||||
|
|
||||||
**After disabling:**
|
**After disabling:**
|
||||||
|
|
||||||
- Restart the gateway so hooks reload
|
- Restart the gateway so hooks reload
|
||||||
|
|
||||||
## Install Hooks
|
## Install Hooks
|
||||||
@@ -182,11 +193,13 @@ openclaw hooks install <path-or-spec>
|
|||||||
Install a hook pack from a local folder/archive or npm.
|
Install a hook pack from a local folder/archive or npm.
|
||||||
|
|
||||||
**What it does:**
|
**What it does:**
|
||||||
|
|
||||||
- Copies the hook pack into `~/.openclaw/hooks/<id>`
|
- Copies the hook pack into `~/.openclaw/hooks/<id>`
|
||||||
- Enables the installed hooks in `hooks.internal.entries.*`
|
- Enables the installed hooks in `hooks.internal.entries.*`
|
||||||
- Records the install under `hooks.internal.installs`
|
- Records the install under `hooks.internal.installs`
|
||||||
|
|
||||||
**Options:**
|
**Options:**
|
||||||
|
|
||||||
- `-l, --link`: Link a local directory instead of copying (adds it to `hooks.internal.load.extraDirs`)
|
- `-l, --link`: Link a local directory instead of copying (adds it to `hooks.internal.load.extraDirs`)
|
||||||
|
|
||||||
**Supported archives:** `.zip`, `.tgz`, `.tar.gz`, `.tar`
|
**Supported archives:** `.zip`, `.tgz`, `.tar.gz`, `.tar`
|
||||||
@@ -217,6 +230,7 @@ openclaw hooks update --all
|
|||||||
Update installed hook packs (npm installs only).
|
Update installed hook packs (npm installs only).
|
||||||
|
|
||||||
**Options:**
|
**Options:**
|
||||||
|
|
||||||
- `--all`: Update all tracked hook packs
|
- `--all`: Update all tracked hook packs
|
||||||
- `--dry-run`: Show what would change without writing
|
- `--dry-run`: Show what would change without writing
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ summary: "OpenClaw CLI reference for `openclaw` commands, subcommands, and optio
|
|||||||
read_when:
|
read_when:
|
||||||
- Adding or modifying CLI commands or options
|
- Adding or modifying CLI commands or options
|
||||||
- Documenting new command surfaces
|
- Documenting new command surfaces
|
||||||
|
title: "CLI Reference"
|
||||||
---
|
---
|
||||||
|
|
||||||
# CLI reference
|
# CLI reference
|
||||||
@@ -269,6 +270,7 @@ Vector search over `MEMORY.md` + `memory/*.md`:
|
|||||||
Chat messages support `/...` commands (text and native). See [/tools/slash-commands](/tools/slash-commands).
|
Chat messages support `/...` commands (text and native). See [/tools/slash-commands](/tools/slash-commands).
|
||||||
|
|
||||||
Highlights:
|
Highlights:
|
||||||
|
|
||||||
- `/status` for quick diagnostics.
|
- `/status` for quick diagnostics.
|
||||||
- `/config` for persisted config changes.
|
- `/config` for persisted config changes.
|
||||||
- `/debug` for runtime-only config overrides (memory, not disk; requires `commands.debug: true`).
|
- `/debug` for runtime-only config overrides (memory, not disk; requires `commands.debug: true`).
|
||||||
@@ -276,9 +278,11 @@ Highlights:
|
|||||||
## Setup + onboarding
|
## Setup + onboarding
|
||||||
|
|
||||||
### `setup`
|
### `setup`
|
||||||
|
|
||||||
Initialize config + workspace.
|
Initialize config + workspace.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|
||||||
- `--workspace <dir>`: agent workspace path (default `~/.openclaw/workspace`).
|
- `--workspace <dir>`: agent workspace path (default `~/.openclaw/workspace`).
|
||||||
- `--wizard`: run the onboarding wizard.
|
- `--wizard`: run the onboarding wizard.
|
||||||
- `--non-interactive`: run wizard without prompts.
|
- `--non-interactive`: run wizard without prompts.
|
||||||
@@ -289,9 +293,11 @@ Options:
|
|||||||
Wizard auto-runs when any wizard flags are present (`--non-interactive`, `--mode`, `--remote-url`, `--remote-token`).
|
Wizard auto-runs when any wizard flags are present (`--non-interactive`, `--mode`, `--remote-url`, `--remote-token`).
|
||||||
|
|
||||||
### `onboard`
|
### `onboard`
|
||||||
|
|
||||||
Interactive wizard to set up gateway, workspace, and skills.
|
Interactive wizard to set up gateway, workspace, and skills.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|
||||||
- `--workspace <dir>`
|
- `--workspace <dir>`
|
||||||
- `--reset` (reset config + credentials + sessions + workspace before wizard)
|
- `--reset` (reset config + credentials + sessions + workspace before wizard)
|
||||||
- `--non-interactive`
|
- `--non-interactive`
|
||||||
@@ -332,21 +338,26 @@ Options:
|
|||||||
- `--json`
|
- `--json`
|
||||||
|
|
||||||
### `configure`
|
### `configure`
|
||||||
|
|
||||||
Interactive configuration wizard (models, channels, skills, gateway).
|
Interactive configuration wizard (models, channels, skills, gateway).
|
||||||
|
|
||||||
### `config`
|
### `config`
|
||||||
|
|
||||||
Non-interactive config helpers (get/set/unset). Running `openclaw config` with no
|
Non-interactive config helpers (get/set/unset). Running `openclaw config` with no
|
||||||
subcommand launches the wizard.
|
subcommand launches the wizard.
|
||||||
|
|
||||||
Subcommands:
|
Subcommands:
|
||||||
|
|
||||||
- `config get <path>`: print a config value (dot/bracket path).
|
- `config get <path>`: print a config value (dot/bracket path).
|
||||||
- `config set <path> <value>`: set a value (JSON5 or raw string).
|
- `config set <path> <value>`: set a value (JSON5 or raw string).
|
||||||
- `config unset <path>`: remove a value.
|
- `config unset <path>`: remove a value.
|
||||||
|
|
||||||
### `doctor`
|
### `doctor`
|
||||||
|
|
||||||
Health checks + quick fixes (config + gateway + legacy services).
|
Health checks + quick fixes (config + gateway + legacy services).
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|
||||||
- `--no-workspace-suggestions`: disable workspace memory hints.
|
- `--no-workspace-suggestions`: disable workspace memory hints.
|
||||||
- `--yes`: accept defaults without prompting (headless).
|
- `--yes`: accept defaults without prompting (headless).
|
||||||
- `--non-interactive`: skip prompts; apply safe migrations only.
|
- `--non-interactive`: skip prompts; apply safe migrations only.
|
||||||
@@ -355,9 +366,11 @@ Options:
|
|||||||
## Channel helpers
|
## Channel helpers
|
||||||
|
|
||||||
### `channels`
|
### `channels`
|
||||||
|
|
||||||
Manage chat channel accounts (WhatsApp/Telegram/Discord/Google Chat/Slack/Mattermost (plugin)/Signal/iMessage/MS Teams).
|
Manage chat channel accounts (WhatsApp/Telegram/Discord/Google Chat/Slack/Mattermost (plugin)/Signal/iMessage/MS Teams).
|
||||||
|
|
||||||
Subcommands:
|
Subcommands:
|
||||||
|
|
||||||
- `channels list`: show configured channels and auth profiles.
|
- `channels list`: show configured channels and auth profiles.
|
||||||
- `channels status`: check gateway reachability and channel health (`--probe` runs extra checks; use `openclaw health` or `openclaw status --deep` for gateway health probes).
|
- `channels status`: check gateway reachability and channel health (`--probe` runs extra checks; use `openclaw health` or `openclaw status --deep` for gateway health probes).
|
||||||
- Tip: `channels status` prints warnings with suggested fixes when it can detect common misconfigurations (then points you to `openclaw doctor`).
|
- Tip: `channels status` prints warnings with suggested fixes when it can detect common misconfigurations (then points you to `openclaw doctor`).
|
||||||
@@ -368,24 +381,29 @@ Subcommands:
|
|||||||
- `channels logout`: log out of a channel session (if supported).
|
- `channels logout`: log out of a channel session (if supported).
|
||||||
|
|
||||||
Common options:
|
Common options:
|
||||||
|
|
||||||
- `--channel <name>`: `whatsapp|telegram|discord|googlechat|slack|mattermost|signal|imessage|msteams`
|
- `--channel <name>`: `whatsapp|telegram|discord|googlechat|slack|mattermost|signal|imessage|msteams`
|
||||||
- `--account <id>`: channel account id (default `default`)
|
- `--account <id>`: channel account id (default `default`)
|
||||||
- `--name <label>`: display name for the account
|
- `--name <label>`: display name for the account
|
||||||
|
|
||||||
`channels login` options:
|
`channels login` options:
|
||||||
|
|
||||||
- `--channel <channel>` (default `whatsapp`; supports `whatsapp`/`web`)
|
- `--channel <channel>` (default `whatsapp`; supports `whatsapp`/`web`)
|
||||||
- `--account <id>`
|
- `--account <id>`
|
||||||
- `--verbose`
|
- `--verbose`
|
||||||
|
|
||||||
`channels logout` options:
|
`channels logout` options:
|
||||||
|
|
||||||
- `--channel <channel>` (default `whatsapp`)
|
- `--channel <channel>` (default `whatsapp`)
|
||||||
- `--account <id>`
|
- `--account <id>`
|
||||||
|
|
||||||
`channels list` options:
|
`channels list` options:
|
||||||
|
|
||||||
- `--no-usage`: skip model provider usage/quota snapshots (OAuth/API-backed only).
|
- `--no-usage`: skip model provider usage/quota snapshots (OAuth/API-backed only).
|
||||||
- `--json`: output JSON (includes usage unless `--no-usage` is set).
|
- `--json`: output JSON (includes usage unless `--no-usage` is set).
|
||||||
|
|
||||||
`channels logs` options:
|
`channels logs` options:
|
||||||
|
|
||||||
- `--channel <name|all>` (default `all`)
|
- `--channel <name|all>` (default `all`)
|
||||||
- `--lines <n>` (default `200`)
|
- `--lines <n>` (default `200`)
|
||||||
- `--json`
|
- `--json`
|
||||||
@@ -393,6 +411,7 @@ Common options:
|
|||||||
More detail: [/concepts/oauth](/concepts/oauth)
|
More detail: [/concepts/oauth](/concepts/oauth)
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
openclaw channels add --channel telegram --account alerts --name "Alerts Bot" --token $TELEGRAM_BOT_TOKEN
|
openclaw channels add --channel telegram --account alerts --name "Alerts Bot" --token $TELEGRAM_BOT_TOKEN
|
||||||
openclaw channels add --channel discord --account work --name "Work Bot" --token $DISCORD_BOT_TOKEN
|
openclaw channels add --channel discord --account work --name "Work Bot" --token $DISCORD_BOT_TOKEN
|
||||||
@@ -402,14 +421,17 @@ openclaw status --deep
|
|||||||
```
|
```
|
||||||
|
|
||||||
### `skills`
|
### `skills`
|
||||||
|
|
||||||
List and inspect available skills plus readiness info.
|
List and inspect available skills plus readiness info.
|
||||||
|
|
||||||
Subcommands:
|
Subcommands:
|
||||||
|
|
||||||
- `skills list`: list skills (default when no subcommand).
|
- `skills list`: list skills (default when no subcommand).
|
||||||
- `skills info <name>`: show details for one skill.
|
- `skills info <name>`: show details for one skill.
|
||||||
- `skills check`: summary of ready vs missing requirements.
|
- `skills check`: summary of ready vs missing requirements.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|
||||||
- `--eligible`: show only ready skills.
|
- `--eligible`: show only ready skills.
|
||||||
- `--json`: output JSON (no styling).
|
- `--json`: output JSON (no styling).
|
||||||
- `-v`, `--verbose`: include missing requirements detail.
|
- `-v`, `--verbose`: include missing requirements detail.
|
||||||
@@ -417,33 +439,41 @@ Options:
|
|||||||
Tip: use `npx clawhub` to search, install, and sync skills.
|
Tip: use `npx clawhub` to search, install, and sync skills.
|
||||||
|
|
||||||
### `pairing`
|
### `pairing`
|
||||||
|
|
||||||
Approve DM pairing requests across channels.
|
Approve DM pairing requests across channels.
|
||||||
|
|
||||||
Subcommands:
|
Subcommands:
|
||||||
|
|
||||||
- `pairing list <channel> [--json]`
|
- `pairing list <channel> [--json]`
|
||||||
- `pairing approve <channel> <code> [--notify]`
|
- `pairing approve <channel> <code> [--notify]`
|
||||||
|
|
||||||
### `webhooks gmail`
|
### `webhooks gmail`
|
||||||
|
|
||||||
Gmail Pub/Sub hook setup + runner. See [/automation/gmail-pubsub](/automation/gmail-pubsub).
|
Gmail Pub/Sub hook setup + runner. See [/automation/gmail-pubsub](/automation/gmail-pubsub).
|
||||||
|
|
||||||
Subcommands:
|
Subcommands:
|
||||||
|
|
||||||
- `webhooks gmail setup` (requires `--account <email>`; supports `--project`, `--topic`, `--subscription`, `--label`, `--hook-url`, `--hook-token`, `--push-token`, `--bind`, `--port`, `--path`, `--include-body`, `--max-bytes`, `--renew-minutes`, `--tailscale`, `--tailscale-path`, `--tailscale-target`, `--push-endpoint`, `--json`)
|
- `webhooks gmail setup` (requires `--account <email>`; supports `--project`, `--topic`, `--subscription`, `--label`, `--hook-url`, `--hook-token`, `--push-token`, `--bind`, `--port`, `--path`, `--include-body`, `--max-bytes`, `--renew-minutes`, `--tailscale`, `--tailscale-path`, `--tailscale-target`, `--push-endpoint`, `--json`)
|
||||||
- `webhooks gmail run` (runtime overrides for the same flags)
|
- `webhooks gmail run` (runtime overrides for the same flags)
|
||||||
|
|
||||||
### `dns setup`
|
### `dns setup`
|
||||||
|
|
||||||
Wide-area discovery DNS helper (CoreDNS + Tailscale). See [/gateway/discovery](/gateway/discovery).
|
Wide-area discovery DNS helper (CoreDNS + Tailscale). See [/gateway/discovery](/gateway/discovery).
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|
||||||
- `--apply`: install/update CoreDNS config (requires sudo; macOS only).
|
- `--apply`: install/update CoreDNS config (requires sudo; macOS only).
|
||||||
|
|
||||||
## Messaging + agent
|
## Messaging + agent
|
||||||
|
|
||||||
### `message`
|
### `message`
|
||||||
|
|
||||||
Unified outbound messaging + channel actions.
|
Unified outbound messaging + channel actions.
|
||||||
|
|
||||||
See: [/cli/message](/cli/message)
|
See: [/cli/message](/cli/message)
|
||||||
|
|
||||||
Subcommands:
|
Subcommands:
|
||||||
|
|
||||||
- `message send|poll|react|reactions|read|edit|delete|pin|unpin|pins|permissions|search|timeout|kick|ban`
|
- `message send|poll|react|reactions|read|edit|delete|pin|unpin|pins|permissions|search|timeout|kick|ban`
|
||||||
- `message thread <create|list|reply>`
|
- `message thread <create|list|reply>`
|
||||||
- `message emoji <list|upload>`
|
- `message emoji <list|upload>`
|
||||||
@@ -455,16 +485,20 @@ Subcommands:
|
|||||||
- `message event <list|create>`
|
- `message event <list|create>`
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
- `openclaw message send --target +15555550123 --message "Hi"`
|
- `openclaw message send --target +15555550123 --message "Hi"`
|
||||||
- `openclaw message poll --channel discord --target channel:123 --poll-question "Snack?" --poll-option Pizza --poll-option Sushi`
|
- `openclaw message poll --channel discord --target channel:123 --poll-question "Snack?" --poll-option Pizza --poll-option Sushi`
|
||||||
|
|
||||||
### `agent`
|
### `agent`
|
||||||
|
|
||||||
Run one agent turn via the Gateway (or `--local` embedded).
|
Run one agent turn via the Gateway (or `--local` embedded).
|
||||||
|
|
||||||
Required:
|
Required:
|
||||||
|
|
||||||
- `--message <text>`
|
- `--message <text>`
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|
||||||
- `--to <dest>` (for session key and optional delivery)
|
- `--to <dest>` (for session key and optional delivery)
|
||||||
- `--session-id <id>`
|
- `--session-id <id>`
|
||||||
- `--thinking <off|minimal|low|medium|high|xhigh>` (GPT-5.2 + Codex models only)
|
- `--thinking <off|minimal|low|medium|high|xhigh>` (GPT-5.2 + Codex models only)
|
||||||
@@ -476,19 +510,24 @@ Options:
|
|||||||
- `--timeout <seconds>`
|
- `--timeout <seconds>`
|
||||||
|
|
||||||
### `agents`
|
### `agents`
|
||||||
|
|
||||||
Manage isolated agents (workspaces + auth + routing).
|
Manage isolated agents (workspaces + auth + routing).
|
||||||
|
|
||||||
#### `agents list`
|
#### `agents list`
|
||||||
|
|
||||||
List configured agents.
|
List configured agents.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|
||||||
- `--json`
|
- `--json`
|
||||||
- `--bindings`
|
- `--bindings`
|
||||||
|
|
||||||
#### `agents add [name]`
|
#### `agents add [name]`
|
||||||
|
|
||||||
Add a new isolated agent. Runs the guided wizard unless flags (or `--non-interactive`) are passed; `--workspace` is required in non-interactive mode.
|
Add a new isolated agent. Runs the guided wizard unless flags (or `--non-interactive`) are passed; `--workspace` is required in non-interactive mode.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|
||||||
- `--workspace <dir>`
|
- `--workspace <dir>`
|
||||||
- `--model <id>`
|
- `--model <id>`
|
||||||
- `--agent-dir <dir>`
|
- `--agent-dir <dir>`
|
||||||
@@ -499,21 +538,26 @@ Options:
|
|||||||
Binding specs use `channel[:accountId]`. When `accountId` is omitted for WhatsApp, the default account id is used.
|
Binding specs use `channel[:accountId]`. When `accountId` is omitted for WhatsApp, the default account id is used.
|
||||||
|
|
||||||
#### `agents delete <id>`
|
#### `agents delete <id>`
|
||||||
|
|
||||||
Delete an agent and prune its workspace + state.
|
Delete an agent and prune its workspace + state.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|
||||||
- `--force`
|
- `--force`
|
||||||
- `--json`
|
- `--json`
|
||||||
|
|
||||||
### `acp`
|
### `acp`
|
||||||
|
|
||||||
Run the ACP bridge that connects IDEs to the Gateway.
|
Run the ACP bridge that connects IDEs to the Gateway.
|
||||||
|
|
||||||
See [`acp`](/cli/acp) for full options and examples.
|
See [`acp`](/cli/acp) for full options and examples.
|
||||||
|
|
||||||
### `status`
|
### `status`
|
||||||
|
|
||||||
Show linked session health and recent recipients.
|
Show linked session health and recent recipients.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|
||||||
- `--json`
|
- `--json`
|
||||||
- `--all` (full diagnosis; read-only, pasteable)
|
- `--all` (full diagnosis; read-only, pasteable)
|
||||||
- `--deep` (probe channels)
|
- `--deep` (probe channels)
|
||||||
@@ -523,34 +567,42 @@ Options:
|
|||||||
- `--debug` (alias for `--verbose`)
|
- `--debug` (alias for `--verbose`)
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
- Overview includes Gateway + node host service status when available.
|
- Overview includes Gateway + node host service status when available.
|
||||||
|
|
||||||
### Usage tracking
|
### Usage tracking
|
||||||
|
|
||||||
OpenClaw can surface provider usage/quota when OAuth/API creds are available.
|
OpenClaw can surface provider usage/quota when OAuth/API creds are available.
|
||||||
|
|
||||||
Surfaces:
|
Surfaces:
|
||||||
|
|
||||||
- `/status` (adds a short provider usage line when available)
|
- `/status` (adds a short provider usage line when available)
|
||||||
- `openclaw status --usage` (prints full provider breakdown)
|
- `openclaw status --usage` (prints full provider breakdown)
|
||||||
- macOS menu bar (Usage section under Context)
|
- macOS menu bar (Usage section under Context)
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
- Data comes directly from provider usage endpoints (no estimates).
|
- Data comes directly from provider usage endpoints (no estimates).
|
||||||
- Providers: Anthropic, GitHub Copilot, OpenAI Codex OAuth, plus Gemini CLI/Antigravity when those provider plugins are enabled.
|
- Providers: Anthropic, GitHub Copilot, OpenAI Codex OAuth, plus Gemini CLI/Antigravity when those provider plugins are enabled.
|
||||||
- If no matching credentials exist, usage is hidden.
|
- If no matching credentials exist, usage is hidden.
|
||||||
- Details: see [Usage tracking](/concepts/usage-tracking).
|
- Details: see [Usage tracking](/concepts/usage-tracking).
|
||||||
|
|
||||||
### `health`
|
### `health`
|
||||||
|
|
||||||
Fetch health from the running Gateway.
|
Fetch health from the running Gateway.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|
||||||
- `--json`
|
- `--json`
|
||||||
- `--timeout <ms>`
|
- `--timeout <ms>`
|
||||||
- `--verbose`
|
- `--verbose`
|
||||||
|
|
||||||
### `sessions`
|
### `sessions`
|
||||||
|
|
||||||
List stored conversation sessions.
|
List stored conversation sessions.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|
||||||
- `--json`
|
- `--json`
|
||||||
- `--verbose`
|
- `--verbose`
|
||||||
- `--store <path>`
|
- `--store <path>`
|
||||||
@@ -559,21 +611,26 @@ Options:
|
|||||||
## Reset / Uninstall
|
## Reset / Uninstall
|
||||||
|
|
||||||
### `reset`
|
### `reset`
|
||||||
|
|
||||||
Reset local config/state (keeps the CLI installed).
|
Reset local config/state (keeps the CLI installed).
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|
||||||
- `--scope <config|config+creds+sessions|full>`
|
- `--scope <config|config+creds+sessions|full>`
|
||||||
- `--yes`
|
- `--yes`
|
||||||
- `--non-interactive`
|
- `--non-interactive`
|
||||||
- `--dry-run`
|
- `--dry-run`
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
- `--non-interactive` requires `--scope` and `--yes`.
|
- `--non-interactive` requires `--scope` and `--yes`.
|
||||||
|
|
||||||
### `uninstall`
|
### `uninstall`
|
||||||
|
|
||||||
Uninstall the gateway service + local data (CLI remains).
|
Uninstall the gateway service + local data (CLI remains).
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|
||||||
- `--service`
|
- `--service`
|
||||||
- `--state`
|
- `--state`
|
||||||
- `--workspace`
|
- `--workspace`
|
||||||
@@ -584,14 +641,17 @@ Options:
|
|||||||
- `--dry-run`
|
- `--dry-run`
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
- `--non-interactive` requires `--yes` and explicit scopes (or `--all`).
|
- `--non-interactive` requires `--yes` and explicit scopes (or `--all`).
|
||||||
|
|
||||||
## Gateway
|
## Gateway
|
||||||
|
|
||||||
### `gateway`
|
### `gateway`
|
||||||
|
|
||||||
Run the WebSocket Gateway.
|
Run the WebSocket Gateway.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|
||||||
- `--port <port>`
|
- `--port <port>`
|
||||||
- `--bind <loopback|tailnet|lan|auto|custom>`
|
- `--bind <loopback|tailnet|lan|auto|custom>`
|
||||||
- `--token <token>`
|
- `--token <token>`
|
||||||
@@ -611,9 +671,11 @@ Options:
|
|||||||
- `--raw-stream-path <path>`
|
- `--raw-stream-path <path>`
|
||||||
|
|
||||||
### `gateway service`
|
### `gateway service`
|
||||||
|
|
||||||
Manage the Gateway service (launchd/systemd/schtasks).
|
Manage the Gateway service (launchd/systemd/schtasks).
|
||||||
|
|
||||||
Subcommands:
|
Subcommands:
|
||||||
|
|
||||||
- `gateway status` (probes the Gateway RPC by default)
|
- `gateway status` (probes the Gateway RPC by default)
|
||||||
- `gateway install` (service install)
|
- `gateway install` (service install)
|
||||||
- `gateway uninstall`
|
- `gateway uninstall`
|
||||||
@@ -622,6 +684,7 @@ Subcommands:
|
|||||||
- `gateway restart`
|
- `gateway restart`
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
- `gateway status` probes the Gateway RPC by default using the service’s resolved port/config (override with `--url/--token/--password`).
|
- `gateway status` probes the Gateway RPC by default using the service’s resolved port/config (override with `--url/--token/--password`).
|
||||||
- `gateway status` supports `--no-probe`, `--deep`, and `--json` for scripting.
|
- `gateway status` supports `--no-probe`, `--deep`, and `--json` for scripting.
|
||||||
- `gateway status` also surfaces legacy or extra gateway services when it can detect them (`--deep` adds system-level scans). Profile-named OpenClaw services are treated as first-class and aren't flagged as "extra".
|
- `gateway status` also surfaces legacy or extra gateway services when it can detect them (`--deep` adds system-level scans). Profile-named OpenClaw services are treated as first-class and aren't flagged as "extra".
|
||||||
@@ -631,13 +694,16 @@ Notes:
|
|||||||
- `gateway install` options: `--port`, `--runtime`, `--token`, `--force`, `--json`.
|
- `gateway install` options: `--port`, `--runtime`, `--token`, `--force`, `--json`.
|
||||||
|
|
||||||
### `logs`
|
### `logs`
|
||||||
|
|
||||||
Tail Gateway file logs via RPC.
|
Tail Gateway file logs via RPC.
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
- TTY sessions render a colorized, structured view; non-TTY falls back to plain text.
|
- TTY sessions render a colorized, structured view; non-TTY falls back to plain text.
|
||||||
- `--json` emits line-delimited JSON (one log event per line).
|
- `--json` emits line-delimited JSON (one log event per line).
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
openclaw logs --follow
|
openclaw logs --follow
|
||||||
openclaw logs --limit 200
|
openclaw logs --limit 200
|
||||||
@@ -647,9 +713,11 @@ openclaw logs --no-color
|
|||||||
```
|
```
|
||||||
|
|
||||||
### `gateway <subcommand>`
|
### `gateway <subcommand>`
|
||||||
|
|
||||||
Gateway CLI helpers (use `--url`, `--token`, `--password`, `--timeout`, `--expect-final` for RPC subcommands).
|
Gateway CLI helpers (use `--url`, `--token`, `--password`, `--timeout`, `--expect-final` for RPC subcommands).
|
||||||
|
|
||||||
Subcommands:
|
Subcommands:
|
||||||
|
|
||||||
- `gateway call <method> [--params <json>]`
|
- `gateway call <method> [--params <json>]`
|
||||||
- `gateway health`
|
- `gateway health`
|
||||||
- `gateway status`
|
- `gateway status`
|
||||||
@@ -659,6 +727,7 @@ Subcommands:
|
|||||||
- `gateway run`
|
- `gateway run`
|
||||||
|
|
||||||
Common RPCs:
|
Common RPCs:
|
||||||
|
|
||||||
- `config.apply` (validate + write config + restart + wake)
|
- `config.apply` (validate + write config + restart + wake)
|
||||||
- `config.patch` (merge a partial update + restart + wake)
|
- `config.patch` (merge a partial update + restart + wake)
|
||||||
- `update.run` (run update + restart + wake)
|
- `update.run` (run update + restart + wake)
|
||||||
@@ -679,14 +748,18 @@ openclaw models status
|
|||||||
```
|
```
|
||||||
|
|
||||||
### `models` (root)
|
### `models` (root)
|
||||||
|
|
||||||
`openclaw models` is an alias for `models status`.
|
`openclaw models` is an alias for `models status`.
|
||||||
|
|
||||||
Root options:
|
Root options:
|
||||||
|
|
||||||
- `--status-json` (alias for `models status --json`)
|
- `--status-json` (alias for `models status --json`)
|
||||||
- `--status-plain` (alias for `models status --plain`)
|
- `--status-plain` (alias for `models status --plain`)
|
||||||
|
|
||||||
### `models list`
|
### `models list`
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|
||||||
- `--all`
|
- `--all`
|
||||||
- `--local`
|
- `--local`
|
||||||
- `--provider <name>`
|
- `--provider <name>`
|
||||||
@@ -694,7 +767,9 @@ Options:
|
|||||||
- `--plain`
|
- `--plain`
|
||||||
|
|
||||||
### `models status`
|
### `models status`
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|
||||||
- `--json`
|
- `--json`
|
||||||
- `--plain`
|
- `--plain`
|
||||||
- `--check` (exit 1=expired/missing, 2=expiring)
|
- `--check` (exit 1=expired/missing, 2=expiring)
|
||||||
@@ -709,33 +784,43 @@ Always includes the auth overview and OAuth expiry status for profiles in the au
|
|||||||
`--probe` runs live requests (may consume tokens and trigger rate limits).
|
`--probe` runs live requests (may consume tokens and trigger rate limits).
|
||||||
|
|
||||||
### `models set <model>`
|
### `models set <model>`
|
||||||
|
|
||||||
Set `agents.defaults.model.primary`.
|
Set `agents.defaults.model.primary`.
|
||||||
|
|
||||||
### `models set-image <model>`
|
### `models set-image <model>`
|
||||||
|
|
||||||
Set `agents.defaults.imageModel.primary`.
|
Set `agents.defaults.imageModel.primary`.
|
||||||
|
|
||||||
### `models aliases list|add|remove`
|
### `models aliases list|add|remove`
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|
||||||
- `list`: `--json`, `--plain`
|
- `list`: `--json`, `--plain`
|
||||||
- `add <alias> <model>`
|
- `add <alias> <model>`
|
||||||
- `remove <alias>`
|
- `remove <alias>`
|
||||||
|
|
||||||
### `models fallbacks list|add|remove|clear`
|
### `models fallbacks list|add|remove|clear`
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|
||||||
- `list`: `--json`, `--plain`
|
- `list`: `--json`, `--plain`
|
||||||
- `add <model>`
|
- `add <model>`
|
||||||
- `remove <model>`
|
- `remove <model>`
|
||||||
- `clear`
|
- `clear`
|
||||||
|
|
||||||
### `models image-fallbacks list|add|remove|clear`
|
### `models image-fallbacks list|add|remove|clear`
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|
||||||
- `list`: `--json`, `--plain`
|
- `list`: `--json`, `--plain`
|
||||||
- `add <model>`
|
- `add <model>`
|
||||||
- `remove <model>`
|
- `remove <model>`
|
||||||
- `clear`
|
- `clear`
|
||||||
|
|
||||||
### `models scan`
|
### `models scan`
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|
||||||
- `--min-params <b>`
|
- `--min-params <b>`
|
||||||
- `--max-age-days <days>`
|
- `--max-age-days <days>`
|
||||||
- `--provider <name>`
|
- `--provider <name>`
|
||||||
@@ -750,13 +835,17 @@ Options:
|
|||||||
- `--json`
|
- `--json`
|
||||||
|
|
||||||
### `models auth add|setup-token|paste-token`
|
### `models auth add|setup-token|paste-token`
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|
||||||
- `add`: interactive auth helper
|
- `add`: interactive auth helper
|
||||||
- `setup-token`: `--provider <name>` (default `anthropic`), `--yes`
|
- `setup-token`: `--provider <name>` (default `anthropic`), `--yes`
|
||||||
- `paste-token`: `--provider <name>`, `--profile-id <id>`, `--expires-in <duration>`
|
- `paste-token`: `--provider <name>`, `--profile-id <id>`, `--expires-in <duration>`
|
||||||
|
|
||||||
### `models auth order get|set|clear`
|
### `models auth order get|set|clear`
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|
||||||
- `get`: `--provider <name>`, `--agent <id>`, `--json`
|
- `get`: `--provider <name>`, `--agent <id>`, `--json`
|
||||||
- `set`: `--provider <name>`, `--agent <id>`, `<profileIds...>`
|
- `set`: `--provider <name>`, `--agent <id>`, `<profileIds...>`
|
||||||
- `clear`: `--provider <name>`, `--agent <id>`
|
- `clear`: `--provider <name>`, `--agent <id>`
|
||||||
@@ -764,34 +853,43 @@ Options:
|
|||||||
## System
|
## System
|
||||||
|
|
||||||
### `system event`
|
### `system event`
|
||||||
|
|
||||||
Enqueue a system event and optionally trigger a heartbeat (Gateway RPC).
|
Enqueue a system event and optionally trigger a heartbeat (Gateway RPC).
|
||||||
|
|
||||||
Required:
|
Required:
|
||||||
|
|
||||||
- `--text <text>`
|
- `--text <text>`
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|
||||||
- `--mode <now|next-heartbeat>`
|
- `--mode <now|next-heartbeat>`
|
||||||
- `--json`
|
- `--json`
|
||||||
- `--url`, `--token`, `--timeout`, `--expect-final`
|
- `--url`, `--token`, `--timeout`, `--expect-final`
|
||||||
|
|
||||||
### `system heartbeat last|enable|disable`
|
### `system heartbeat last|enable|disable`
|
||||||
|
|
||||||
Heartbeat controls (Gateway RPC).
|
Heartbeat controls (Gateway RPC).
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|
||||||
- `--json`
|
- `--json`
|
||||||
- `--url`, `--token`, `--timeout`, `--expect-final`
|
- `--url`, `--token`, `--timeout`, `--expect-final`
|
||||||
|
|
||||||
### `system presence`
|
### `system presence`
|
||||||
|
|
||||||
List system presence entries (Gateway RPC).
|
List system presence entries (Gateway RPC).
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|
||||||
- `--json`
|
- `--json`
|
||||||
- `--url`, `--token`, `--timeout`, `--expect-final`
|
- `--url`, `--token`, `--timeout`, `--expect-final`
|
||||||
|
|
||||||
## Cron
|
## Cron
|
||||||
|
|
||||||
Manage scheduled jobs (Gateway RPC). See [/automation/cron-jobs](/automation/cron-jobs).
|
Manage scheduled jobs (Gateway RPC). See [/automation/cron-jobs](/automation/cron-jobs).
|
||||||
|
|
||||||
Subcommands:
|
Subcommands:
|
||||||
|
|
||||||
- `cron status [--json]`
|
- `cron status [--json]`
|
||||||
- `cron list [--all] [--json]` (table output by default; use `--json` for raw)
|
- `cron list [--all] [--json]` (table output by default; use `--json` for raw)
|
||||||
- `cron add` (alias: `create`; requires `--name` and exactly one of `--at` | `--every` | `--cron`, and exactly one payload of `--system-event` | `--message`)
|
- `cron add` (alias: `create`; requires `--name` and exactly one of `--at` | `--every` | `--cron`, and exactly one payload of `--system-event` | `--message`)
|
||||||
@@ -810,6 +908,7 @@ All `cron` commands accept `--url`, `--token`, `--timeout`, `--expect-final`.
|
|||||||
[`openclaw node`](/cli/node).
|
[`openclaw node`](/cli/node).
|
||||||
|
|
||||||
Subcommands:
|
Subcommands:
|
||||||
|
|
||||||
- `node run --host <gateway-host> --port 18789`
|
- `node run --host <gateway-host> --port 18789`
|
||||||
- `node status`
|
- `node status`
|
||||||
- `node install [--host <gateway-host>] [--port <port>] [--tls] [--tls-fingerprint <sha256>] [--node-id <id>] [--display-name <name>] [--runtime <node|bun>] [--force]`
|
- `node install [--host <gateway-host>] [--port <port>] [--tls] [--tls-fingerprint <sha256>] [--node-id <id>] [--display-name <name>] [--runtime <node|bun>] [--force]`
|
||||||
@@ -822,9 +921,11 @@ Subcommands:
|
|||||||
`nodes` talks to the Gateway and targets paired nodes. See [/nodes](/nodes).
|
`nodes` talks to the Gateway and targets paired nodes. See [/nodes](/nodes).
|
||||||
|
|
||||||
Common options:
|
Common options:
|
||||||
|
|
||||||
- `--url`, `--token`, `--timeout`, `--json`
|
- `--url`, `--token`, `--timeout`, `--json`
|
||||||
|
|
||||||
Subcommands:
|
Subcommands:
|
||||||
|
|
||||||
- `nodes status [--connected] [--last-connected <duration>]`
|
- `nodes status [--connected] [--last-connected <duration>]`
|
||||||
- `nodes describe --node <id|name|ip>`
|
- `nodes describe --node <id|name|ip>`
|
||||||
- `nodes list [--connected] [--last-connected <duration>]`
|
- `nodes list [--connected] [--last-connected <duration>]`
|
||||||
@@ -837,11 +938,13 @@ Subcommands:
|
|||||||
- `nodes notify --node <id|name|ip> [--title <text>] [--body <text>] [--sound <name>] [--priority <passive|active|timeSensitive>] [--delivery <system|overlay|auto>] [--invoke-timeout <ms>]` (mac only)
|
- `nodes notify --node <id|name|ip> [--title <text>] [--body <text>] [--sound <name>] [--priority <passive|active|timeSensitive>] [--delivery <system|overlay|auto>] [--invoke-timeout <ms>]` (mac only)
|
||||||
|
|
||||||
Camera:
|
Camera:
|
||||||
|
|
||||||
- `nodes camera list --node <id|name|ip>`
|
- `nodes camera list --node <id|name|ip>`
|
||||||
- `nodes camera snap --node <id|name|ip> [--facing front|back|both] [--device-id <id>] [--max-width <px>] [--quality <0-1>] [--delay-ms <ms>] [--invoke-timeout <ms>]`
|
- `nodes camera snap --node <id|name|ip> [--facing front|back|both] [--device-id <id>] [--max-width <px>] [--quality <0-1>] [--delay-ms <ms>] [--invoke-timeout <ms>]`
|
||||||
- `nodes camera clip --node <id|name|ip> [--facing front|back] [--device-id <id>] [--duration <ms|10s|1m>] [--no-audio] [--invoke-timeout <ms>]`
|
- `nodes camera clip --node <id|name|ip> [--facing front|back] [--device-id <id>] [--duration <ms|10s|1m>] [--no-audio] [--invoke-timeout <ms>]`
|
||||||
|
|
||||||
Canvas + screen:
|
Canvas + screen:
|
||||||
|
|
||||||
- `nodes canvas snapshot --node <id|name|ip> [--format png|jpg|jpeg] [--max-width <px>] [--quality <0-1>] [--invoke-timeout <ms>]`
|
- `nodes canvas snapshot --node <id|name|ip> [--format png|jpg|jpeg] [--max-width <px>] [--quality <0-1>] [--invoke-timeout <ms>]`
|
||||||
- `nodes canvas present --node <id|name|ip> [--target <urlOrPath>] [--x <px>] [--y <px>] [--width <px>] [--height <px>] [--invoke-timeout <ms>]`
|
- `nodes canvas present --node <id|name|ip> [--target <urlOrPath>] [--x <px>] [--y <px>] [--width <px>] [--height <px>] [--invoke-timeout <ms>]`
|
||||||
- `nodes canvas hide --node <id|name|ip> [--invoke-timeout <ms>]`
|
- `nodes canvas hide --node <id|name|ip> [--invoke-timeout <ms>]`
|
||||||
@@ -852,6 +955,7 @@ Canvas + screen:
|
|||||||
- `nodes screen record --node <id|name|ip> [--screen <index>] [--duration <ms|10s>] [--fps <n>] [--no-audio] [--out <path>] [--invoke-timeout <ms>]`
|
- `nodes screen record --node <id|name|ip> [--screen <index>] [--duration <ms|10s>] [--fps <n>] [--no-audio] [--out <path>] [--invoke-timeout <ms>]`
|
||||||
|
|
||||||
Location:
|
Location:
|
||||||
|
|
||||||
- `nodes location get --node <id|name|ip> [--max-age <ms>] [--accuracy <coarse|balanced|precise>] [--location-timeout <ms>] [--invoke-timeout <ms>]`
|
- `nodes location get --node <id|name|ip> [--max-age <ms>] [--accuracy <coarse|balanced|precise>] [--location-timeout <ms>] [--invoke-timeout <ms>]`
|
||||||
|
|
||||||
## Browser
|
## Browser
|
||||||
@@ -859,10 +963,12 @@ Location:
|
|||||||
Browser control CLI (dedicated Chrome/Brave/Edge/Chromium). See [`openclaw browser`](/cli/browser) and the [Browser tool](/tools/browser).
|
Browser control CLI (dedicated Chrome/Brave/Edge/Chromium). See [`openclaw browser`](/cli/browser) and the [Browser tool](/tools/browser).
|
||||||
|
|
||||||
Common options:
|
Common options:
|
||||||
|
|
||||||
- `--url`, `--token`, `--timeout`, `--json`
|
- `--url`, `--token`, `--timeout`, `--json`
|
||||||
- `--browser-profile <name>`
|
- `--browser-profile <name>`
|
||||||
|
|
||||||
Manage:
|
Manage:
|
||||||
|
|
||||||
- `browser status`
|
- `browser status`
|
||||||
- `browser start`
|
- `browser start`
|
||||||
- `browser stop`
|
- `browser stop`
|
||||||
@@ -876,10 +982,12 @@ Manage:
|
|||||||
- `browser delete-profile --name <name>`
|
- `browser delete-profile --name <name>`
|
||||||
|
|
||||||
Inspect:
|
Inspect:
|
||||||
|
|
||||||
- `browser screenshot [targetId] [--full-page] [--ref <ref>] [--element <selector>] [--type png|jpeg]`
|
- `browser screenshot [targetId] [--full-page] [--ref <ref>] [--element <selector>] [--type png|jpeg]`
|
||||||
- `browser snapshot [--format aria|ai] [--target-id <id>] [--limit <n>] [--interactive] [--compact] [--depth <n>] [--selector <sel>] [--out <path>]`
|
- `browser snapshot [--format aria|ai] [--target-id <id>] [--limit <n>] [--interactive] [--compact] [--depth <n>] [--selector <sel>] [--out <path>]`
|
||||||
|
|
||||||
Actions:
|
Actions:
|
||||||
|
|
||||||
- `browser navigate <url> [--target-id <id>]`
|
- `browser navigate <url> [--target-id <id>]`
|
||||||
- `browser resize <width> <height> [--target-id <id>]`
|
- `browser resize <width> <height> [--target-id <id>]`
|
||||||
- `browser click <ref> [--double] [--button <left|right|middle>] [--modifiers <csv>] [--target-id <id>]`
|
- `browser click <ref> [--double] [--button <left|right|middle>] [--modifiers <csv>] [--target-id <id>]`
|
||||||
@@ -899,14 +1007,17 @@ Actions:
|
|||||||
## Docs search
|
## Docs search
|
||||||
|
|
||||||
### `docs [query...]`
|
### `docs [query...]`
|
||||||
|
|
||||||
Search the live docs index.
|
Search the live docs index.
|
||||||
|
|
||||||
## TUI
|
## TUI
|
||||||
|
|
||||||
### `tui`
|
### `tui`
|
||||||
|
|
||||||
Open the terminal UI connected to the Gateway.
|
Open the terminal UI connected to the Gateway.
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|
||||||
- `--url <url>`
|
- `--url <url>`
|
||||||
- `--token <token>`
|
- `--token <token>`
|
||||||
- `--password <password>`
|
- `--password <password>`
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ summary: "CLI reference for `openclaw logs` (tail gateway logs via RPC)"
|
|||||||
read_when:
|
read_when:
|
||||||
- You need to tail Gateway logs remotely (without SSH)
|
- You need to tail Gateway logs remotely (without SSH)
|
||||||
- You want JSON log lines for tooling
|
- You want JSON log lines for tooling
|
||||||
|
title: "logs"
|
||||||
---
|
---
|
||||||
|
|
||||||
# `openclaw logs`
|
# `openclaw logs`
|
||||||
@@ -10,6 +11,7 @@ read_when:
|
|||||||
Tail Gateway file logs over RPC (works in remote mode).
|
Tail Gateway file logs over RPC (works in remote mode).
|
||||||
|
|
||||||
Related:
|
Related:
|
||||||
|
|
||||||
- Logging overview: [Logging](/logging)
|
- Logging overview: [Logging](/logging)
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
@@ -20,4 +22,3 @@ openclaw logs --follow
|
|||||||
openclaw logs --json
|
openclaw logs --json
|
||||||
openclaw logs --limit 500
|
openclaw logs --limit 500
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ summary: "CLI reference for `openclaw memory` (status/index/search)"
|
|||||||
read_when:
|
read_when:
|
||||||
- You want to index or search semantic memory
|
- You want to index or search semantic memory
|
||||||
- You’re debugging memory availability or indexing
|
- You’re debugging memory availability or indexing
|
||||||
|
title: "memory"
|
||||||
---
|
---
|
||||||
|
|
||||||
# `openclaw memory`
|
# `openclaw memory`
|
||||||
@@ -11,8 +12,9 @@ Manage semantic memory indexing and search.
|
|||||||
Provided by the active memory plugin (default: `memory-core`; set `plugins.slots.memory = "none"` to disable).
|
Provided by the active memory plugin (default: `memory-core`; set `plugins.slots.memory = "none"` to disable).
|
||||||
|
|
||||||
Related:
|
Related:
|
||||||
|
|
||||||
- Memory concept: [Memory](/concepts/memory)
|
- Memory concept: [Memory](/concepts/memory)
|
||||||
- Plugins: [Plugins](/plugins)
|
- Plugins: [Plugins](/plugins)
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
@@ -36,6 +38,7 @@ Common:
|
|||||||
- `--verbose`: emit detailed logs during probes and indexing.
|
- `--verbose`: emit detailed logs during probes and indexing.
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
- `memory status --deep` probes vector + embedding availability.
|
- `memory status --deep` probes vector + embedding availability.
|
||||||
- `memory status --deep --index` runs a reindex if the store is dirty.
|
- `memory status --deep --index` runs a reindex if the store is dirty.
|
||||||
- `memory index --verbose` prints per-phase details (provider, model, sources, batch activity).
|
- `memory index --verbose` prints per-phase details (provider, model, sources, batch activity).
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ summary: "CLI reference for `openclaw message` (send + channel actions)"
|
|||||||
read_when:
|
read_when:
|
||||||
- Adding or modifying message CLI actions
|
- Adding or modifying message CLI actions
|
||||||
- Changing outbound channel behavior
|
- Changing outbound channel behavior
|
||||||
|
title: "message"
|
||||||
---
|
---
|
||||||
|
|
||||||
# `openclaw message`
|
# `openclaw message`
|
||||||
@@ -17,11 +18,13 @@ openclaw message <subcommand> [flags]
|
|||||||
```
|
```
|
||||||
|
|
||||||
Channel selection:
|
Channel selection:
|
||||||
|
|
||||||
- `--channel` required if more than one channel is configured.
|
- `--channel` required if more than one channel is configured.
|
||||||
- If exactly one channel is configured, it becomes the default.
|
- If exactly one channel is configured, it becomes the default.
|
||||||
- Values: `whatsapp|telegram|discord|googlechat|slack|mattermost|signal|imessage|msteams` (Mattermost requires plugin)
|
- Values: `whatsapp|telegram|discord|googlechat|slack|mattermost|signal|imessage|msteams` (Mattermost requires plugin)
|
||||||
|
|
||||||
Target formats (`--target`):
|
Target formats (`--target`):
|
||||||
|
|
||||||
- WhatsApp: E.164 or group JID
|
- WhatsApp: E.164 or group JID
|
||||||
- Telegram: chat id or `@username`
|
- Telegram: chat id or `@username`
|
||||||
- Discord: `channel:<id>` or `user:<id>` (or `<@id>` mention; raw numeric ids are treated as channels)
|
- Discord: `channel:<id>` or `user:<id>` (or `<@id>` mention; raw numeric ids are treated as channels)
|
||||||
@@ -33,6 +36,7 @@ Target formats (`--target`):
|
|||||||
- MS Teams: conversation id (`19:...@thread.tacv2`) or `conversation:<id>` or `user:<aad-object-id>`
|
- MS Teams: conversation id (`19:...@thread.tacv2`) or `conversation:<id>` or `user:<aad-object-id>`
|
||||||
|
|
||||||
Name lookup:
|
Name lookup:
|
||||||
|
|
||||||
- For supported providers (Discord/Slack/etc), channel names like `Help` or `#help` are resolved via the directory cache.
|
- For supported providers (Discord/Slack/etc), channel names like `Help` or `#help` are resolved via the directory cache.
|
||||||
- On cache miss, OpenClaw will attempt a live directory lookup when the provider supports it.
|
- On cache miss, OpenClaw will attempt a live directory lookup when the provider supports it.
|
||||||
|
|
||||||
@@ -180,12 +184,14 @@ Name lookup:
|
|||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
Send a Discord reply:
|
Send a Discord reply:
|
||||||
|
|
||||||
```
|
```
|
||||||
openclaw message send --channel discord \
|
openclaw message send --channel discord \
|
||||||
--target channel:123 --message "hi" --reply-to 456
|
--target channel:123 --message "hi" --reply-to 456
|
||||||
```
|
```
|
||||||
|
|
||||||
Create a Discord poll:
|
Create a Discord poll:
|
||||||
|
|
||||||
```
|
```
|
||||||
openclaw message poll --channel discord \
|
openclaw message poll --channel discord \
|
||||||
--target channel:123 \
|
--target channel:123 \
|
||||||
@@ -195,12 +201,14 @@ openclaw message poll --channel discord \
|
|||||||
```
|
```
|
||||||
|
|
||||||
Send a Teams proactive message:
|
Send a Teams proactive message:
|
||||||
|
|
||||||
```
|
```
|
||||||
openclaw message send --channel msteams \
|
openclaw message send --channel msteams \
|
||||||
--target conversation:19:abc@thread.tacv2 --message "hi"
|
--target conversation:19:abc@thread.tacv2 --message "hi"
|
||||||
```
|
```
|
||||||
|
|
||||||
Create a Teams poll:
|
Create a Teams poll:
|
||||||
|
|
||||||
```
|
```
|
||||||
openclaw message poll --channel msteams \
|
openclaw message poll --channel msteams \
|
||||||
--target conversation:19:abc@thread.tacv2 \
|
--target conversation:19:abc@thread.tacv2 \
|
||||||
@@ -209,12 +217,14 @@ openclaw message poll --channel msteams \
|
|||||||
```
|
```
|
||||||
|
|
||||||
React in Slack:
|
React in Slack:
|
||||||
|
|
||||||
```
|
```
|
||||||
openclaw message react --channel slack \
|
openclaw message react --channel slack \
|
||||||
--target C123 --message-id 456 --emoji "✅"
|
--target C123 --message-id 456 --emoji "✅"
|
||||||
```
|
```
|
||||||
|
|
||||||
React in a Signal group:
|
React in a Signal group:
|
||||||
|
|
||||||
```
|
```
|
||||||
openclaw message react --channel signal \
|
openclaw message react --channel signal \
|
||||||
--target signal:group:abc123 --message-id 1737630212345 \
|
--target signal:group:abc123 --message-id 1737630212345 \
|
||||||
@@ -222,6 +232,7 @@ openclaw message react --channel signal \
|
|||||||
```
|
```
|
||||||
|
|
||||||
Send Telegram inline buttons:
|
Send Telegram inline buttons:
|
||||||
|
|
||||||
```
|
```
|
||||||
openclaw message send --channel telegram --target @mychat --message "Choose:" \
|
openclaw message send --channel telegram --target @mychat --message "Choose:" \
|
||||||
--buttons '[ [{"text":"Yes","callback_data":"cmd:yes"}], [{"text":"No","callback_data":"cmd:no"}] ]'
|
--buttons '[ [{"text":"Yes","callback_data":"cmd:yes"}], [{"text":"No","callback_data":"cmd:no"}] ]'
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user