chore: Run pnpm format:fix.
This commit is contained in:
@@ -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'
|
||||||
|
|||||||
@@ -7,168 +7,179 @@
|
|||||||
|
|
||||||
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
||||||
import { DynamicBorder } from "@mariozechner/pi-coding-agent";
|
import { DynamicBorder } from "@mariozechner/pi-coding-agent";
|
||||||
import { Container, Key, matchesKey, type SelectItem, SelectList, Text } from "@mariozechner/pi-tui";
|
import {
|
||||||
|
Container,
|
||||||
|
Key,
|
||||||
|
matchesKey,
|
||||||
|
type SelectItem,
|
||||||
|
SelectList,
|
||||||
|
Text,
|
||||||
|
} from "@mariozechner/pi-tui";
|
||||||
|
|
||||||
interface FileInfo {
|
interface FileInfo {
|
||||||
status: string;
|
status: string;
|
||||||
statusLabel: string;
|
statusLabel: string;
|
||||||
file: string;
|
file: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function (pi: ExtensionAPI) {
|
export default function (pi: ExtensionAPI) {
|
||||||
pi.registerCommand("diff", {
|
pi.registerCommand("diff", {
|
||||||
description: "Show git changes and open in VS Code diff view",
|
description: "Show git changes and open in VS Code diff view",
|
||||||
handler: async (_args, ctx) => {
|
handler: async (_args, ctx) => {
|
||||||
if (!ctx.hasUI) {
|
if (!ctx.hasUI) {
|
||||||
ctx.ui.notify("No UI available", "error");
|
ctx.ui.notify("No UI available", "error");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get changed files from git status
|
// Get changed files from git status
|
||||||
const result = await pi.exec("git", ["status", "--porcelain"], { cwd: ctx.cwd });
|
const result = await pi.exec("git", ["status", "--porcelain"], { cwd: ctx.cwd });
|
||||||
|
|
||||||
if (result.code !== 0) {
|
if (result.code !== 0) {
|
||||||
ctx.ui.notify(`git status failed: ${result.stderr}`, "error");
|
ctx.ui.notify(`git status failed: ${result.stderr}`, "error");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!result.stdout || !result.stdout.trim()) {
|
if (!result.stdout || !result.stdout.trim()) {
|
||||||
ctx.ui.notify("No changes in working tree", "info");
|
ctx.ui.notify("No changes in working tree", "info");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse git status output
|
// Parse git status output
|
||||||
// Format: XY filename (where XY is two-letter status, then space, then filename)
|
// Format: XY filename (where XY is two-letter status, then space, then filename)
|
||||||
const lines = result.stdout.split("\n");
|
const lines = result.stdout.split("\n");
|
||||||
const files: FileInfo[] = [];
|
const files: FileInfo[] = [];
|
||||||
|
|
||||||
for (const line of lines) {
|
for (const line of lines) {
|
||||||
if (line.length < 4) continue; // Need at least "XY f"
|
if (line.length < 4) continue; // Need at least "XY f"
|
||||||
|
|
||||||
const status = line.slice(0, 2);
|
const status = line.slice(0, 2);
|
||||||
const file = line.slice(2).trimStart();
|
const file = line.slice(2).trimStart();
|
||||||
|
|
||||||
// Translate status codes to short labels
|
// Translate status codes to short labels
|
||||||
let statusLabel: string;
|
let statusLabel: string;
|
||||||
if (status.includes("M")) statusLabel = "M";
|
if (status.includes("M")) statusLabel = "M";
|
||||||
else if (status.includes("A")) statusLabel = "A";
|
else if (status.includes("A")) statusLabel = "A";
|
||||||
else if (status.includes("D")) statusLabel = "D";
|
else if (status.includes("D")) statusLabel = "D";
|
||||||
else if (status.includes("?")) statusLabel = "?";
|
else if (status.includes("?")) statusLabel = "?";
|
||||||
else if (status.includes("R")) statusLabel = "R";
|
else if (status.includes("R")) statusLabel = "R";
|
||||||
else if (status.includes("C")) statusLabel = "C";
|
else if (status.includes("C")) statusLabel = "C";
|
||||||
else statusLabel = status.trim() || "~";
|
else statusLabel = status.trim() || "~";
|
||||||
|
|
||||||
files.push({ status: statusLabel, statusLabel, file });
|
files.push({ status: statusLabel, statusLabel, file });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (files.length === 0) {
|
if (files.length === 0) {
|
||||||
ctx.ui.notify("No changes found", "info");
|
ctx.ui.notify("No changes found", "info");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const openSelected = async (fileInfo: FileInfo): Promise<void> => {
|
const openSelected = async (fileInfo: FileInfo): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
// Open in VS Code diff view.
|
// Open in VS Code diff view.
|
||||||
// For untracked files, git difftool won't work, so fall back to just opening the file.
|
// For untracked files, git difftool won't work, so fall back to just opening the file.
|
||||||
if (fileInfo.status === "?") {
|
if (fileInfo.status === "?") {
|
||||||
await pi.exec("code", ["-g", fileInfo.file], { cwd: ctx.cwd });
|
await pi.exec("code", ["-g", fileInfo.file], { cwd: ctx.cwd });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const diffResult = await pi.exec("git", ["difftool", "-y", "--tool=vscode", fileInfo.file], {
|
const diffResult = await pi.exec(
|
||||||
cwd: ctx.cwd,
|
"git",
|
||||||
});
|
["difftool", "-y", "--tool=vscode", fileInfo.file],
|
||||||
if (diffResult.code !== 0) {
|
{
|
||||||
await pi.exec("code", ["-g", fileInfo.file], { cwd: ctx.cwd });
|
cwd: ctx.cwd,
|
||||||
}
|
},
|
||||||
} catch (error) {
|
);
|
||||||
const message = error instanceof Error ? error.message : String(error);
|
if (diffResult.code !== 0) {
|
||||||
ctx.ui.notify(`Failed to open ${fileInfo.file}: ${message}`, "error");
|
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
|
// Show file picker with SelectList
|
||||||
await ctx.ui.custom<void>((tui, theme, _kb, done) => {
|
await ctx.ui.custom<void>((tui, theme, _kb, done) => {
|
||||||
const container = new Container();
|
const container = new Container();
|
||||||
|
|
||||||
// Top border
|
// Top border
|
||||||
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
||||||
|
|
||||||
// Title
|
// Title
|
||||||
container.addChild(new Text(theme.fg("accent", theme.bold(" Select file to diff")), 0, 0));
|
container.addChild(new Text(theme.fg("accent", theme.bold(" Select file to diff")), 0, 0));
|
||||||
|
|
||||||
// Build select items with colored status
|
// Build select items with colored status
|
||||||
const items: SelectItem[] = files.map((f) => {
|
const items: SelectItem[] = files.map((f) => {
|
||||||
let statusColor: string;
|
let statusColor: string;
|
||||||
switch (f.status) {
|
switch (f.status) {
|
||||||
case "M":
|
case "M":
|
||||||
statusColor = theme.fg("warning", f.status);
|
statusColor = theme.fg("warning", f.status);
|
||||||
break;
|
break;
|
||||||
case "A":
|
case "A":
|
||||||
statusColor = theme.fg("success", f.status);
|
statusColor = theme.fg("success", f.status);
|
||||||
break;
|
break;
|
||||||
case "D":
|
case "D":
|
||||||
statusColor = theme.fg("error", f.status);
|
statusColor = theme.fg("error", f.status);
|
||||||
break;
|
break;
|
||||||
case "?":
|
case "?":
|
||||||
statusColor = theme.fg("muted", f.status);
|
statusColor = theme.fg("muted", f.status);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
statusColor = theme.fg("dim", f.status);
|
statusColor = theme.fg("dim", f.status);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
value: f,
|
value: f,
|
||||||
label: `${statusColor} ${f.file}`,
|
label: `${statusColor} ${f.file}`,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const visibleRows = Math.min(files.length, 15);
|
const visibleRows = Math.min(files.length, 15);
|
||||||
let currentIndex = 0;
|
let currentIndex = 0;
|
||||||
|
|
||||||
const selectList = new SelectList(items, visibleRows, {
|
const selectList = new SelectList(items, visibleRows, {
|
||||||
selectedPrefix: (t) => theme.fg("accent", t),
|
selectedPrefix: (t) => theme.fg("accent", t),
|
||||||
selectedText: (t) => t, // Keep existing colors
|
selectedText: (t) => t, // Keep existing colors
|
||||||
description: (t) => theme.fg("muted", t),
|
description: (t) => theme.fg("muted", t),
|
||||||
scrollInfo: (t) => theme.fg("dim", t),
|
scrollInfo: (t) => theme.fg("dim", t),
|
||||||
noMatch: (t) => theme.fg("warning", t),
|
noMatch: (t) => theme.fg("warning", t),
|
||||||
});
|
});
|
||||||
selectList.onSelect = (item) => {
|
selectList.onSelect = (item) => {
|
||||||
void openSelected(item.value as FileInfo);
|
void openSelected(item.value as FileInfo);
|
||||||
};
|
};
|
||||||
selectList.onCancel = () => done();
|
selectList.onCancel = () => done();
|
||||||
selectList.onSelectionChange = (item) => {
|
selectList.onSelectionChange = (item) => {
|
||||||
currentIndex = items.indexOf(item);
|
currentIndex = items.indexOf(item);
|
||||||
};
|
};
|
||||||
container.addChild(selectList);
|
container.addChild(selectList);
|
||||||
|
|
||||||
// Help text
|
// Help text
|
||||||
container.addChild(
|
container.addChild(
|
||||||
new Text(theme.fg("dim", " ↑↓ navigate • ←→ page • enter open • esc close"), 0, 0),
|
new Text(theme.fg("dim", " ↑↓ navigate • ←→ page • enter open • esc close"), 0, 0),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Bottom border
|
// Bottom border
|
||||||
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
render: (w) => container.render(w),
|
render: (w) => container.render(w),
|
||||||
invalidate: () => container.invalidate(),
|
invalidate: () => container.invalidate(),
|
||||||
handleInput: (data) => {
|
handleInput: (data) => {
|
||||||
// Add paging with left/right
|
// Add paging with left/right
|
||||||
if (matchesKey(data, Key.left)) {
|
if (matchesKey(data, Key.left)) {
|
||||||
// Page up - clamp to 0
|
// Page up - clamp to 0
|
||||||
currentIndex = Math.max(0, currentIndex - visibleRows);
|
currentIndex = Math.max(0, currentIndex - visibleRows);
|
||||||
selectList.setSelectedIndex(currentIndex);
|
selectList.setSelectedIndex(currentIndex);
|
||||||
} else if (matchesKey(data, Key.right)) {
|
} else if (matchesKey(data, Key.right)) {
|
||||||
// Page down - clamp to last
|
// Page down - clamp to last
|
||||||
currentIndex = Math.min(items.length - 1, currentIndex + visibleRows);
|
currentIndex = Math.min(items.length - 1, currentIndex + visibleRows);
|
||||||
selectList.setSelectedIndex(currentIndex);
|
selectList.setSelectedIndex(currentIndex);
|
||||||
} else {
|
} else {
|
||||||
selectList.handleInput(data);
|
selectList.handleInput(data);
|
||||||
}
|
}
|
||||||
tui.requestRender();
|
tui.requestRender();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,167 +7,174 @@
|
|||||||
|
|
||||||
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
||||||
import { DynamicBorder } from "@mariozechner/pi-coding-agent";
|
import { DynamicBorder } from "@mariozechner/pi-coding-agent";
|
||||||
import { Container, Key, matchesKey, type SelectItem, SelectList, Text } from "@mariozechner/pi-tui";
|
import {
|
||||||
|
Container,
|
||||||
|
Key,
|
||||||
|
matchesKey,
|
||||||
|
type SelectItem,
|
||||||
|
SelectList,
|
||||||
|
Text,
|
||||||
|
} from "@mariozechner/pi-tui";
|
||||||
|
|
||||||
interface FileEntry {
|
interface FileEntry {
|
||||||
path: string;
|
path: string;
|
||||||
operations: Set<"read" | "write" | "edit">;
|
operations: Set<"read" | "write" | "edit">;
|
||||||
lastTimestamp: number;
|
lastTimestamp: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
type FileToolName = "read" | "write" | "edit";
|
type FileToolName = "read" | "write" | "edit";
|
||||||
|
|
||||||
export default function (pi: ExtensionAPI) {
|
export default function (pi: ExtensionAPI) {
|
||||||
pi.registerCommand("files", {
|
pi.registerCommand("files", {
|
||||||
description: "Show files read/written/edited in this session",
|
description: "Show files read/written/edited in this session",
|
||||||
handler: async (_args, ctx) => {
|
handler: async (_args, ctx) => {
|
||||||
if (!ctx.hasUI) {
|
if (!ctx.hasUI) {
|
||||||
ctx.ui.notify("No UI available", "error");
|
ctx.ui.notify("No UI available", "error");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the current branch (path from leaf to root)
|
// Get the current branch (path from leaf to root)
|
||||||
const branch = ctx.sessionManager.getBranch();
|
const branch = ctx.sessionManager.getBranch();
|
||||||
|
|
||||||
// First pass: collect tool calls (id -> {path, name}) from assistant messages
|
// First pass: collect tool calls (id -> {path, name}) from assistant messages
|
||||||
const toolCalls = new Map<string, { path: string; name: FileToolName; timestamp: number }>();
|
const toolCalls = new Map<string, { path: string; name: FileToolName; timestamp: number }>();
|
||||||
|
|
||||||
for (const entry of branch) {
|
for (const entry of branch) {
|
||||||
if (entry.type !== "message") continue;
|
if (entry.type !== "message") continue;
|
||||||
const msg = entry.message;
|
const msg = entry.message;
|
||||||
|
|
||||||
if (msg.role === "assistant" && Array.isArray(msg.content)) {
|
if (msg.role === "assistant" && Array.isArray(msg.content)) {
|
||||||
for (const block of msg.content) {
|
for (const block of msg.content) {
|
||||||
if (block.type === "toolCall") {
|
if (block.type === "toolCall") {
|
||||||
const name = block.name;
|
const name = block.name;
|
||||||
if (name === "read" || name === "write" || name === "edit") {
|
if (name === "read" || name === "write" || name === "edit") {
|
||||||
const path = block.arguments?.path;
|
const path = block.arguments?.path;
|
||||||
if (path && typeof path === "string") {
|
if (path && typeof path === "string") {
|
||||||
toolCalls.set(block.id, { path, name, timestamp: msg.timestamp });
|
toolCalls.set(block.id, { path, name, timestamp: msg.timestamp });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Second pass: match tool results to get the actual execution timestamp
|
// Second pass: match tool results to get the actual execution timestamp
|
||||||
const fileMap = new Map<string, FileEntry>();
|
const fileMap = new Map<string, FileEntry>();
|
||||||
|
|
||||||
for (const entry of branch) {
|
for (const entry of branch) {
|
||||||
if (entry.type !== "message") continue;
|
if (entry.type !== "message") continue;
|
||||||
const msg = entry.message;
|
const msg = entry.message;
|
||||||
|
|
||||||
if (msg.role === "toolResult") {
|
if (msg.role === "toolResult") {
|
||||||
const toolCall = toolCalls.get(msg.toolCallId);
|
const toolCall = toolCalls.get(msg.toolCallId);
|
||||||
if (!toolCall) continue;
|
if (!toolCall) continue;
|
||||||
|
|
||||||
const { path, name } = toolCall;
|
const { path, name } = toolCall;
|
||||||
const timestamp = msg.timestamp;
|
const timestamp = msg.timestamp;
|
||||||
|
|
||||||
const existing = fileMap.get(path);
|
const existing = fileMap.get(path);
|
||||||
if (existing) {
|
if (existing) {
|
||||||
existing.operations.add(name);
|
existing.operations.add(name);
|
||||||
if (timestamp > existing.lastTimestamp) {
|
if (timestamp > existing.lastTimestamp) {
|
||||||
existing.lastTimestamp = timestamp;
|
existing.lastTimestamp = timestamp;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fileMap.set(path, {
|
fileMap.set(path, {
|
||||||
path,
|
path,
|
||||||
operations: new Set([name]),
|
operations: new Set([name]),
|
||||||
lastTimestamp: timestamp,
|
lastTimestamp: timestamp,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fileMap.size === 0) {
|
if (fileMap.size === 0) {
|
||||||
ctx.ui.notify("No files read/written/edited in this session", "info");
|
ctx.ui.notify("No files read/written/edited in this session", "info");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort by most recent first
|
// Sort by most recent first
|
||||||
const files = Array.from(fileMap.values()).sort((a, b) => b.lastTimestamp - a.lastTimestamp);
|
const files = Array.from(fileMap.values()).sort((a, b) => b.lastTimestamp - a.lastTimestamp);
|
||||||
|
|
||||||
const openSelected = async (file: FileEntry): Promise<void> => {
|
const openSelected = async (file: FileEntry): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
await pi.exec("code", ["-g", file.path], { cwd: ctx.cwd });
|
await pi.exec("code", ["-g", file.path], { cwd: ctx.cwd });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message = error instanceof Error ? error.message : String(error);
|
const message = error instanceof Error ? error.message : String(error);
|
||||||
ctx.ui.notify(`Failed to open ${file.path}: ${message}`, "error");
|
ctx.ui.notify(`Failed to open ${file.path}: ${message}`, "error");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Show file picker with SelectList
|
// Show file picker with SelectList
|
||||||
await ctx.ui.custom<void>((tui, theme, _kb, done) => {
|
await ctx.ui.custom<void>((tui, theme, _kb, done) => {
|
||||||
const container = new Container();
|
const container = new Container();
|
||||||
|
|
||||||
// Top border
|
// Top border
|
||||||
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
||||||
|
|
||||||
// Title
|
// Title
|
||||||
container.addChild(new Text(theme.fg("accent", theme.bold(" Select file to open")), 0, 0));
|
container.addChild(new Text(theme.fg("accent", theme.bold(" Select file to open")), 0, 0));
|
||||||
|
|
||||||
// Build select items with colored operations
|
// Build select items with colored operations
|
||||||
const items: SelectItem[] = files.map((f) => {
|
const items: SelectItem[] = files.map((f) => {
|
||||||
const ops: string[] = [];
|
const ops: string[] = [];
|
||||||
if (f.operations.has("read")) ops.push(theme.fg("muted", "R"));
|
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("write")) ops.push(theme.fg("success", "W"));
|
||||||
if (f.operations.has("edit")) ops.push(theme.fg("warning", "E"));
|
if (f.operations.has("edit")) ops.push(theme.fg("warning", "E"));
|
||||||
const opsLabel = ops.join("");
|
const opsLabel = ops.join("");
|
||||||
return {
|
return {
|
||||||
value: f,
|
value: f,
|
||||||
label: `${opsLabel} ${f.path}`,
|
label: `${opsLabel} ${f.path}`,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const visibleRows = Math.min(files.length, 15);
|
const visibleRows = Math.min(files.length, 15);
|
||||||
let currentIndex = 0;
|
let currentIndex = 0;
|
||||||
|
|
||||||
const selectList = new SelectList(items, visibleRows, {
|
const selectList = new SelectList(items, visibleRows, {
|
||||||
selectedPrefix: (t) => theme.fg("accent", t),
|
selectedPrefix: (t) => theme.fg("accent", t),
|
||||||
selectedText: (t) => t, // Keep existing colors
|
selectedText: (t) => t, // Keep existing colors
|
||||||
description: (t) => theme.fg("muted", t),
|
description: (t) => theme.fg("muted", t),
|
||||||
scrollInfo: (t) => theme.fg("dim", t),
|
scrollInfo: (t) => theme.fg("dim", t),
|
||||||
noMatch: (t) => theme.fg("warning", t),
|
noMatch: (t) => theme.fg("warning", t),
|
||||||
});
|
});
|
||||||
selectList.onSelect = (item) => {
|
selectList.onSelect = (item) => {
|
||||||
void openSelected(item.value as FileEntry);
|
void openSelected(item.value as FileEntry);
|
||||||
};
|
};
|
||||||
selectList.onCancel = () => done();
|
selectList.onCancel = () => done();
|
||||||
selectList.onSelectionChange = (item) => {
|
selectList.onSelectionChange = (item) => {
|
||||||
currentIndex = items.indexOf(item);
|
currentIndex = items.indexOf(item);
|
||||||
};
|
};
|
||||||
container.addChild(selectList);
|
container.addChild(selectList);
|
||||||
|
|
||||||
// Help text
|
// Help text
|
||||||
container.addChild(
|
container.addChild(
|
||||||
new Text(theme.fg("dim", " ↑↓ navigate • ←→ page • enter open • esc close"), 0, 0),
|
new Text(theme.fg("dim", " ↑↓ navigate • ←→ page • enter open • esc close"), 0, 0),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Bottom border
|
// Bottom border
|
||||||
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
container.addChild(new DynamicBorder((s: string) => theme.fg("accent", s)));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
render: (w) => container.render(w),
|
render: (w) => container.render(w),
|
||||||
invalidate: () => container.invalidate(),
|
invalidate: () => container.invalidate(),
|
||||||
handleInput: (data) => {
|
handleInput: (data) => {
|
||||||
// Add paging with left/right
|
// Add paging with left/right
|
||||||
if (matchesKey(data, Key.left)) {
|
if (matchesKey(data, Key.left)) {
|
||||||
// Page up - clamp to 0
|
// Page up - clamp to 0
|
||||||
currentIndex = Math.max(0, currentIndex - visibleRows);
|
currentIndex = Math.max(0, currentIndex - visibleRows);
|
||||||
selectList.setSelectedIndex(currentIndex);
|
selectList.setSelectedIndex(currentIndex);
|
||||||
} else if (matchesKey(data, Key.right)) {
|
} else if (matchesKey(data, Key.right)) {
|
||||||
// Page down - clamp to last
|
// Page down - clamp to last
|
||||||
currentIndex = Math.min(items.length - 1, currentIndex + visibleRows);
|
currentIndex = Math.min(items.length - 1, currentIndex + visibleRows);
|
||||||
selectList.setSelectedIndex(currentIndex);
|
selectList.setSelectedIndex(currentIndex);
|
||||||
} else {
|
} else {
|
||||||
selectList.handleInput(data);
|
selectList.handleInput(data);
|
||||||
}
|
}
|
||||||
tui.requestRender();
|
tui.requestRender();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,158 +1,171 @@
|
|||||||
import { DynamicBorder, type ExtensionAPI, type ExtensionContext } from "@mariozechner/pi-coding-agent";
|
import {
|
||||||
|
DynamicBorder,
|
||||||
|
type ExtensionAPI,
|
||||||
|
type ExtensionContext,
|
||||||
|
} from "@mariozechner/pi-coding-agent";
|
||||||
import { Container, Text } from "@mariozechner/pi-tui";
|
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 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;
|
const ISSUE_PROMPT_PATTERN = /^\s*Analyze GitHub issue\(s\):\s*(\S+)/im;
|
||||||
|
|
||||||
type PromptMatch = {
|
type PromptMatch = {
|
||||||
kind: "pr" | "issue";
|
kind: "pr" | "issue";
|
||||||
url: string;
|
url: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type GhMetadata = {
|
type GhMetadata = {
|
||||||
title?: string;
|
title?: string;
|
||||||
author?: {
|
author?: {
|
||||||
login?: string;
|
login?: string;
|
||||||
name?: string | null;
|
name?: string | null;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
function extractPromptMatch(prompt: string): PromptMatch | undefined {
|
function extractPromptMatch(prompt: string): PromptMatch | undefined {
|
||||||
const prMatch = prompt.match(PR_PROMPT_PATTERN);
|
const prMatch = prompt.match(PR_PROMPT_PATTERN);
|
||||||
if (prMatch?.[1]) {
|
if (prMatch?.[1]) {
|
||||||
return { kind: "pr", url: prMatch[1].trim() };
|
return { kind: "pr", url: prMatch[1].trim() };
|
||||||
}
|
}
|
||||||
|
|
||||||
const issueMatch = prompt.match(ISSUE_PROMPT_PATTERN);
|
const issueMatch = prompt.match(ISSUE_PROMPT_PATTERN);
|
||||||
if (issueMatch?.[1]) {
|
if (issueMatch?.[1]) {
|
||||||
return { kind: "issue", url: issueMatch[1].trim() };
|
return { kind: "issue", url: issueMatch[1].trim() };
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchGhMetadata(
|
async function fetchGhMetadata(
|
||||||
pi: ExtensionAPI,
|
pi: ExtensionAPI,
|
||||||
kind: PromptMatch["kind"],
|
kind: PromptMatch["kind"],
|
||||||
url: string,
|
url: string,
|
||||||
): Promise<GhMetadata | undefined> {
|
): Promise<GhMetadata | undefined> {
|
||||||
const args =
|
const args =
|
||||||
kind === "pr" ? ["pr", "view", url, "--json", "title,author"] : ["issue", "view", url, "--json", "title,author"];
|
kind === "pr"
|
||||||
|
? ["pr", "view", url, "--json", "title,author"]
|
||||||
|
: ["issue", "view", url, "--json", "title,author"];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await pi.exec("gh", args);
|
const result = await pi.exec("gh", args);
|
||||||
if (result.code !== 0 || !result.stdout) return undefined;
|
if (result.code !== 0 || !result.stdout) return undefined;
|
||||||
return JSON.parse(result.stdout) as GhMetadata;
|
return JSON.parse(result.stdout) as GhMetadata;
|
||||||
} catch {
|
} catch {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatAuthor(author?: GhMetadata["author"]): string | undefined {
|
function formatAuthor(author?: GhMetadata["author"]): string | undefined {
|
||||||
if (!author) return undefined;
|
if (!author) return undefined;
|
||||||
const name = author.name?.trim();
|
const name = author.name?.trim();
|
||||||
const login = author.login?.trim();
|
const login = author.login?.trim();
|
||||||
if (name && login) return `${name} (@${login})`;
|
if (name && login) return `${name} (@${login})`;
|
||||||
if (login) return `@${login}`;
|
if (login) return `@${login}`;
|
||||||
if (name) return name;
|
if (name) return name;
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function promptUrlWidgetExtension(pi: ExtensionAPI) {
|
export default function promptUrlWidgetExtension(pi: ExtensionAPI) {
|
||||||
const setWidget = (ctx: ExtensionContext, match: PromptMatch, title?: string, authorText?: string) => {
|
const setWidget = (
|
||||||
ctx.ui.setWidget("prompt-url", (_tui, thm) => {
|
ctx: ExtensionContext,
|
||||||
const titleText = title ? thm.fg("accent", title) : thm.fg("accent", match.url);
|
match: PromptMatch,
|
||||||
const authorLine = authorText ? thm.fg("muted", authorText) : undefined;
|
title?: string,
|
||||||
const urlLine = thm.fg("dim", match.url);
|
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];
|
const lines = [titleText];
|
||||||
if (authorLine) lines.push(authorLine);
|
if (authorLine) lines.push(authorLine);
|
||||||
lines.push(urlLine);
|
lines.push(urlLine);
|
||||||
|
|
||||||
const container = new Container();
|
const container = new Container();
|
||||||
container.addChild(new DynamicBorder((s: string) => thm.fg("muted", s)));
|
container.addChild(new DynamicBorder((s: string) => thm.fg("muted", s)));
|
||||||
container.addChild(new Text(lines.join("\n"), 1, 0));
|
container.addChild(new Text(lines.join("\n"), 1, 0));
|
||||||
return container;
|
return container;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const applySessionName = (ctx: ExtensionContext, match: PromptMatch, title?: string) => {
|
const applySessionName = (ctx: ExtensionContext, match: PromptMatch, title?: string) => {
|
||||||
const label = match.kind === "pr" ? "PR" : "Issue";
|
const label = match.kind === "pr" ? "PR" : "Issue";
|
||||||
const trimmedTitle = title?.trim();
|
const trimmedTitle = title?.trim();
|
||||||
const fallbackName = `${label}: ${match.url}`;
|
const fallbackName = `${label}: ${match.url}`;
|
||||||
const desiredName = trimmedTitle ? `${label}: ${trimmedTitle} (${match.url})` : fallbackName;
|
const desiredName = trimmedTitle ? `${label}: ${trimmedTitle} (${match.url})` : fallbackName;
|
||||||
const currentName = pi.getSessionName()?.trim();
|
const currentName = pi.getSessionName()?.trim();
|
||||||
if (!currentName) {
|
if (!currentName) {
|
||||||
pi.setSessionName(desiredName);
|
pi.setSessionName(desiredName);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (currentName === match.url || currentName === fallbackName) {
|
if (currentName === match.url || currentName === fallbackName) {
|
||||||
pi.setSessionName(desiredName);
|
pi.setSessionName(desiredName);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
pi.on("before_agent_start", async (event, ctx) => {
|
pi.on("before_agent_start", async (event, ctx) => {
|
||||||
if (!ctx.hasUI) return;
|
if (!ctx.hasUI) return;
|
||||||
const match = extractPromptMatch(event.prompt);
|
const match = extractPromptMatch(event.prompt);
|
||||||
if (!match) {
|
if (!match) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setWidget(ctx, match);
|
setWidget(ctx, match);
|
||||||
applySessionName(ctx, match);
|
applySessionName(ctx, match);
|
||||||
void fetchGhMetadata(pi, match.kind, match.url).then((meta) => {
|
void fetchGhMetadata(pi, match.kind, match.url).then((meta) => {
|
||||||
const title = meta?.title?.trim();
|
const title = meta?.title?.trim();
|
||||||
const authorText = formatAuthor(meta?.author);
|
const authorText = formatAuthor(meta?.author);
|
||||||
setWidget(ctx, match, title, authorText);
|
setWidget(ctx, match, title, authorText);
|
||||||
applySessionName(ctx, match, title);
|
applySessionName(ctx, match, title);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
pi.on("session_switch", async (_event, ctx) => {
|
pi.on("session_switch", async (_event, ctx) => {
|
||||||
rebuildFromSession(ctx);
|
rebuildFromSession(ctx);
|
||||||
});
|
});
|
||||||
|
|
||||||
const getUserText = (content: string | { type: string; text?: string }[] | undefined): string => {
|
const getUserText = (content: string | { type: string; text?: string }[] | undefined): string => {
|
||||||
if (!content) return "";
|
if (!content) return "";
|
||||||
if (typeof content === "string") return content;
|
if (typeof content === "string") return content;
|
||||||
return (
|
return (
|
||||||
content
|
content
|
||||||
.filter((block): block is { type: "text"; text: string } => block.type === "text")
|
.filter((block): block is { type: "text"; text: string } => block.type === "text")
|
||||||
.map((block) => block.text)
|
.map((block) => block.text)
|
||||||
.join("\n") ?? ""
|
.join("\n") ?? ""
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const rebuildFromSession = (ctx: ExtensionContext) => {
|
const rebuildFromSession = (ctx: ExtensionContext) => {
|
||||||
if (!ctx.hasUI) return;
|
if (!ctx.hasUI) return;
|
||||||
|
|
||||||
const entries = ctx.sessionManager.getEntries();
|
const entries = ctx.sessionManager.getEntries();
|
||||||
const lastMatch = [...entries].reverse().find((entry) => {
|
const lastMatch = [...entries].reverse().find((entry) => {
|
||||||
if (entry.type !== "message" || entry.message.role !== "user") return false;
|
if (entry.type !== "message" || entry.message.role !== "user") return false;
|
||||||
const text = getUserText(entry.message.content);
|
const text = getUserText(entry.message.content);
|
||||||
return !!extractPromptMatch(text);
|
return !!extractPromptMatch(text);
|
||||||
});
|
});
|
||||||
|
|
||||||
const content =
|
const content =
|
||||||
lastMatch?.type === "message" && lastMatch.message.role === "user" ? lastMatch.message.content : undefined;
|
lastMatch?.type === "message" && lastMatch.message.role === "user"
|
||||||
const text = getUserText(content);
|
? lastMatch.message.content
|
||||||
const match = text ? extractPromptMatch(text) : undefined;
|
: undefined;
|
||||||
if (!match) {
|
const text = getUserText(content);
|
||||||
ctx.ui.setWidget("prompt-url", undefined);
|
const match = text ? extractPromptMatch(text) : undefined;
|
||||||
return;
|
if (!match) {
|
||||||
}
|
ctx.ui.setWidget("prompt-url", undefined);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setWidget(ctx, match);
|
setWidget(ctx, match);
|
||||||
applySessionName(ctx, match);
|
applySessionName(ctx, match);
|
||||||
void fetchGhMetadata(pi, match.kind, match.url).then((meta) => {
|
void fetchGhMetadata(pi, match.kind, match.url).then((meta) => {
|
||||||
const title = meta?.title?.trim();
|
const title = meta?.title?.trim();
|
||||||
const authorText = formatAuthor(meta?.author);
|
const authorText = formatAuthor(meta?.author);
|
||||||
setWidget(ctx, match, title, authorText);
|
setWidget(ctx, match, title, authorText);
|
||||||
applySessionName(ctx, match, title);
|
applySessionName(ctx, match, title);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
pi.on("session_start", async (_event, ctx) => {
|
pi.on("session_start", async (_event, ctx) => {
|
||||||
rebuildFromSession(ctx);
|
rebuildFromSession(ctx);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,17 +8,17 @@ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|||||||
import { Text } from "@mariozechner/pi-tui";
|
import { Text } from "@mariozechner/pi-tui";
|
||||||
|
|
||||||
export default function (pi: ExtensionAPI) {
|
export default function (pi: ExtensionAPI) {
|
||||||
pi.registerCommand("tui", {
|
pi.registerCommand("tui", {
|
||||||
description: "Show TUI stats",
|
description: "Show TUI stats",
|
||||||
handler: async (_args, ctx) => {
|
handler: async (_args, ctx) => {
|
||||||
if (!ctx.hasUI) return;
|
if (!ctx.hasUI) return;
|
||||||
let redraws = 0;
|
let redraws = 0;
|
||||||
await ctx.ui.custom<void>((tui, _theme, _keybindings, done) => {
|
await ctx.ui.custom<void>((tui, _theme, _keybindings, done) => {
|
||||||
redraws = tui.fullRedraws;
|
redraws = tui.fullRedraws;
|
||||||
done(undefined);
|
done(undefined);
|
||||||
return new Text("", 0, 0);
|
return new Text("", 0, 0);
|
||||||
});
|
});
|
||||||
ctx.ui.notify(`TUI full redraws: ${redraws}`, "info");
|
ctx.ui.notify(`TUI full redraws: ${redraws}`, "info");
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,19 @@
|
|||||||
---
|
---
|
||||||
description: Audit changelog entries before release
|
description: Audit changelog entries before release
|
||||||
---
|
---
|
||||||
|
|
||||||
Audit changelog entries for all commits since the last release.
|
Audit changelog entries for all commits since the last release.
|
||||||
|
|
||||||
## Process
|
## Process
|
||||||
|
|
||||||
1. **Find the last release tag:**
|
1. **Find the last release tag:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git tag --sort=-version:refname | head -1
|
git tag --sort=-version:refname | head -1
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **List all commits since that tag:**
|
2. **List all commits since that tag:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git log <tag>..HEAD --oneline
|
git log <tag>..HEAD --oneline
|
||||||
```
|
```
|
||||||
@@ -42,6 +45,7 @@ Audit changelog entries for all commits since the last release.
|
|||||||
## Changelog Format Reference
|
## Changelog Format Reference
|
||||||
|
|
||||||
Sections (in order):
|
Sections (in order):
|
||||||
|
|
||||||
- `### Breaking Changes` - API changes requiring migration
|
- `### Breaking Changes` - API changes requiring migration
|
||||||
- `### Added` - New features
|
- `### Added` - New features
|
||||||
- `### Changed` - Changes to existing functionality
|
- `### Changed` - Changes to existing functionality
|
||||||
@@ -49,5 +53,6 @@ Sections (in order):
|
|||||||
- `### Removed` - Removed features
|
- `### Removed` - Removed features
|
||||||
|
|
||||||
Attribution:
|
Attribution:
|
||||||
|
|
||||||
- Internal: `Fixed foo ([#123](https://github.com/badlogic/pi-mono/issues/123))`
|
- 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))`
|
- External: `Added bar ([#456](https://github.com/badlogic/pi-mono/pull/456) by [@user](https://github.com/user))`
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
---
|
---
|
||||||
description: Analyze GitHub issues (bugs or feature requests)
|
description: Analyze GitHub issues (bugs or feature requests)
|
||||||
---
|
---
|
||||||
|
|
||||||
Analyze GitHub issue(s): $ARGUMENTS
|
Analyze GitHub issue(s): $ARGUMENTS
|
||||||
|
|
||||||
For each issue:
|
For each issue:
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
---
|
---
|
||||||
description: Review PRs from URLs with structured issue and code analysis
|
description: Review PRs from URLs with structured issue and code analysis
|
||||||
---
|
---
|
||||||
|
|
||||||
You are given one or more GitHub PR URLs: $@
|
You are given one or more GitHub PR URLs: $@
|
||||||
|
|
||||||
For each PR URL, do the following in order:
|
For each PR URL, do the following in order:
|
||||||
|
|
||||||
1. Read the PR page in full. Include description, all comments, all commits, and all changed files.
|
1. Read the PR page in full. Include description, all comments, all commits, and all changed files.
|
||||||
2. Identify any linked issues referenced in the PR body, comments, commit messages, or cross links. Read each issue in full, including all comments.
|
2. Identify any linked issues referenced in the PR body, comments, commit messages, or cross links. Read each issue in full, including all comments.
|
||||||
3. Analyze the PR diff. Read all relevant code files in full with no truncation from the current main branch and compare against the diff. Do not fetch PR file blobs unless a file is missing on main or the diff context is insufficient. Include related code paths that are not in the diff but are required to validate behavior.
|
3. Analyze the PR diff. Read all relevant code files in full with no truncation from the current main branch and compare against the diff. Do not fetch PR file blobs unless a file is missing on main or the diff context is insufficient. Include related code paths that are not in the diff but are required to validate behavior.
|
||||||
4. Check if docs/*.md require modification. This is usually the case when existing features have been changed, or new features have been added.
|
4. Check if docs/\*.md require modification. This is usually the case when existing features have been changed, or new features have been added.
|
||||||
5. Provide a structured review with these sections:
|
5. Provide a structured review with these sections:
|
||||||
- Good: solid choices or improvements
|
- Good: solid choices or improvements
|
||||||
- Bad: concrete issues, regressions, missing tests, or risks
|
- Bad: concrete issues, regressions, missing tests, or risks
|
||||||
@@ -18,16 +20,17 @@ For each PR URL, do the following in order:
|
|||||||
Output format per PR:
|
Output format per PR:
|
||||||
PR: <url>
|
PR: <url>
|
||||||
Good:
|
Good:
|
||||||
|
|
||||||
- ...
|
- ...
|
||||||
Bad:
|
Bad:
|
||||||
- ...
|
- ...
|
||||||
Ugly:
|
Ugly:
|
||||||
- ...
|
- ...
|
||||||
Questions or Assumptions:
|
Questions or Assumptions:
|
||||||
- ...
|
- ...
|
||||||
Change summary:
|
Change summary:
|
||||||
- ...
|
- ...
|
||||||
Tests:
|
Tests:
|
||||||
- ...
|
- ...
|
||||||
|
|
||||||
If no issues are found, say so under Bad and Ugly.
|
If no issues are found, say so under Bad and Ugly.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
17
AGENTS.md
17
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)
|
||||||
@@ -49,6 +54,7 @@
|
|||||||
- 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 lint` before commits.
|
||||||
- Add brief code comments for tricky or non-obvious logic.
|
- Add brief code comments for tricky or non-obvious logic.
|
||||||
@@ -57,11 +63,13 @@
|
|||||||
- 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 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!
|
||||||
|
|
||||||
## 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'`.
|
||||||
|
|||||||
78
CHANGELOG.md
78
CHANGELOG.md
@@ -5,6 +5,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
## 2026.1.30
|
## 2026.1.30
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
|
|
||||||
- CLI: add `completion` command (Zsh/Bash/PowerShell/Fish) and auto-setup during postinstall/onboarding.
|
- 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.
|
- CLI: add per-agent `models status` (`--agent` filter). (#4780) Thanks @jlowin.
|
||||||
- Agents: add Kimi K2.5 to the synthetic model catalog. (#4407) Thanks @manikv12.
|
- Agents: add Kimi K2.5 to the synthetic model catalog. (#4407) Thanks @manikv12.
|
||||||
@@ -16,6 +17,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
- Docs: add pi/pi-dev docs and update OpenClaw branding + install links.
|
- Docs: add pi/pi-dev docs and update OpenClaw branding + install links.
|
||||||
|
|
||||||
### Fixes
|
### Fixes
|
||||||
|
|
||||||
- Security: restrict local path extraction in media parser to prevent LFI. (#4880)
|
- Security: restrict local path extraction in media parser to prevent LFI. (#4880)
|
||||||
- Gateway: prevent token defaults from becoming the literal "undefined". (#4873) Thanks @Hisleren.
|
- Gateway: prevent token defaults from becoming the literal "undefined". (#4873) Thanks @Hisleren.
|
||||||
- Control UI: fix assets resolution for npm global installs. (#4909) Thanks @YuriNachos.
|
- Control UI: fix assets resolution for npm global installs. (#4909) Thanks @YuriNachos.
|
||||||
@@ -37,6 +39,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
## 2026.1.29
|
## 2026.1.29
|
||||||
|
|
||||||
### Changes
|
### 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.
|
||||||
@@ -98,10 +101,13 @@ Docs: https://docs.openclaw.ai
|
|||||||
- 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.
|
||||||
|
|
||||||
### 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
|
||||||
|
|
||||||
- Telegram: avoid silent empty replies by tracking normalization skips before fallback. (#3796)
|
- Telegram: avoid silent empty replies by tracking normalization skips before fallback. (#3796)
|
||||||
- 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.
|
||||||
@@ -154,6 +160,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
## 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.
|
||||||
@@ -162,16 +169,19 @@ Docs: https://docs.openclaw.ai
|
|||||||
## 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
|
||||||
@@ -179,6 +189,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
- 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
|
||||||
@@ -197,6 +208,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
- 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.
|
||||||
@@ -240,11 +252,13 @@ Docs: https://docs.openclaw.ai
|
|||||||
## 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
|
||||||
@@ -252,6 +266,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
- 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
|
||||||
@@ -266,6 +281,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
- 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)
|
||||||
@@ -304,6 +320,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
## 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.
|
||||||
@@ -311,6 +328,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
- 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.
|
||||||
@@ -341,12 +359,14 @@ Docs: https://docs.openclaw.ai
|
|||||||
## 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.
|
||||||
@@ -369,10 +389,12 @@ Docs: https://docs.openclaw.ai
|
|||||||
- 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.
|
||||||
@@ -395,6 +417,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
## 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
|
||||||
@@ -473,9 +496,11 @@ Docs: https://docs.openclaw.ai
|
|||||||
- 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)
|
||||||
@@ -575,20 +600,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"`.
|
||||||
@@ -597,6 +625,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).
|
||||||
@@ -632,6 +661,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.
|
||||||
@@ -688,17 +718,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).
|
||||||
@@ -738,6 +771,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.
|
||||||
@@ -774,12 +808,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.
|
||||||
@@ -796,6 +832,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.
|
||||||
@@ -817,6 +854,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)
|
||||||
@@ -824,14 +862,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.
|
||||||
@@ -844,6 +884,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.
|
||||||
@@ -863,12 +904,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.
|
||||||
@@ -876,11 +919,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).
|
||||||
@@ -889,6 +934,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.
|
||||||
@@ -902,9 +948,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.
|
||||||
@@ -929,6 +977,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.
|
||||||
@@ -936,12 +985,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.
|
||||||
@@ -977,6 +1028,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.
|
||||||
@@ -985,6 +1037,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.
|
||||||
@@ -1024,12 +1077,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.
|
||||||
@@ -1043,6 +1098,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.
|
||||||
@@ -1114,10 +1170,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.
|
||||||
@@ -1126,10 +1182,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.
|
||||||
@@ -1151,6 +1209,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.
|
||||||
@@ -1168,7 +1227,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.
|
||||||
|
|
||||||
@@ -1177,6 +1237,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.
|
||||||
@@ -1185,6 +1246,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"`).
|
||||||
@@ -1199,6 +1261,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.
|
||||||
@@ -1210,13 +1273,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`).
|
||||||
@@ -1224,6 +1289,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)
|
||||||
@@ -19,11 +20,13 @@ Welcome to the lobster tank! 🦞
|
|||||||
- GitHub: [@joshp123](https://github.com/joshp123) · X: [@jjpcodes](https://x.com/jjpcodes)
|
- GitHub: [@joshp123](https://github.com/joshp123) · X: [@jjpcodes](https://x.com/jjpcodes)
|
||||||
|
|
||||||
## 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 tests: `pnpm tsgo && pnpm format && pnpm lint && pnpm build && pnpm test`
|
- Run tests: `pnpm tsgo && pnpm format && pnpm lint && pnpm build && pnpm test`
|
||||||
- Keep PRs focused (one thing per PR)
|
- Keep PRs focused (one thing per PR)
|
||||||
@@ -34,6 +37,7 @@ Welcome to the lobster tank! 🦞
|
|||||||
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 +48,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.
|
||||||
|
|||||||
36
README.md
36
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",
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -337,9 +346,9 @@ Details: [Security guide](https://docs.openclaw.ai/gateway/security) · [Docker
|
|||||||
{
|
{
|
||||||
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)
|
||||||
|
|||||||
@@ -12,19 +12,19 @@ services:
|
|||||||
- ${OPENCLAW_CONFIG_DIR}:/home/node/.openclaw
|
- ${OPENCLAW_CONFIG_DIR}:/home/node/.openclaw
|
||||||
- ${OPENCLAW_WORKSPACE_DIR}:/home/node/.openclaw/workspace
|
- ${OPENCLAW_WORKSPACE_DIR}:/home/node/.openclaw/workspace
|
||||||
ports:
|
ports:
|
||||||
- '${OPENCLAW_GATEWAY_PORT:-18789}:18789'
|
- "${OPENCLAW_GATEWAY_PORT:-18789}:18789"
|
||||||
- '${OPENCLAW_BRIDGE_PORT:-18790}:18790'
|
- "${OPENCLAW_BRIDGE_PORT:-18790}:18790"
|
||||||
init: true
|
init: true
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
command:
|
command:
|
||||||
[
|
[
|
||||||
'node',
|
"node",
|
||||||
'dist/index.js',
|
"dist/index.js",
|
||||||
"gateway",
|
"gateway",
|
||||||
'--bind',
|
"--bind",
|
||||||
'${OPENCLAW_GATEWAY_BIND:-lan}',
|
"${OPENCLAW_GATEWAY_BIND:-lan}",
|
||||||
'--port',
|
"--port",
|
||||||
'${OPENCLAW_GATEWAY_PORT:-18789}',
|
"${OPENCLAW_GATEWAY_PORT:-18789}",
|
||||||
]
|
]
|
||||||
|
|
||||||
openclaw-cli:
|
openclaw-cli:
|
||||||
@@ -42,4 +42,4 @@ services:
|
|||||||
stdin_open: true
|
stdin_open: true
|
||||||
tty: true
|
tty: true
|
||||||
init: true
|
init: true
|
||||||
entrypoint: ['node', 'dist/index.js']
|
entrypoint: ["node", "dist/index.js"]
|
||||||
|
|||||||
13
docs.acp.md
13
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
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ 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
|
||||||
---
|
---
|
||||||
|
|
||||||
# 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 +17,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)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ read_when:
|
|||||||
- 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
|
||||||
---
|
---
|
||||||
|
|
||||||
# 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 +13,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:
|
||||||
@@ -24,18 +26,19 @@ cron is the mechanism.
|
|||||||
- 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”.
|
||||||
|
|
||||||
## 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 +47,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 +61,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 +74,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 +85,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 +100,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 +117,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 +134,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 +150,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 +170,11 @@ 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`
|
||||||
|
|
||||||
## 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 +186,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 +211,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 +222,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 +236,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,7 +250,8 @@ 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" \
|
||||||
--cron "0 6 * * 1" \
|
--cron "0 6 * * 1" \
|
||||||
@@ -242,15 +272,17 @@ openclaw cron add --name "Ops sweep" --cron "0 6 * * *" --session isolated --mes
|
|||||||
# Switch or clear the agent on an existing job
|
# Switch or clear the agent on an existing job
|
||||||
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 +291,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.
|
||||||
|
|||||||
@@ -5,20 +5,21 @@ read_when:
|
|||||||
- Setting up background monitoring or notifications
|
- Setting up background monitoring or notifications
|
||||||
- Optimizing token usage for periodic checks
|
- Optimizing token usage for periodic checks
|
||||||
---
|
---
|
||||||
|
|
||||||
# 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 +60,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 +158,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 +169,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 +181,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 +194,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 +213,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 +241,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 +260,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.
|
||||||
|
|||||||
@@ -26,8 +26,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 +47,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 +72,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 +99,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 +118,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 +133,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 +142,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 +191,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.
|
||||||
|
|||||||
@@ -4,10 +4,11 @@ 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
|
||||||
---
|
---
|
||||||
|
|
||||||
# Polls
|
# Polls
|
||||||
|
|
||||||
|
|
||||||
## Supported channels
|
## Supported channels
|
||||||
|
|
||||||
- WhatsApp (web channel)
|
- WhatsApp (web channel)
|
||||||
- Discord
|
- Discord
|
||||||
- MS Teams (Adaptive Cards)
|
- MS Teams (Adaptive Cards)
|
||||||
@@ -33,6 +34,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 +44,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 +54,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.
|
||||||
|
|||||||
@@ -16,18 +16,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 +39,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 +48,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 +83,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 +95,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 +106,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
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ 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
|
||||||
---
|
---
|
||||||
|
|
||||||
# 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 +35,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 +52,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 +65,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 +83,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 +114,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)
|
||||||
|
|||||||
@@ -11,9 +11,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 +25,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,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,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 +37,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 +47,7 @@ Agents:
|
|||||||
```
|
```
|
||||||
|
|
||||||
### 3. Quality Assurance Workflows
|
### 3. Quality Assurance Workflows
|
||||||
|
|
||||||
```
|
```
|
||||||
Group: "Customer Support"
|
Group: "Customer Support"
|
||||||
Agents:
|
Agents:
|
||||||
@@ -52,6 +56,7 @@ Agents:
|
|||||||
```
|
```
|
||||||
|
|
||||||
### 4. Task Automation
|
### 4. Task Automation
|
||||||
|
|
||||||
```
|
```
|
||||||
Group: "Project Management"
|
Group: "Project Management"
|
||||||
Agents:
|
Agents:
|
||||||
@@ -65,6 +70,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 +89,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 +102,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 +162,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 +176,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 +187,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 +196,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 +243,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 +255,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 +274,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 +287,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 +308,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 +328,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 +350,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 +373,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 +415,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 +428,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)
|
||||||
|
|||||||
@@ -5,11 +5,13 @@ read_when:
|
|||||||
- Troubleshooting webhook pairing
|
- Troubleshooting webhook pairing
|
||||||
- Configuring iMessage on macOS
|
- Configuring iMessage on macOS
|
||||||
---
|
---
|
||||||
|
|
||||||
# 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 +22,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 +33,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 +58,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 +75,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 +96,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 +120,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 +135,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 +168,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 (default behavior)
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## 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.
|
||||||
@@ -204,11 +229,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 +244,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.
|
||||||
|
|||||||
@@ -3,41 +3,45 @@ summary: "Discord bot support status, capabilities, and configuration"
|
|||||||
read_when:
|
read_when:
|
||||||
- Working on Discord channel features
|
- Working on Discord channel features
|
||||||
---
|
---
|
||||||
# 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 +68,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 +84,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 +125,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 +137,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 +149,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 +168,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 +199,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**:
|
||||||
@@ -204,6 +223,7 @@ Notes:
|
|||||||
- **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"`).
|
||||||
|
|
||||||
## 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 +233,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 +248,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 +271,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 +279,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 +295,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.",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -327,6 +348,7 @@ ack reaction after the bot replies.
|
|||||||
- `moderation` (timeout/kick/ban, default `false`)
|
- `moderation` (timeout/kick/ban, default `false`)
|
||||||
|
|
||||||
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.
|
||||||
@@ -334,40 +356,45 @@ Reaction notifications use `guilds.<id>.reactionNotifications`:
|
|||||||
|
|
||||||
### 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 +406,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 +429,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.
|
||||||
|
|||||||
@@ -3,26 +3,28 @@ 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
|
||||||
---
|
---
|
||||||
|
|
||||||
# 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 +33,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 +87,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 +116,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 +148,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 +168,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 +176,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 +198,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 +229,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)
|
||||||
|
|||||||
@@ -3,15 +3,17 @@ 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
|
||||||
---
|
---
|
||||||
|
|
||||||
# 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`.
|
||||||
@@ -22,6 +24,7 @@ read_when:
|
|||||||
- **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).
|
||||||
|
|||||||
@@ -4,73 +4,82 @@ read_when:
|
|||||||
- Setting up iMessage support
|
- Setting up iMessage support
|
||||||
- Debugging iMessage send/receive
|
- Debugging iMessage send/receive
|
||||||
---
|
---
|
||||||
# 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 +91,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 +102,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 +129,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 +158,7 @@ Architecture:
|
|||||||
```
|
```
|
||||||
|
|
||||||
Concrete config example (Tailscale hostname):
|
Concrete config example (Tailscale hostname):
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
channels: {
|
channels: {
|
||||||
@@ -152,19 +167,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 +189,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 +200,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 +229,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 +289,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`.
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ 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
|
||||||
---
|
---
|
||||||
|
|
||||||
# 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.
|
||||||
|
|||||||
@@ -32,12 +32,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 +58,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 +76,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 +92,11 @@ Multiple accounts:
|
|||||||
marketing: {
|
marketing: {
|
||||||
channelAccessToken: "...",
|
channelAccessToken: "...",
|
||||||
channelSecret: "...",
|
channelSecret: "...",
|
||||||
webhookPath: "/line/marketing"
|
webhookPath: "/line/marketing",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -148,11 +148,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 +162,10 @@ messages.
|
|||||||
confirmLabel: "Yes",
|
confirmLabel: "Yes",
|
||||||
confirmData: "yes",
|
confirmData: "yes",
|
||||||
cancelLabel: "No",
|
cancelLabel: "No",
|
||||||
cancelData: "no"
|
cancelData: "no",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -8,15 +8,18 @@ read_when:
|
|||||||
# 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 +30,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 +49,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.
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ summary: "Matrix support status, capabilities, and configuration"
|
|||||||
read_when:
|
read_when:
|
||||||
- Working on Matrix channel features
|
- Working on Matrix channel features
|
||||||
---
|
---
|
||||||
|
|
||||||
# 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 +37,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 +64,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 +85,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 +101,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" },
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -159,11 +161,11 @@ 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"],
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -187,17 +189,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)
|
||||||
|
|
||||||
|
|||||||
@@ -12,14 +12,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 +33,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 +48,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 +64,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 +72,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 +98,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 +114,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 +123,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.
|
||||||
|
|||||||
@@ -3,16 +3,17 @@ 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
|
||||||
---
|
---
|
||||||
|
|
||||||
# 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 +21,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 +38,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 +55,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 +118,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 +127,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 +154,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 +193,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 +201,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 +224,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 +256,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 +282,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 +302,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 +356,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 +379,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 +425,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 +435,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.
|
||||||
@@ -430,6 +468,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 +479,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,6 +511,7 @@ 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.
|
||||||
|
|
||||||
@@ -481,11 +522,11 @@ By default, OpenClaw only downloads media from Microsoft/Teams hostnames. Overri
|
|||||||
|
|
||||||
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 +541,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 +560,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 +598,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 +613,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 +632,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 +657,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 +672,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 +692,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 +700,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 +708,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 +717,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 +755,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)
|
||||||
|
|||||||
@@ -3,19 +3,23 @@ summary: "Nextcloud Talk support status, capabilities, and configuration"
|
|||||||
read_when:
|
read_when:
|
||||||
- Working on Nextcloud Talk channel features
|
- Working on Nextcloud Talk channel features
|
||||||
---
|
---
|
||||||
|
|
||||||
# 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 +30,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 +51,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 +73,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.
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ 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
|
||||||
---
|
---
|
||||||
|
|
||||||
# Nostr
|
# Nostr
|
||||||
|
|
||||||
**Status:** Optional plugin (disabled by default).
|
**Status:** Optional plugin (disabled by default).
|
||||||
@@ -40,14 +41,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 +60,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 +150,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 +165,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 +194,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
|
||||||
|
|
||||||
|
|||||||
@@ -4,19 +4,21 @@ read_when:
|
|||||||
- Setting up Signal support
|
- Setting up Signal support
|
||||||
- Debugging Signal send/receive
|
- Debugging Signal send/receive
|
||||||
---
|
---
|
||||||
# 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 +27,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 +74,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 +91,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 +112,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 +132,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 +153,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 +161,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 +195,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`.
|
||||||
|
|||||||
@@ -8,38 +8,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 +51,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 +63,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 +81,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 +90,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 +120,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 +151,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 +161,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 +256,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 +285,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 +297,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 +307,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 +358,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 +367,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 +474,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 +506,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 +537,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`).
|
||||||
|
|||||||
@@ -3,49 +3,56 @@ summary: "Telegram bot support status, capabilities, and configuration"
|
|||||||
read_when:
|
read_when:
|
||||||
- Working on Telegram features or webhooks
|
- Working on Telegram features or webhooks
|
||||||
---
|
---
|
||||||
# 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** and 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**.
|
||||||
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 +62,9 @@ Example:
|
|||||||
enabled: true,
|
enabled: true,
|
||||||
botToken: "123:abc",
|
botToken: "123:abc",
|
||||||
dmPolicy: "pairing",
|
dmPolicy: "pairing",
|
||||||
groups: { "*": { requireMention: true } }
|
groups: { "*": { requireMention: true } },
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -66,19 +73,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 +96,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`.
|
||||||
@@ -98,12 +110,14 @@ group messages, so use admin if you need full visibility.
|
|||||||
- 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.
|
||||||
|
|
||||||
## 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 +127,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 +142,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 +168,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 +179,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 +224,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 +259,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 +304,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 +329,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 +342,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 +365,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` (optionally `channels.telegram.webhookSecret` + `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 +413,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 +434,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 +455,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 +468,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 +484,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 +495,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 +514,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 +544,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 +580,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 +611,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 +626,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 +660,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.
|
||||||
@@ -669,6 +730,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`.
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ summary: "Tlon/Urbit support status, capabilities, and configuration"
|
|||||||
read_when:
|
read_when:
|
||||||
- Working on Tlon/Urbit channel features
|
- Working on Tlon/Urbit channel features
|
||||||
---
|
---
|
||||||
|
|
||||||
# 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 +33,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 +48,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 +62,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 +74,9 @@ Disable auto-discovery:
|
|||||||
{
|
{
|
||||||
channels: {
|
channels: {
|
||||||
tlon: {
|
tlon: {
|
||||||
autoDiscoverChannels: false
|
autoDiscoverChannels: false,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -90,9 +88,9 @@ DM allowlist (empty = allow all):
|
|||||||
{
|
{
|
||||||
channels: {
|
channels: {
|
||||||
tlon: {
|
tlon: {
|
||||||
dmAllowlist: ["~zod", "~nec"]
|
dmAllowlist: ["~zod", "~nec"],
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -107,15 +105,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",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ 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)
|
||||||
---
|
---
|
||||||
|
|
||||||
# Channel troubleshooting
|
# Channel troubleshooting
|
||||||
|
|
||||||
Start with:
|
Start with:
|
||||||
@@ -16,10 +17,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).
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ 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
|
||||||
---
|
---
|
||||||
|
|
||||||
# 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 +28,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 +49,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 +71,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 +81,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 +96,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,10 +110,10 @@ 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
|
allowedRoles: ["moderator"], // Or restrict to roles
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -130,9 +134,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 +157,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 +183,11 @@ Example (one bot account in two channels):
|
|||||||
twitch: {
|
twitch: {
|
||||||
accounts: {
|
accounts: {
|
||||||
default: {
|
default: {
|
||||||
allowedRoles: ["moderator", "vip"]
|
allowedRoles: ["moderator", "vip"],
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -195,11 +199,11 @@ Example (one bot account in two channels):
|
|||||||
twitch: {
|
twitch: {
|
||||||
accounts: {
|
accounts: {
|
||||||
default: {
|
default: {
|
||||||
allowFrom: ["123456789", "987654321"]
|
allowFrom: ["123456789", "987654321"],
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -214,11 +218,11 @@ Users in `allowFrom` bypass role checks:
|
|||||||
accounts: {
|
accounts: {
|
||||||
default: {
|
default: {
|
||||||
allowFrom: ["123456789"],
|
allowFrom: ["123456789"],
|
||||||
allowedRoles: ["moderator"]
|
allowedRoles: ["moderator"],
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -232,11 +236,11 @@ By default, `requireMention` is `true`. To disable and respond to all messages:
|
|||||||
twitch: {
|
twitch: {
|
||||||
accounts: {
|
accounts: {
|
||||||
default: {
|
default: {
|
||||||
requireMention: false
|
requireMention: false,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -258,6 +262,7 @@ openclaw channels status --probe
|
|||||||
### 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 +270,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 +299,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 +334,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",
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -3,45 +3,51 @@ summary: "WhatsApp (web channel) integration: login, inbox, replies, media, and
|
|||||||
read_when:
|
read_when:
|
||||||
- Working on WhatsApp/web channel behavior or inbox routing
|
- Working on WhatsApp/web channel behavior or inbox routing
|
||||||
---
|
---
|
||||||
# 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 +57,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 +80,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 +101,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 +112,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 +120,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 +131,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 +142,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 +189,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 +201,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 +217,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 +226,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 +234,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 +243,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 +257,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 +266,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 +284,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 +292,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 +319,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 +341,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 +380,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 +388,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.)
|
||||||
|
|||||||
@@ -3,43 +3,50 @@ summary: "Zalo bot support status, capabilities, and configuration"
|
|||||||
read_when:
|
read_when:
|
||||||
- Working on Zalo features or webhooks
|
- Working on Zalo features or webhooks
|
||||||
---
|
---
|
||||||
|
|
||||||
# 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 +55,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 +70,9 @@ Example:
|
|||||||
zalo: {
|
zalo: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
botToken: "12345689:abc-xyz",
|
botToken: "12345689:abc-xyz",
|
||||||
dmPolicy: "pairing"
|
dmPolicy: "pairing",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -71,15 +80,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 +98,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 +107,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 +118,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 +175,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.
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ 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
|
||||||
---
|
---
|
||||||
|
|
||||||
# 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 +12,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 +69,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 +94,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 +102,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 +120,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`
|
||||||
|
|||||||
@@ -110,9 +110,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": {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,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
|
||||||
|
|||||||
@@ -9,6 +9,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 +26,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 +35,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 +65,10 @@ Config sample:
|
|||||||
name: "OpenClaw",
|
name: "OpenClaw",
|
||||||
theme: "space lobster",
|
theme: "space lobster",
|
||||||
emoji: "🦞",
|
emoji: "🦞",
|
||||||
avatar: "avatars/openclaw.png"
|
avatar: "avatars/openclaw.png",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -11,6 +11,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)
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,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 +35,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.
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,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 +57,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 +73,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.
|
||||||
|
|||||||
@@ -15,10 +15,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.
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,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.
|
||||||
|
|||||||
@@ -13,4 +13,3 @@ Open the Control UI using your current auth.
|
|||||||
openclaw dashboard
|
openclaw dashboard
|
||||||
openclaw dashboard --no-open
|
openclaw dashboard --no-open
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -10,11 +10,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.
|
||||||
|
|||||||
@@ -10,6 +10,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)
|
||||||
|
|
||||||
|
|||||||
@@ -12,4 +12,3 @@ Search the live docs index.
|
|||||||
openclaw docs browser extension
|
openclaw docs browser extension
|
||||||
openclaw docs sandbox allowHostControl
|
openclaw docs sandbox allowHostControl
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,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 +23,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.
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,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 +33,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 +64,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 +93,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 +104,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 +126,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 +156,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 +170,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 +186,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).
|
||||||
|
|
||||||
|
|||||||
@@ -15,5 +15,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.
|
||||||
|
|||||||
@@ -10,6 +10,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 +23,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 +65,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 +105,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 +130,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 +146,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 +164,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 +180,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 +192,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 +229,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
|
||||||
|
|
||||||
|
|||||||
@@ -269,6 +269,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 +277,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 +292,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 +337,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 +365,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 +380,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 +410,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 +420,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 +438,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 +484,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 +509,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 +537,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 +566,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 +610,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 +640,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 +670,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 +683,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 +693,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 +712,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 +726,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 +747,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 +766,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 +783,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 +834,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 +852,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 +907,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 +920,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 +937,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 +954,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 +962,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 +981,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 +1006,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>`
|
||||||
|
|||||||
@@ -10,6 +10,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 +21,3 @@ openclaw logs --follow
|
|||||||
openclaw logs --json
|
openclaw logs --json
|
||||||
openclaw logs --limit 500
|
openclaw logs --limit 500
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -11,8 +11,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 +37,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).
|
||||||
|
|||||||
@@ -17,11 +17,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 +35,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 +183,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 +200,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 +216,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 +231,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"}] ]'
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ read_when:
|
|||||||
Model discovery, scanning, and configuration (default model, fallbacks, auth profiles).
|
Model discovery, scanning, and configuration (default model, fallbacks, auth profiles).
|
||||||
|
|
||||||
Related:
|
Related:
|
||||||
|
|
||||||
- Providers + models: [Models](/providers/models)
|
- Providers + models: [Models](/providers/models)
|
||||||
- Provider auth setup: [Getting started](/start/getting-started)
|
- Provider auth setup: [Getting started](/start/getting-started)
|
||||||
|
|
||||||
@@ -32,12 +33,15 @@ the command uses `OPENCLAW_AGENT_DIR`/`PI_CODING_AGENT_DIR` if set, otherwise th
|
|||||||
configured default agent.
|
configured default agent.
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
- `models set <model-or-alias>` accepts `provider/model` or an alias.
|
- `models set <model-or-alias>` accepts `provider/model` or an alias.
|
||||||
- Model refs are parsed by splitting on the **first** `/`. If the model ID includes `/` (OpenRouter-style), include the provider prefix (example: `openrouter/moonshotai/kimi-k2`).
|
- Model refs are parsed by splitting on the **first** `/`. If the model ID includes `/` (OpenRouter-style), include the provider prefix (example: `openrouter/moonshotai/kimi-k2`).
|
||||||
- If you omit the provider, OpenClaw treats the input as an alias or a model for the **default provider** (only works when there is no `/` in the model ID).
|
- If you omit the provider, OpenClaw treats the input as an alias or a model for the **default provider** (only works when there is no `/` in the model ID).
|
||||||
|
|
||||||
### `models status`
|
### `models status`
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|
||||||
- `--json`
|
- `--json`
|
||||||
- `--plain`
|
- `--plain`
|
||||||
- `--check` (exit 1=expired/missing, 2=expiring)
|
- `--check` (exit 1=expired/missing, 2=expiring)
|
||||||
@@ -64,9 +68,11 @@ openclaw models auth login --provider <id>
|
|||||||
openclaw models auth setup-token
|
openclaw models auth setup-token
|
||||||
openclaw models auth paste-token
|
openclaw models auth paste-token
|
||||||
```
|
```
|
||||||
|
|
||||||
`models auth login` runs a provider plugin’s auth flow (OAuth/API key). Use
|
`models auth login` runs a provider plugin’s auth flow (OAuth/API key). Use
|
||||||
`openclaw plugins list` to see which providers are installed.
|
`openclaw plugins list` to see which providers are installed.
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
- `setup-token` prompts for a setup-token value (generate it with `claude setup-token` on any machine).
|
- `setup-token` prompts for a setup-token value (generate it with `claude setup-token` on any machine).
|
||||||
- `paste-token` accepts a token string generated elsewhere or from automation.
|
- `paste-token` accepts a token string generated elsewhere or from automation.
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ Use a node host when you want agents to **run commands on other machines** in yo
|
|||||||
network without installing a full macOS companion app there.
|
network without installing a full macOS companion app there.
|
||||||
|
|
||||||
Common use cases:
|
Common use cases:
|
||||||
|
|
||||||
- Run commands on remote Linux/Windows boxes (build servers, lab machines, NAS).
|
- Run commands on remote Linux/Windows boxes (build servers, lab machines, NAS).
|
||||||
- Keep exec **sandboxed** on the gateway, but delegate approved runs to other hosts.
|
- Keep exec **sandboxed** on the gateway, but delegate approved runs to other hosts.
|
||||||
- Provide a lightweight, headless execution target for automation or CI nodes.
|
- Provide a lightweight, headless execution target for automation or CI nodes.
|
||||||
@@ -35,9 +36,9 @@ Disable it on the node if needed:
|
|||||||
{
|
{
|
||||||
nodeHost: {
|
nodeHost: {
|
||||||
browserProxy: {
|
browserProxy: {
|
||||||
enabled: false
|
enabled: false,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -48,6 +49,7 @@ openclaw node run --host <gateway-host> --port 18789
|
|||||||
```
|
```
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|
||||||
- `--host <host>`: Gateway WebSocket host (default: `127.0.0.1`)
|
- `--host <host>`: Gateway WebSocket host (default: `127.0.0.1`)
|
||||||
- `--port <port>`: Gateway WebSocket port (default: `18789`)
|
- `--port <port>`: Gateway WebSocket port (default: `18789`)
|
||||||
- `--tls`: Use TLS for the gateway connection
|
- `--tls`: Use TLS for the gateway connection
|
||||||
@@ -64,6 +66,7 @@ openclaw node install --host <gateway-host> --port 18789
|
|||||||
```
|
```
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|
||||||
- `--host <host>`: Gateway WebSocket host (default: `127.0.0.1`)
|
- `--host <host>`: Gateway WebSocket host (default: `127.0.0.1`)
|
||||||
- `--port <port>`: Gateway WebSocket port (default: `18789`)
|
- `--port <port>`: Gateway WebSocket port (default: `18789`)
|
||||||
- `--tls`: Use TLS for the gateway connection
|
- `--tls`: Use TLS for the gateway connection
|
||||||
|
|||||||
@@ -10,11 +10,13 @@ read_when:
|
|||||||
Manage paired nodes (devices) and invoke node capabilities.
|
Manage paired nodes (devices) and invoke node capabilities.
|
||||||
|
|
||||||
Related:
|
Related:
|
||||||
|
|
||||||
- Nodes overview: [Nodes](/nodes)
|
- Nodes overview: [Nodes](/nodes)
|
||||||
- Camera: [Camera nodes](/nodes/camera)
|
- Camera: [Camera nodes](/nodes/camera)
|
||||||
- Images: [Image nodes](/nodes/images)
|
- Images: [Image nodes](/nodes/images)
|
||||||
|
|
||||||
Common options:
|
Common options:
|
||||||
|
|
||||||
- `--url`, `--token`, `--timeout`, `--json`
|
- `--url`, `--token`, `--timeout`, `--json`
|
||||||
|
|
||||||
## Common commands
|
## Common commands
|
||||||
@@ -44,6 +46,7 @@ openclaw nodes run --agent main --node <id|name|ip> --raw "git status"
|
|||||||
```
|
```
|
||||||
|
|
||||||
Invoke flags:
|
Invoke flags:
|
||||||
|
|
||||||
- `--params <json>`: JSON object string (default `{}`).
|
- `--params <json>`: JSON object string (default `{}`).
|
||||||
- `--invoke-timeout <ms>`: node invoke timeout (default `15000`).
|
- `--invoke-timeout <ms>`: node invoke timeout (default `15000`).
|
||||||
- `--idempotency-key <key>`: optional idempotency key.
|
- `--idempotency-key <key>`: optional idempotency key.
|
||||||
@@ -58,6 +61,7 @@ Invoke flags:
|
|||||||
- Requires a node that advertises `system.run` (macOS companion app or headless node host).
|
- Requires a node that advertises `system.run` (macOS companion app or headless node host).
|
||||||
|
|
||||||
Flags:
|
Flags:
|
||||||
|
|
||||||
- `--cwd <path>`: working directory.
|
- `--cwd <path>`: working directory.
|
||||||
- `--env <key=val>`: env override (repeatable).
|
- `--env <key=val>`: env override (repeatable).
|
||||||
- `--command-timeout <ms>`: command timeout.
|
- `--command-timeout <ms>`: command timeout.
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ read_when:
|
|||||||
Interactive onboarding wizard (local or remote Gateway setup).
|
Interactive onboarding wizard (local or remote Gateway setup).
|
||||||
|
|
||||||
Related:
|
Related:
|
||||||
|
|
||||||
- Wizard guide: [Onboarding](/start/onboarding)
|
- Wizard guide: [Onboarding](/start/onboarding)
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
@@ -21,6 +22,7 @@ openclaw onboard --mode remote --remote-url ws://gateway-host:18789
|
|||||||
```
|
```
|
||||||
|
|
||||||
Flow notes:
|
Flow notes:
|
||||||
|
|
||||||
- `quickstart`: minimal prompts, auto-generates a gateway token.
|
- `quickstart`: minimal prompts, auto-generates a gateway token.
|
||||||
- `manual`: full prompts for port/bind/auth (alias of `advanced`).
|
- `manual`: full prompts for port/bind/auth (alias of `advanced`).
|
||||||
- Fastest first chat: `openclaw dashboard` (Control UI, no channel setup).
|
- Fastest first chat: `openclaw dashboard` (Control UI, no channel setup).
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ read_when:
|
|||||||
Approve or inspect DM pairing requests (for channels that support pairing).
|
Approve or inspect DM pairing requests (for channels that support pairing).
|
||||||
|
|
||||||
Related:
|
Related:
|
||||||
|
|
||||||
- Pairing flow: [Pairing](/start/pairing)
|
- Pairing flow: [Pairing](/start/pairing)
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
@@ -17,4 +18,3 @@ Related:
|
|||||||
openclaw pairing list whatsapp
|
openclaw pairing list whatsapp
|
||||||
openclaw pairing approve whatsapp <code> --notify
|
openclaw pairing approve whatsapp <code> --notify
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ read_when:
|
|||||||
Manage Gateway plugins/extensions (loaded in-process).
|
Manage Gateway plugins/extensions (loaded in-process).
|
||||||
|
|
||||||
Related:
|
Related:
|
||||||
|
|
||||||
- Plugin system: [Plugins](/plugin)
|
- Plugin system: [Plugins](/plugin)
|
||||||
- Plugin manifest + schema: [Plugin manifest](/plugins/manifest)
|
- Plugin manifest + schema: [Plugin manifest](/plugins/manifest)
|
||||||
- Security hardening: [Security](/gateway/security)
|
- Security hardening: [Security](/gateway/security)
|
||||||
|
|||||||
@@ -14,4 +14,3 @@ openclaw reset
|
|||||||
openclaw reset --dry-run
|
openclaw reset --dry-run
|
||||||
openclaw reset --scope config+creds+sessions --yes --non-interactive
|
openclaw reset --scope config+creds+sessions --yes --non-interactive
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ openclaw sandbox list --json # JSON output
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Output includes:**
|
**Output includes:**
|
||||||
|
|
||||||
- Container name and status (running/stopped)
|
- Container name and status (running/stopped)
|
||||||
- Docker image and whether it matches config
|
- Docker image and whether it matches config
|
||||||
- Age (time since creation)
|
- Age (time since creation)
|
||||||
@@ -56,6 +57,7 @@ openclaw sandbox recreate --all --force # Skip confirmation
|
|||||||
```
|
```
|
||||||
|
|
||||||
**Options:**
|
**Options:**
|
||||||
|
|
||||||
- `--all`: Recreate all sandbox containers
|
- `--all`: Recreate all sandbox containers
|
||||||
- `--session <key>`: Recreate container for specific session
|
- `--session <key>`: Recreate container for specific session
|
||||||
- `--agent <id>`: Recreate containers for specific agent
|
- `--agent <id>`: Recreate containers for specific agent
|
||||||
@@ -97,7 +99,6 @@ openclaw sandbox recreate --all
|
|||||||
openclaw sandbox recreate --agent family
|
openclaw sandbox recreate --agent family
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
### For a specific agent only
|
### For a specific agent only
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -108,6 +109,7 @@ openclaw sandbox recreate --agent alfred
|
|||||||
## Why is this needed?
|
## Why is this needed?
|
||||||
|
|
||||||
**Problem:** When you update sandbox Docker images or configuration:
|
**Problem:** When you update sandbox Docker images or configuration:
|
||||||
|
|
||||||
- Existing containers continue running with old settings
|
- Existing containers continue running with old settings
|
||||||
- Containers are only pruned after 24h of inactivity
|
- Containers are only pruned after 24h of inactivity
|
||||||
- Regularly-used agents keep old containers running indefinitely
|
- Regularly-used agents keep old containers running indefinitely
|
||||||
@@ -126,20 +128,20 @@ Sandbox settings live in `~/.openclaw/openclaw.json` under `agents.defaults.sand
|
|||||||
"agents": {
|
"agents": {
|
||||||
"defaults": {
|
"defaults": {
|
||||||
"sandbox": {
|
"sandbox": {
|
||||||
"mode": "all", // off, non-main, all
|
"mode": "all", // off, non-main, all
|
||||||
"scope": "agent", // session, agent, shared
|
"scope": "agent", // session, agent, shared
|
||||||
"docker": {
|
"docker": {
|
||||||
"image": "openclaw-sandbox:bookworm-slim",
|
"image": "openclaw-sandbox:bookworm-slim",
|
||||||
"containerPrefix": "openclaw-sbx-"
|
"containerPrefix": "openclaw-sbx-",
|
||||||
// ... more Docker options
|
// ... more Docker options
|
||||||
},
|
},
|
||||||
"prune": {
|
"prune": {
|
||||||
"idleHours": 24, // Auto-prune after 24h idle
|
"idleHours": 24, // Auto-prune after 24h idle
|
||||||
"maxAgeDays": 7 // Auto-prune after 7 days
|
"maxAgeDays": 7, // Auto-prune after 7 days
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ read_when:
|
|||||||
Security tools (audit + optional fixes).
|
Security tools (audit + optional fixes).
|
||||||
|
|
||||||
Related:
|
Related:
|
||||||
|
|
||||||
- Security guide: [Security](/gateway/security)
|
- Security guide: [Security](/gateway/security)
|
||||||
|
|
||||||
## Audit
|
## Audit
|
||||||
|
|||||||
@@ -13,4 +13,3 @@ openclaw sessions
|
|||||||
openclaw sessions --active 120
|
openclaw sessions --active 120
|
||||||
openclaw sessions --json
|
openclaw sessions --json
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ read_when:
|
|||||||
Initialize `~/.openclaw/openclaw.json` and the agent workspace.
|
Initialize `~/.openclaw/openclaw.json` and the agent workspace.
|
||||||
|
|
||||||
Related:
|
Related:
|
||||||
|
|
||||||
- Getting started: [Getting started](/start/getting-started)
|
- Getting started: [Getting started](/start/getting-started)
|
||||||
- Wizard: [Onboarding](/start/onboarding)
|
- Wizard: [Onboarding](/start/onboarding)
|
||||||
|
|
||||||
@@ -25,4 +26,3 @@ To run the wizard via setup:
|
|||||||
```bash
|
```bash
|
||||||
openclaw setup --wizard
|
openclaw setup --wizard
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ read_when:
|
|||||||
Inspect skills (bundled + workspace + managed overrides) and see what’s eligible vs missing requirements.
|
Inspect skills (bundled + workspace + managed overrides) and see what’s eligible vs missing requirements.
|
||||||
|
|
||||||
Related:
|
Related:
|
||||||
|
|
||||||
- Skills system: [Skills](/tools/skills)
|
- Skills system: [Skills](/tools/skills)
|
||||||
- Skills config: [Skills config](/tools/skills-config)
|
- Skills config: [Skills config](/tools/skills-config)
|
||||||
- ClawHub installs: [ClawHub](/tools/clawhub)
|
- ClawHub installs: [ClawHub](/tools/clawhub)
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ openclaw status --usage
|
|||||||
```
|
```
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
- `--deep` runs live probes (WhatsApp Web + Telegram + Discord + Google Chat + Slack + Signal).
|
- `--deep` runs live probes (WhatsApp Web + Telegram + Discord + Google Chat + Slack + Signal).
|
||||||
- Output includes per-agent session stores when multiple agents are configured.
|
- Output includes per-agent session stores when multiple agents are configured.
|
||||||
- Overview includes Gateway + node host service install/runtime status when available.
|
- Overview includes Gateway + node host service install/runtime status when available.
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ it as a `System:` line in the prompt. Use `--mode now` to trigger the heartbeat
|
|||||||
immediately; `next-heartbeat` waits for the next scheduled tick.
|
immediately; `next-heartbeat` waits for the next scheduled tick.
|
||||||
|
|
||||||
Flags:
|
Flags:
|
||||||
|
|
||||||
- `--text <text>`: required system event text.
|
- `--text <text>`: required system event text.
|
||||||
- `--mode <mode>`: `now` or `next-heartbeat` (default).
|
- `--mode <mode>`: `now` or `next-heartbeat` (default).
|
||||||
- `--json`: machine-readable output.
|
- `--json`: machine-readable output.
|
||||||
@@ -34,11 +35,13 @@ Flags:
|
|||||||
## `system heartbeat last|enable|disable`
|
## `system heartbeat last|enable|disable`
|
||||||
|
|
||||||
Heartbeat controls:
|
Heartbeat controls:
|
||||||
|
|
||||||
- `last`: show the last heartbeat event.
|
- `last`: show the last heartbeat event.
|
||||||
- `enable`: turn heartbeats back on (use this if they were disabled).
|
- `enable`: turn heartbeats back on (use this if they were disabled).
|
||||||
- `disable`: pause heartbeats.
|
- `disable`: pause heartbeats.
|
||||||
|
|
||||||
Flags:
|
Flags:
|
||||||
|
|
||||||
- `--json`: machine-readable output.
|
- `--json`: machine-readable output.
|
||||||
|
|
||||||
## `system presence`
|
## `system presence`
|
||||||
@@ -47,6 +50,7 @@ List the current system presence entries the Gateway knows about (nodes,
|
|||||||
instances, and similar status lines).
|
instances, and similar status lines).
|
||||||
|
|
||||||
Flags:
|
Flags:
|
||||||
|
|
||||||
- `--json`: machine-readable output.
|
- `--json`: machine-readable output.
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ read_when:
|
|||||||
Open the terminal UI connected to the Gateway.
|
Open the terminal UI connected to the Gateway.
|
||||||
|
|
||||||
Related:
|
Related:
|
||||||
|
|
||||||
- TUI guide: [TUI](/tui)
|
- TUI guide: [TUI](/tui)
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
@@ -19,4 +20,3 @@ openclaw tui
|
|||||||
openclaw tui --url ws://127.0.0.1:18789 --token <token>
|
openclaw tui --url ws://127.0.0.1:18789 --token <token>
|
||||||
openclaw tui --session main --deliver
|
openclaw tui --session main --deliver
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -14,4 +14,3 @@ openclaw uninstall
|
|||||||
openclaw uninstall --all --yes
|
openclaw uninstall --all --yes
|
||||||
openclaw uninstall --dry-run
|
openclaw uninstall --dry-run
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ openclaw update status --timeout 10
|
|||||||
```
|
```
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
|
|
||||||
- `--json`: print machine-readable status JSON.
|
- `--json`: print machine-readable status JSON.
|
||||||
- `--timeout <seconds>`: timeout for checks (default is 3s).
|
- `--timeout <seconds>`: timeout for checks (default is 3s).
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ read_when:
|
|||||||
`voicecall` is a plugin-provided command. It only appears if the voice-call plugin is installed and enabled.
|
`voicecall` is a plugin-provided command. It only appears if the voice-call plugin is installed and enabled.
|
||||||
|
|
||||||
Primary doc:
|
Primary doc:
|
||||||
|
|
||||||
- Voice-call plugin: [Voice Call](/plugins/voice-call)
|
- Voice-call plugin: [Voice Call](/plugins/voice-call)
|
||||||
|
|
||||||
## Common commands
|
## Common commands
|
||||||
@@ -30,4 +31,3 @@ openclaw voicecall unexpose
|
|||||||
```
|
```
|
||||||
|
|
||||||
Security note: only expose the webhook endpoint to networks you trust. Prefer Tailscale Serve over Funnel when possible.
|
Security note: only expose the webhook endpoint to networks you trust. Prefer Tailscale Serve over Funnel when possible.
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ read_when:
|
|||||||
Webhook helpers and integrations (Gmail Pub/Sub, webhook helpers).
|
Webhook helpers and integrations (Gmail Pub/Sub, webhook helpers).
|
||||||
|
|
||||||
Related:
|
Related:
|
||||||
|
|
||||||
- Webhooks: [Webhook](/automation/webhook)
|
- Webhooks: [Webhook](/automation/webhook)
|
||||||
- Gmail Pub/Sub: [Gmail Pub/Sub](/automation/gmail-pubsub)
|
- Gmail Pub/Sub: [Gmail Pub/Sub](/automation/gmail-pubsub)
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ summary: "Agent loop lifecycle, streams, and wait semantics"
|
|||||||
read_when:
|
read_when:
|
||||||
- You need an exact walkthrough of the agent loop or lifecycle events
|
- You need an exact walkthrough of the agent loop or lifecycle events
|
||||||
---
|
---
|
||||||
|
|
||||||
# Agent Loop (OpenClaw)
|
# Agent Loop (OpenClaw)
|
||||||
|
|
||||||
An agentic loop is the full “real” run of an agent: intake → context assembly → model inference →
|
An agentic loop is the full “real” run of an agent: intake → context assembly → model inference →
|
||||||
@@ -14,53 +15,61 @@ as the model thinks, calls tools, and streams output. This doc explains how that
|
|||||||
wired end-to-end.
|
wired end-to-end.
|
||||||
|
|
||||||
## Entry points
|
## Entry points
|
||||||
|
|
||||||
- Gateway RPC: `agent` and `agent.wait`.
|
- Gateway RPC: `agent` and `agent.wait`.
|
||||||
- CLI: `agent` command.
|
- CLI: `agent` command.
|
||||||
|
|
||||||
## How it works (high-level)
|
## How it works (high-level)
|
||||||
1) `agent` RPC validates params, resolves session (sessionKey/sessionId), persists session metadata, returns `{ runId, acceptedAt }` immediately.
|
|
||||||
2) `agentCommand` runs the agent:
|
1. `agent` RPC validates params, resolves session (sessionKey/sessionId), persists session metadata, returns `{ runId, acceptedAt }` immediately.
|
||||||
|
2. `agentCommand` runs the agent:
|
||||||
- resolves model + thinking/verbose defaults
|
- resolves model + thinking/verbose defaults
|
||||||
- loads skills snapshot
|
- loads skills snapshot
|
||||||
- calls `runEmbeddedPiAgent` (pi-agent-core runtime)
|
- calls `runEmbeddedPiAgent` (pi-agent-core runtime)
|
||||||
- emits **lifecycle end/error** if the embedded loop does not emit one
|
- emits **lifecycle end/error** if the embedded loop does not emit one
|
||||||
3) `runEmbeddedPiAgent`:
|
3. `runEmbeddedPiAgent`:
|
||||||
- serializes runs via per-session + global queues
|
- serializes runs via per-session + global queues
|
||||||
- resolves model + auth profile and builds the pi session
|
- resolves model + auth profile and builds the pi session
|
||||||
- subscribes to pi events and streams assistant/tool deltas
|
- subscribes to pi events and streams assistant/tool deltas
|
||||||
- enforces timeout -> aborts run if exceeded
|
- enforces timeout -> aborts run if exceeded
|
||||||
- returns payloads + usage metadata
|
- returns payloads + usage metadata
|
||||||
4) `subscribeEmbeddedPiSession` bridges pi-agent-core events to OpenClaw `agent` stream:
|
4. `subscribeEmbeddedPiSession` bridges pi-agent-core events to OpenClaw `agent` stream:
|
||||||
- tool events => `stream: "tool"`
|
- tool events => `stream: "tool"`
|
||||||
- assistant deltas => `stream: "assistant"`
|
- assistant deltas => `stream: "assistant"`
|
||||||
- lifecycle events => `stream: "lifecycle"` (`phase: "start" | "end" | "error"`)
|
- lifecycle events => `stream: "lifecycle"` (`phase: "start" | "end" | "error"`)
|
||||||
5) `agent.wait` uses `waitForAgentJob`:
|
5. `agent.wait` uses `waitForAgentJob`:
|
||||||
- waits for **lifecycle end/error** for `runId`
|
- waits for **lifecycle end/error** for `runId`
|
||||||
- returns `{ status: ok|error|timeout, startedAt, endedAt, error? }`
|
- returns `{ status: ok|error|timeout, startedAt, endedAt, error? }`
|
||||||
|
|
||||||
## Queueing + concurrency
|
## Queueing + concurrency
|
||||||
|
|
||||||
- Runs are serialized per session key (session lane) and optionally through a global lane.
|
- Runs are serialized per session key (session lane) and optionally through a global lane.
|
||||||
- This prevents tool/session races and keeps session history consistent.
|
- This prevents tool/session races and keeps session history consistent.
|
||||||
- Messaging channels can choose queue modes (collect/steer/followup) that feed this lane system.
|
- Messaging channels can choose queue modes (collect/steer/followup) that feed this lane system.
|
||||||
See [Command Queue](/concepts/queue).
|
See [Command Queue](/concepts/queue).
|
||||||
|
|
||||||
## Session + workspace preparation
|
## Session + workspace preparation
|
||||||
|
|
||||||
- Workspace is resolved and created; sandboxed runs may redirect to a sandbox workspace root.
|
- Workspace is resolved and created; sandboxed runs may redirect to a sandbox workspace root.
|
||||||
- Skills are loaded (or reused from a snapshot) and injected into env and prompt.
|
- Skills are loaded (or reused from a snapshot) and injected into env and prompt.
|
||||||
- Bootstrap/context files are resolved and injected into the system prompt report.
|
- Bootstrap/context files are resolved and injected into the system prompt report.
|
||||||
- A session write lock is acquired; `SessionManager` is opened and prepared before streaming.
|
- A session write lock is acquired; `SessionManager` is opened and prepared before streaming.
|
||||||
|
|
||||||
## Prompt assembly + system prompt
|
## Prompt assembly + system prompt
|
||||||
|
|
||||||
- System prompt is built from OpenClaw’s base prompt, skills prompt, bootstrap context, and per-run overrides.
|
- System prompt is built from OpenClaw’s base prompt, skills prompt, bootstrap context, and per-run overrides.
|
||||||
- Model-specific limits and compaction reserve tokens are enforced.
|
- Model-specific limits and compaction reserve tokens are enforced.
|
||||||
- See [System prompt](/concepts/system-prompt) for what the model sees.
|
- See [System prompt](/concepts/system-prompt) for what the model sees.
|
||||||
|
|
||||||
## Hook points (where you can intercept)
|
## Hook points (where you can intercept)
|
||||||
|
|
||||||
OpenClaw has two hook systems:
|
OpenClaw has two hook systems:
|
||||||
|
|
||||||
- **Internal hooks** (Gateway hooks): event-driven scripts for commands and lifecycle events.
|
- **Internal hooks** (Gateway hooks): event-driven scripts for commands and lifecycle events.
|
||||||
- **Plugin hooks**: extension points inside the agent/tool lifecycle and gateway pipeline.
|
- **Plugin hooks**: extension points inside the agent/tool lifecycle and gateway pipeline.
|
||||||
|
|
||||||
### Internal hooks (Gateway hooks)
|
### Internal hooks (Gateway hooks)
|
||||||
|
|
||||||
- **`agent:bootstrap`**: runs while building bootstrap files before the system prompt is finalized.
|
- **`agent:bootstrap`**: runs while building bootstrap files before the system prompt is finalized.
|
||||||
Use this to add/remove bootstrap context files.
|
Use this to add/remove bootstrap context files.
|
||||||
- **Command hooks**: `/new`, `/reset`, `/stop`, and other command events (see Hooks doc).
|
- **Command hooks**: `/new`, `/reset`, `/stop`, and other command events (see Hooks doc).
|
||||||
@@ -68,7 +77,9 @@ OpenClaw has two hook systems:
|
|||||||
See [Hooks](/hooks) for setup and examples.
|
See [Hooks](/hooks) for setup and examples.
|
||||||
|
|
||||||
### Plugin hooks (agent + gateway lifecycle)
|
### Plugin hooks (agent + gateway lifecycle)
|
||||||
|
|
||||||
These run inside the agent loop or gateway pipeline:
|
These run inside the agent loop or gateway pipeline:
|
||||||
|
|
||||||
- **`before_agent_start`**: inject context or override system prompt before the run starts.
|
- **`before_agent_start`**: inject context or override system prompt before the run starts.
|
||||||
- **`agent_end`**: inspect the final message list and run metadata after completion.
|
- **`agent_end`**: inspect the final message list and run metadata after completion.
|
||||||
- **`before_compaction` / `after_compaction`**: observe or annotate compaction cycles.
|
- **`before_compaction` / `after_compaction`**: observe or annotate compaction cycles.
|
||||||
@@ -81,17 +92,20 @@ These run inside the agent loop or gateway pipeline:
|
|||||||
See [Plugins](/plugin#plugin-hooks) for the hook API and registration details.
|
See [Plugins](/plugin#plugin-hooks) for the hook API and registration details.
|
||||||
|
|
||||||
## Streaming + partial replies
|
## Streaming + partial replies
|
||||||
|
|
||||||
- Assistant deltas are streamed from pi-agent-core and emitted as `assistant` events.
|
- Assistant deltas are streamed from pi-agent-core and emitted as `assistant` events.
|
||||||
- Block streaming can emit partial replies either on `text_end` or `message_end`.
|
- Block streaming can emit partial replies either on `text_end` or `message_end`.
|
||||||
- Reasoning streaming can be emitted as a separate stream or as block replies.
|
- Reasoning streaming can be emitted as a separate stream or as block replies.
|
||||||
- See [Streaming](/concepts/streaming) for chunking and block reply behavior.
|
- See [Streaming](/concepts/streaming) for chunking and block reply behavior.
|
||||||
|
|
||||||
## Tool execution + messaging tools
|
## Tool execution + messaging tools
|
||||||
|
|
||||||
- Tool start/update/end events are emitted on the `tool` stream.
|
- Tool start/update/end events are emitted on the `tool` stream.
|
||||||
- Tool results are sanitized for size and image payloads before logging/emitting.
|
- Tool results are sanitized for size and image payloads before logging/emitting.
|
||||||
- Messaging tool sends are tracked to suppress duplicate assistant confirmations.
|
- Messaging tool sends are tracked to suppress duplicate assistant confirmations.
|
||||||
|
|
||||||
## Reply shaping + suppression
|
## Reply shaping + suppression
|
||||||
|
|
||||||
- Final payloads are assembled from:
|
- Final payloads are assembled from:
|
||||||
- assistant text (and optional reasoning)
|
- assistant text (and optional reasoning)
|
||||||
- inline tool summaries (when verbose + allowed)
|
- inline tool summaries (when verbose + allowed)
|
||||||
@@ -102,24 +116,29 @@ See [Plugins](/plugin#plugin-hooks) for the hook API and registration details.
|
|||||||
(unless a messaging tool already sent a user-visible reply).
|
(unless a messaging tool already sent a user-visible reply).
|
||||||
|
|
||||||
## Compaction + retries
|
## Compaction + retries
|
||||||
|
|
||||||
- Auto-compaction emits `compaction` stream events and can trigger a retry.
|
- Auto-compaction emits `compaction` stream events and can trigger a retry.
|
||||||
- On retry, in-memory buffers and tool summaries are reset to avoid duplicate output.
|
- On retry, in-memory buffers and tool summaries are reset to avoid duplicate output.
|
||||||
- See [Compaction](/concepts/compaction) for the compaction pipeline.
|
- See [Compaction](/concepts/compaction) for the compaction pipeline.
|
||||||
|
|
||||||
## Event streams (today)
|
## Event streams (today)
|
||||||
|
|
||||||
- `lifecycle`: emitted by `subscribeEmbeddedPiSession` (and as a fallback by `agentCommand`)
|
- `lifecycle`: emitted by `subscribeEmbeddedPiSession` (and as a fallback by `agentCommand`)
|
||||||
- `assistant`: streamed deltas from pi-agent-core
|
- `assistant`: streamed deltas from pi-agent-core
|
||||||
- `tool`: streamed tool events from pi-agent-core
|
- `tool`: streamed tool events from pi-agent-core
|
||||||
|
|
||||||
## Chat channel handling
|
## Chat channel handling
|
||||||
|
|
||||||
- Assistant deltas are buffered into chat `delta` messages.
|
- Assistant deltas are buffered into chat `delta` messages.
|
||||||
- A chat `final` is emitted on **lifecycle end/error**.
|
- A chat `final` is emitted on **lifecycle end/error**.
|
||||||
|
|
||||||
## Timeouts
|
## Timeouts
|
||||||
|
|
||||||
- `agent.wait` default: 30s (just the wait). `timeoutMs` param overrides.
|
- `agent.wait` default: 30s (just the wait). `timeoutMs` param overrides.
|
||||||
- Agent runtime: `agents.defaults.timeoutSeconds` default 600s; enforced in `runEmbeddedPiAgent` abort timer.
|
- Agent runtime: `agents.defaults.timeoutSeconds` default 600s; enforced in `runEmbeddedPiAgent` abort timer.
|
||||||
|
|
||||||
## Where things can end early
|
## Where things can end early
|
||||||
|
|
||||||
- Agent timeout (abort)
|
- Agent timeout (abort)
|
||||||
- AbortSignal (cancel)
|
- AbortSignal (cancel)
|
||||||
- Gateway disconnect or RPC timeout
|
- Gateway disconnect or RPC timeout
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ read_when:
|
|||||||
- You need to explain the agent workspace or its file layout
|
- You need to explain the agent workspace or its file layout
|
||||||
- You want to back up or migrate an agent workspace
|
- You want to back up or migrate an agent workspace
|
||||||
---
|
---
|
||||||
|
|
||||||
# Agent workspace
|
# Agent workspace
|
||||||
|
|
||||||
The workspace is the agent's home. It is the only working directory used for
|
The workspace is the agent's home. It is the only working directory used for
|
||||||
@@ -29,8 +30,8 @@ inside a sandbox workspace under `~/.openclaw/sandboxes`, not your host workspac
|
|||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
agent: {
|
agent: {
|
||||||
workspace: "~/.openclaw/workspace"
|
workspace: "~/.openclaw/workspace",
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ summary: "Agent runtime (embedded pi-mono), workspace contract, and session boot
|
|||||||
read_when:
|
read_when:
|
||||||
- Changing agent runtime, workspace bootstrap, or session behavior
|
- Changing agent runtime, workspace bootstrap, or session behavior
|
||||||
---
|
---
|
||||||
|
|
||||||
# Agent Runtime 🤖
|
# Agent Runtime 🤖
|
||||||
|
|
||||||
OpenClaw runs a single embedded agent runtime derived from **pi-mono**.
|
OpenClaw runs a single embedded agent runtime derived from **pi-mono**.
|
||||||
@@ -22,6 +23,7 @@ per-session workspaces under `agents.defaults.sandbox.workspaceRoot` (see
|
|||||||
## Bootstrap files (injected)
|
## Bootstrap files (injected)
|
||||||
|
|
||||||
Inside `agents.defaults.workspace`, OpenClaw expects these user-editable files:
|
Inside `agents.defaults.workspace`, OpenClaw expects these user-editable files:
|
||||||
|
|
||||||
- `AGENTS.md` — operating instructions + “memory”
|
- `AGENTS.md` — operating instructions + “memory”
|
||||||
- `SOUL.md` — persona, boundaries, tone
|
- `SOUL.md` — persona, boundaries, tone
|
||||||
- `TOOLS.md` — user-maintained tool notes (e.g. `imsg`, `sag`, conventions)
|
- `TOOLS.md` — user-maintained tool notes (e.g. `imsg`, `sag`, conventions)
|
||||||
@@ -48,11 +50,12 @@ To disable bootstrap file creation entirely (for pre-seeded workspaces), set:
|
|||||||
Core tools (read/exec/edit/write and related system tools) are always available,
|
Core tools (read/exec/edit/write and related system tools) are always available,
|
||||||
subject to tool policy. `apply_patch` is optional and gated by
|
subject to tool policy. `apply_patch` is optional and gated by
|
||||||
`tools.exec.applyPatch`. `TOOLS.md` does **not** control which tools exist; it’s
|
`tools.exec.applyPatch`. `TOOLS.md` does **not** control which tools exist; it’s
|
||||||
guidance for how *you* want them used.
|
guidance for how _you_ want them used.
|
||||||
|
|
||||||
## Skills
|
## Skills
|
||||||
|
|
||||||
OpenClaw loads skills from three locations (workspace wins on name conflict):
|
OpenClaw loads skills from three locations (workspace wins on name conflict):
|
||||||
|
|
||||||
- Bundled (shipped with the install)
|
- Bundled (shipped with the install)
|
||||||
- Managed/local: `~/.openclaw/skills`
|
- Managed/local: `~/.openclaw/skills`
|
||||||
- Workspace: `<workspace>/skills`
|
- Workspace: `<workspace>/skills`
|
||||||
@@ -69,6 +72,7 @@ OpenClaw reuses pieces of the pi-mono codebase (models/tools), but **session man
|
|||||||
## Sessions
|
## Sessions
|
||||||
|
|
||||||
Session transcripts are stored as JSONL at:
|
Session transcripts are stored as JSONL at:
|
||||||
|
|
||||||
- `~/.openclaw/agents/<agentId>/sessions/<SessionId>.jsonl`
|
- `~/.openclaw/agents/<agentId>/sessions/<SessionId>.jsonl`
|
||||||
|
|
||||||
The session ID is stable and chosen by OpenClaw.
|
The session ID is stable and chosen by OpenClaw.
|
||||||
@@ -109,9 +113,10 @@ Model refs in config (for example `agents.defaults.model` and `agents.defaults.m
|
|||||||
## Configuration (minimal)
|
## Configuration (minimal)
|
||||||
|
|
||||||
At minimum, set:
|
At minimum, set:
|
||||||
|
|
||||||
- `agents.defaults.workspace`
|
- `agents.defaults.workspace`
|
||||||
- `channels.whatsapp.allowFrom` (strongly recommended)
|
- `channels.whatsapp.allowFrom` (strongly recommended)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
*Next: [Group Chats](/concepts/group-messages)* 🦞
|
_Next: [Group Chats](/concepts/group-messages)_ 🦞
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ summary: "WebSocket gateway architecture, components, and client flows"
|
|||||||
read_when:
|
read_when:
|
||||||
- Working on gateway protocol, clients, or transports
|
- Working on gateway protocol, clients, or transports
|
||||||
---
|
---
|
||||||
|
|
||||||
# Gateway architecture
|
# Gateway architecture
|
||||||
|
|
||||||
Last updated: 2026-01-22
|
Last updated: 2026-01-22
|
||||||
@@ -22,26 +23,31 @@ Last updated: 2026-01-22
|
|||||||
## Components and flows
|
## Components and flows
|
||||||
|
|
||||||
### Gateway (daemon)
|
### Gateway (daemon)
|
||||||
|
|
||||||
- Maintains provider connections.
|
- Maintains provider connections.
|
||||||
- Exposes a typed WS API (requests, responses, server‑push events).
|
- Exposes a typed WS API (requests, responses, server‑push events).
|
||||||
- Validates inbound frames against JSON Schema.
|
- Validates inbound frames against JSON Schema.
|
||||||
- Emits events like `agent`, `chat`, `presence`, `health`, `heartbeat`, `cron`.
|
- Emits events like `agent`, `chat`, `presence`, `health`, `heartbeat`, `cron`.
|
||||||
|
|
||||||
### Clients (mac app / CLI / web admin)
|
### Clients (mac app / CLI / web admin)
|
||||||
|
|
||||||
- One WS connection per client.
|
- One WS connection per client.
|
||||||
- Send requests (`health`, `status`, `send`, `agent`, `system-presence`).
|
- Send requests (`health`, `status`, `send`, `agent`, `system-presence`).
|
||||||
- Subscribe to events (`tick`, `agent`, `presence`, `shutdown`).
|
- Subscribe to events (`tick`, `agent`, `presence`, `shutdown`).
|
||||||
|
|
||||||
### Nodes (macOS / iOS / Android / headless)
|
### Nodes (macOS / iOS / Android / headless)
|
||||||
|
|
||||||
- Connect to the **same WS server** with `role: node`.
|
- Connect to the **same WS server** with `role: node`.
|
||||||
- Provide a device identity in `connect`; pairing is **device‑based** (role `node`) and
|
- Provide a device identity in `connect`; pairing is **device‑based** (role `node`) and
|
||||||
approval lives in the device pairing store.
|
approval lives in the device pairing store.
|
||||||
- Expose commands like `canvas.*`, `camera.*`, `screen.record`, `location.get`.
|
- Expose commands like `canvas.*`, `camera.*`, `screen.record`, `location.get`.
|
||||||
|
|
||||||
Protocol details:
|
Protocol details:
|
||||||
|
|
||||||
- [Gateway protocol](/gateway/protocol)
|
- [Gateway protocol](/gateway/protocol)
|
||||||
|
|
||||||
### WebChat
|
### WebChat
|
||||||
|
|
||||||
- Static UI that uses the Gateway WS API for chat history and sends.
|
- Static UI that uses the Gateway WS API for chat history and sends.
|
||||||
- In remote setups, connects through the same SSH/Tailscale tunnel as other
|
- In remote setups, connects through the same SSH/Tailscale tunnel as other
|
||||||
clients.
|
clients.
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ summary: "Routing rules per channel (WhatsApp, Telegram, Discord, Slack) and sha
|
|||||||
read_when:
|
read_when:
|
||||||
- Changing channel routing or inbox behavior
|
- Changing channel routing or inbox behavior
|
||||||
---
|
---
|
||||||
# Channels & routing
|
|
||||||
|
|
||||||
|
# Channels & routing
|
||||||
|
|
||||||
OpenClaw routes replies **back to the channel where a message came from**. The
|
OpenClaw routes replies **back to the channel where a message came from**. The
|
||||||
model does not choose a channel; routing is deterministic and controlled by the
|
model does not choose a channel; routing is deterministic and controlled by the
|
||||||
@@ -62,8 +62,8 @@ Config:
|
|||||||
broadcast: {
|
broadcast: {
|
||||||
strategy: "parallel",
|
strategy: "parallel",
|
||||||
"120363403215116621@g.us": ["alfred", "baerbel"],
|
"120363403215116621@g.us": ["alfred", "baerbel"],
|
||||||
"+15555550123": ["support", "logger"]
|
"+15555550123": ["support", "logger"],
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -79,14 +79,12 @@ Example:
|
|||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
agents: {
|
agents: {
|
||||||
list: [
|
list: [{ id: "support", name: "Support", workspace: "~/.openclaw/workspace-support" }],
|
||||||
{ id: "support", name: "Support", workspace: "~/.openclaw/workspace-support" }
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
bindings: [
|
bindings: [
|
||||||
{ match: { channel: "slack", teamId: "T123" }, agentId: "support" },
|
{ match: { channel: "slack", teamId: "T123" }, agentId: "support" },
|
||||||
{ match: { channel: "telegram", peer: { kind: "group", id: "-100123" } }, agentId: "support" }
|
{ match: { channel: "telegram", peer: { kind: "group", id: "-100123" } }, agentId: "support" },
|
||||||
]
|
],
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -108,6 +106,7 @@ agent in one place.
|
|||||||
## Reply context
|
## Reply context
|
||||||
|
|
||||||
Inbound replies include:
|
Inbound replies include:
|
||||||
|
|
||||||
- `ReplyToId`, `ReplyToBody`, and `ReplyToSender` when available.
|
- `ReplyToId`, `ReplyToBody`, and `ReplyToSender` when available.
|
||||||
- Quoted context is appended to `Body` as a `[Replying to ...]` block.
|
- Quoted context is appended to `Body` as a `[Replying to ...]` block.
|
||||||
|
|
||||||
|
|||||||
@@ -4,24 +4,30 @@ read_when:
|
|||||||
- You want to understand auto-compaction and /compact
|
- You want to understand auto-compaction and /compact
|
||||||
- You are debugging long sessions hitting context limits
|
- You are debugging long sessions hitting context limits
|
||||||
---
|
---
|
||||||
|
|
||||||
# Context Window & Compaction
|
# Context Window & Compaction
|
||||||
|
|
||||||
Every model has a **context window** (max tokens it can see). Long-running chats accumulate messages and tool results; once the window is tight, OpenClaw **compacts** older history to stay within limits.
|
Every model has a **context window** (max tokens it can see). Long-running chats accumulate messages and tool results; once the window is tight, OpenClaw **compacts** older history to stay within limits.
|
||||||
|
|
||||||
## What compaction is
|
## What compaction is
|
||||||
|
|
||||||
Compaction **summarizes older conversation** into a compact summary entry and keeps recent messages intact. The summary is stored in the session history, so future requests use:
|
Compaction **summarizes older conversation** into a compact summary entry and keeps recent messages intact. The summary is stored in the session history, so future requests use:
|
||||||
|
|
||||||
- The compaction summary
|
- The compaction summary
|
||||||
- Recent messages after the compaction point
|
- Recent messages after the compaction point
|
||||||
|
|
||||||
Compaction **persists** in the session’s JSONL history.
|
Compaction **persists** in the session’s JSONL history.
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
See [Compaction config & modes](/concepts/compaction) for the `agents.defaults.compaction` settings.
|
See [Compaction config & modes](/concepts/compaction) for the `agents.defaults.compaction` settings.
|
||||||
|
|
||||||
## Auto-compaction (default on)
|
## Auto-compaction (default on)
|
||||||
|
|
||||||
When a session nears or exceeds the model’s context window, OpenClaw triggers auto-compaction and may retry the original request using the compacted context.
|
When a session nears or exceeds the model’s context window, OpenClaw triggers auto-compaction and may retry the original request using the compacted context.
|
||||||
|
|
||||||
You’ll see:
|
You’ll see:
|
||||||
|
|
||||||
- `🧹 Auto-compaction complete` in verbose mode
|
- `🧹 Auto-compaction complete` in verbose mode
|
||||||
- `/status` showing `🧹 Compactions: <count>`
|
- `/status` showing `🧹 Compactions: <count>`
|
||||||
|
|
||||||
@@ -29,21 +35,26 @@ Before compaction, OpenClaw can run a **silent memory flush** turn to store
|
|||||||
durable notes to disk. See [Memory](/concepts/memory) for details and config.
|
durable notes to disk. See [Memory](/concepts/memory) for details and config.
|
||||||
|
|
||||||
## Manual compaction
|
## Manual compaction
|
||||||
|
|
||||||
Use `/compact` (optionally with instructions) to force a compaction pass:
|
Use `/compact` (optionally with instructions) to force a compaction pass:
|
||||||
|
|
||||||
```
|
```
|
||||||
/compact Focus on decisions and open questions
|
/compact Focus on decisions and open questions
|
||||||
```
|
```
|
||||||
|
|
||||||
## Context window source
|
## Context window source
|
||||||
|
|
||||||
Context window is model-specific. OpenClaw uses the model definition from the configured provider catalog to determine limits.
|
Context window is model-specific. OpenClaw uses the model definition from the configured provider catalog to determine limits.
|
||||||
|
|
||||||
## Compaction vs pruning
|
## Compaction vs pruning
|
||||||
|
|
||||||
- **Compaction**: summarises and **persists** in JSONL.
|
- **Compaction**: summarises and **persists** in JSONL.
|
||||||
- **Session pruning**: trims old **tool results** only, **in-memory**, per request.
|
- **Session pruning**: trims old **tool results** only, **in-memory**, per request.
|
||||||
|
|
||||||
See [/concepts/session-pruning](/concepts/session-pruning) for pruning details.
|
See [/concepts/session-pruning](/concepts/session-pruning) for pruning details.
|
||||||
|
|
||||||
## Tips
|
## Tips
|
||||||
|
|
||||||
- Use `/compact` when sessions feel stale or context is bloated.
|
- Use `/compact` when sessions feel stale or context is bloated.
|
||||||
- Large tool outputs are already truncated; pruning can further reduce tool-result buildup.
|
- Large tool outputs are already truncated; pruning can further reduce tool-result buildup.
|
||||||
- If you need a fresh slate, `/new` or `/reset` starts a new session id.
|
- If you need a fresh slate, `/new` or `/reset` starts a new session id.
|
||||||
|
|||||||
@@ -5,16 +5,18 @@ read_when:
|
|||||||
- You are debugging why the model “knows” something (or forgot it)
|
- You are debugging why the model “knows” something (or forgot it)
|
||||||
- You want to reduce context overhead (/context, /status, /compact)
|
- You want to reduce context overhead (/context, /status, /compact)
|
||||||
---
|
---
|
||||||
|
|
||||||
# Context
|
# Context
|
||||||
|
|
||||||
“Context” is **everything OpenClaw sends to the model for a run**. It is bounded by the model’s **context window** (token limit).
|
“Context” is **everything OpenClaw sends to the model for a run**. It is bounded by the model’s **context window** (token limit).
|
||||||
|
|
||||||
Beginner mental model:
|
Beginner mental model:
|
||||||
|
|
||||||
- **System prompt** (OpenClaw-built): rules, tools, skills list, time/runtime, and injected workspace files.
|
- **System prompt** (OpenClaw-built): rules, tools, skills list, time/runtime, and injected workspace files.
|
||||||
- **Conversation history**: your messages + the assistant’s messages for this session.
|
- **Conversation history**: your messages + the assistant’s messages for this session.
|
||||||
- **Tool calls/results + attachments**: command output, file reads, images/audio, etc.
|
- **Tool calls/results + attachments**: command output, file reads, images/audio, etc.
|
||||||
|
|
||||||
Context is *not the same thing* as “memory”: memory can be stored on disk and reloaded later; context is what’s inside the model’s current window.
|
Context is _not the same thing_ as “memory”: memory can be stored on disk and reloaded later; context is what’s inside the model’s current window.
|
||||||
|
|
||||||
## Quick start (inspect context)
|
## Quick start (inspect context)
|
||||||
|
|
||||||
@@ -76,6 +78,7 @@ Top tools (schema size):
|
|||||||
## What counts toward the context window
|
## What counts toward the context window
|
||||||
|
|
||||||
Everything the model receives counts, including:
|
Everything the model receives counts, including:
|
||||||
|
|
||||||
- System prompt (all sections).
|
- System prompt (all sections).
|
||||||
- Conversation history.
|
- Conversation history.
|
||||||
- Tool calls + tool results.
|
- Tool calls + tool results.
|
||||||
@@ -86,6 +89,7 @@ Everything the model receives counts, including:
|
|||||||
## How OpenClaw builds the system prompt
|
## How OpenClaw builds the system prompt
|
||||||
|
|
||||||
The system prompt is **OpenClaw-owned** and rebuilt each run. It includes:
|
The system prompt is **OpenClaw-owned** and rebuilt each run. It includes:
|
||||||
|
|
||||||
- Tool list + short descriptions.
|
- Tool list + short descriptions.
|
||||||
- Skills list (metadata only; see below).
|
- Skills list (metadata only; see below).
|
||||||
- Workspace location.
|
- Workspace location.
|
||||||
@@ -98,6 +102,7 @@ Full breakdown: [System Prompt](/concepts/system-prompt).
|
|||||||
## Injected workspace files (Project Context)
|
## Injected workspace files (Project Context)
|
||||||
|
|
||||||
By default, OpenClaw injects a fixed set of workspace files (if present):
|
By default, OpenClaw injects a fixed set of workspace files (if present):
|
||||||
|
|
||||||
- `AGENTS.md`
|
- `AGENTS.md`
|
||||||
- `SOUL.md`
|
- `SOUL.md`
|
||||||
- `TOOLS.md`
|
- `TOOLS.md`
|
||||||
@@ -112,19 +117,21 @@ Large files are truncated per-file using `agents.defaults.bootstrapMaxChars` (de
|
|||||||
|
|
||||||
The system prompt includes a compact **skills list** (name + description + location). This list has real overhead.
|
The system prompt includes a compact **skills list** (name + description + location). This list has real overhead.
|
||||||
|
|
||||||
Skill instructions are *not* included by default. The model is expected to `read` the skill’s `SKILL.md` **only when needed**.
|
Skill instructions are _not_ included by default. The model is expected to `read` the skill’s `SKILL.md` **only when needed**.
|
||||||
|
|
||||||
## Tools: there are two costs
|
## Tools: there are two costs
|
||||||
|
|
||||||
Tools affect context in two ways:
|
Tools affect context in two ways:
|
||||||
1) **Tool list text** in the system prompt (what you see as “Tooling”).
|
|
||||||
2) **Tool schemas** (JSON). These are sent to the model so it can call tools. They count toward context even though you don’t see them as plain text.
|
1. **Tool list text** in the system prompt (what you see as “Tooling”).
|
||||||
|
2. **Tool schemas** (JSON). These are sent to the model so it can call tools. They count toward context even though you don’t see them as plain text.
|
||||||
|
|
||||||
`/context detail` breaks down the biggest tool schemas so you can see what dominates.
|
`/context detail` breaks down the biggest tool schemas so you can see what dominates.
|
||||||
|
|
||||||
## Commands, directives, and “inline shortcuts”
|
## Commands, directives, and “inline shortcuts”
|
||||||
|
|
||||||
Slash commands are handled by the Gateway. There are a few different behaviors:
|
Slash commands are handled by the Gateway. There are a few different behaviors:
|
||||||
|
|
||||||
- **Standalone commands**: a message that is only `/...` runs as a command.
|
- **Standalone commands**: a message that is only `/...` runs as a command.
|
||||||
- **Directives**: `/think`, `/verbose`, `/reasoning`, `/elevated`, `/model`, `/queue` are stripped before the model sees the message.
|
- **Directives**: `/think`, `/verbose`, `/reasoning`, `/elevated`, `/model`, `/queue` are stripped before the model sees the message.
|
||||||
- Directive-only messages persist session settings.
|
- Directive-only messages persist session settings.
|
||||||
@@ -136,15 +143,17 @@ Details: [Slash commands](/tools/slash-commands).
|
|||||||
## Sessions, compaction, and pruning (what persists)
|
## Sessions, compaction, and pruning (what persists)
|
||||||
|
|
||||||
What persists across messages depends on the mechanism:
|
What persists across messages depends on the mechanism:
|
||||||
|
|
||||||
- **Normal history** persists in the session transcript until compacted/pruned by policy.
|
- **Normal history** persists in the session transcript until compacted/pruned by policy.
|
||||||
- **Compaction** persists a summary into the transcript and keeps recent messages intact.
|
- **Compaction** persists a summary into the transcript and keeps recent messages intact.
|
||||||
- **Pruning** removes old tool results from the *in-memory* prompt for a run, but does not rewrite the transcript.
|
- **Pruning** removes old tool results from the _in-memory_ prompt for a run, but does not rewrite the transcript.
|
||||||
|
|
||||||
Docs: [Session](/concepts/session), [Compaction](/concepts/compaction), [Session pruning](/concepts/session-pruning).
|
Docs: [Session](/concepts/session), [Compaction](/concepts/compaction), [Session pruning](/concepts/session-pruning).
|
||||||
|
|
||||||
## What `/context` actually reports
|
## What `/context` actually reports
|
||||||
|
|
||||||
`/context` prefers the latest **run-built** system prompt report when available:
|
`/context` prefers the latest **run-built** system prompt report when available:
|
||||||
|
|
||||||
- `System prompt (run)` = captured from the last embedded (tool-capable) run and persisted in the session store.
|
- `System prompt (run)` = captured from the last embedded (tool-capable) run and persisted in the session store.
|
||||||
- `System prompt (estimate)` = computed on the fly when no run report exists (or when running via a CLI backend that doesn’t generate the report).
|
- `System prompt (estimate)` = computed on the fly when no run report exists (or when running via a CLI backend that doesn’t generate the report).
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ summary: "Behavior and config for WhatsApp group message handling (mentionPatter
|
|||||||
read_when:
|
read_when:
|
||||||
- Changing group message rules or mentions
|
- Changing group message rules or mentions
|
||||||
---
|
---
|
||||||
|
|
||||||
# Group messages (WhatsApp web channel)
|
# Group messages (WhatsApp web channel)
|
||||||
|
|
||||||
Goal: let Clawd sit in WhatsApp groups, wake up only when pinged, and keep that thread separate from the personal DM session.
|
Goal: let Clawd sit in WhatsApp groups, wake up only when pinged, and keep that thread separate from the personal DM session.
|
||||||
@@ -10,15 +11,17 @@ Goal: let Clawd sit in WhatsApp groups, wake up only when pinged, and keep that
|
|||||||
Note: `agents.list[].groupChat.mentionPatterns` is now used by Telegram/Discord/Slack/iMessage as well; this doc focuses on WhatsApp-specific behavior. For multi-agent setups, set `agents.list[].groupChat.mentionPatterns` per agent (or use `messages.groupChat.mentionPatterns` as a global fallback).
|
Note: `agents.list[].groupChat.mentionPatterns` is now used by Telegram/Discord/Slack/iMessage as well; this doc focuses on WhatsApp-specific behavior. For multi-agent setups, set `agents.list[].groupChat.mentionPatterns` per agent (or use `messages.groupChat.mentionPatterns` as a global fallback).
|
||||||
|
|
||||||
## What’s implemented (2025-12-03)
|
## What’s implemented (2025-12-03)
|
||||||
|
|
||||||
- Activation modes: `mention` (default) or `always`. `mention` requires a ping (real WhatsApp @-mentions via `mentionedJids`, regex patterns, or the bot’s E.164 anywhere in the text). `always` wakes the agent on every message but it should reply only when it can add meaningful value; otherwise it returns the silent token `NO_REPLY`. Defaults can be set in config (`channels.whatsapp.groups`) and overridden per group via `/activation`. When `channels.whatsapp.groups` is set, it also acts as a group allowlist (include `"*"` to allow all).
|
- Activation modes: `mention` (default) or `always`. `mention` requires a ping (real WhatsApp @-mentions via `mentionedJids`, regex patterns, or the bot’s E.164 anywhere in the text). `always` wakes the agent on every message but it should reply only when it can add meaningful value; otherwise it returns the silent token `NO_REPLY`. Defaults can be set in config (`channels.whatsapp.groups`) and overridden per group via `/activation`. When `channels.whatsapp.groups` is set, it also acts as a group allowlist (include `"*"` to allow all).
|
||||||
- Group policy: `channels.whatsapp.groupPolicy` controls whether group messages are accepted (`open|disabled|allowlist`). `allowlist` uses `channels.whatsapp.groupAllowFrom` (fallback: explicit `channels.whatsapp.allowFrom`). Default is `allowlist` (blocked until you add senders).
|
- Group policy: `channels.whatsapp.groupPolicy` controls whether group messages are accepted (`open|disabled|allowlist`). `allowlist` uses `channels.whatsapp.groupAllowFrom` (fallback: explicit `channels.whatsapp.allowFrom`). Default is `allowlist` (blocked until you add senders).
|
||||||
- Per-group sessions: session keys look like `agent:<agentId>:whatsapp:group:<jid>` so commands such as `/verbose on` or `/think high` (sent as standalone messages) are scoped to that group; personal DM state is untouched. Heartbeats are skipped for group threads.
|
- Per-group sessions: session keys look like `agent:<agentId>:whatsapp:group:<jid>` so commands such as `/verbose on` or `/think high` (sent as standalone messages) are scoped to that group; personal DM state is untouched. Heartbeats are skipped for group threads.
|
||||||
- Context injection: **pending-only** group messages (default 50) that *did not* trigger a run are prefixed under `[Chat messages since your last reply - for context]`, with the triggering line under `[Current message - respond to this]`. Messages already in the session are not re-injected.
|
- Context injection: **pending-only** group messages (default 50) that _did not_ trigger a run are prefixed under `[Chat messages since your last reply - for context]`, with the triggering line under `[Current message - respond to this]`. Messages already in the session are not re-injected.
|
||||||
- Sender surfacing: every group batch now ends with `[from: Sender Name (+E164)]` so Pi knows who is speaking.
|
- Sender surfacing: every group batch now ends with `[from: Sender Name (+E164)]` so Pi knows who is speaking.
|
||||||
- Ephemeral/view-once: we unwrap those before extracting text/mentions, so pings inside them still trigger.
|
- Ephemeral/view-once: we unwrap those before extracting text/mentions, so pings inside them still trigger.
|
||||||
- Group system prompt: on the first turn of a group session (and whenever `/activation` changes the mode) we inject a short blurb into the system prompt like `You are replying inside the WhatsApp group "<subject>". Group members: Alice (+44...), Bob (+43...), … Activation: trigger-only … Address the specific sender noted in the message context.` If metadata isn’t available we still tell the agent it’s a group chat.
|
- Group system prompt: on the first turn of a group session (and whenever `/activation` changes the mode) we inject a short blurb into the system prompt like `You are replying inside the WhatsApp group "<subject>". Group members: Alice (+44...), Bob (+43...), … Activation: trigger-only … Address the specific sender noted in the message context.` If metadata isn’t available we still tell the agent it’s a group chat.
|
||||||
|
|
||||||
## Config example (WhatsApp)
|
## Config example (WhatsApp)
|
||||||
|
|
||||||
Add a `groupChat` block to `~/.openclaw/openclaw.json` so display-name pings work even when WhatsApp strips the visual `@` in the text body:
|
Add a `groupChat` block to `~/.openclaw/openclaw.json` so display-name pings work even when WhatsApp strips the visual `@` in the text body:
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
@@ -26,9 +29,9 @@ Add a `groupChat` block to `~/.openclaw/openclaw.json` so display-name pings wor
|
|||||||
channels: {
|
channels: {
|
||||||
whatsapp: {
|
whatsapp: {
|
||||||
groups: {
|
groups: {
|
||||||
"*": { requireMention: true }
|
"*": { requireMention: true },
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
agents: {
|
agents: {
|
||||||
list: [
|
list: [
|
||||||
@@ -36,42 +39,44 @@ Add a `groupChat` block to `~/.openclaw/openclaw.json` so display-name pings wor
|
|||||||
id: "main",
|
id: "main",
|
||||||
groupChat: {
|
groupChat: {
|
||||||
historyLimit: 50,
|
historyLimit: 50,
|
||||||
mentionPatterns: [
|
mentionPatterns: ["@?openclaw", "\\+?15555550123"],
|
||||||
"@?openclaw",
|
},
|
||||||
"\\+?15555550123"
|
},
|
||||||
]
|
],
|
||||||
}
|
},
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
- The regexes are case-insensitive; they cover a display-name ping like `@openclaw` and the raw number with or without `+`/spaces.
|
- The regexes are case-insensitive; they cover a display-name ping like `@openclaw` and the raw number with or without `+`/spaces.
|
||||||
- WhatsApp still sends canonical mentions via `mentionedJids` when someone taps the contact, so the number fallback is rarely needed but is a useful safety net.
|
- WhatsApp still sends canonical mentions via `mentionedJids` when someone taps the contact, so the number fallback is rarely needed but is a useful safety net.
|
||||||
|
|
||||||
### Activation command (owner-only)
|
### Activation command (owner-only)
|
||||||
|
|
||||||
Use the group chat command:
|
Use the group chat command:
|
||||||
|
|
||||||
- `/activation mention`
|
- `/activation mention`
|
||||||
- `/activation always`
|
- `/activation always`
|
||||||
|
|
||||||
Only the owner number (from `channels.whatsapp.allowFrom`, or the bot’s own E.164 when unset) can change this. Send `/status` as a standalone message in the group to see the current activation mode.
|
Only the owner number (from `channels.whatsapp.allowFrom`, or the bot’s own E.164 when unset) can change this. Send `/status` as a standalone message in the group to see the current activation mode.
|
||||||
|
|
||||||
## How to use
|
## How to use
|
||||||
1) Add your WhatsApp account (the one running OpenClaw) to the group.
|
|
||||||
2) Say `@openclaw …` (or include the number). Only allowlisted senders can trigger it unless you set `groupPolicy: "open"`.
|
1. Add your WhatsApp account (the one running OpenClaw) to the group.
|
||||||
3) The agent prompt will include recent group context plus the trailing `[from: …]` marker so it can address the right person.
|
2. Say `@openclaw …` (or include the number). Only allowlisted senders can trigger it unless you set `groupPolicy: "open"`.
|
||||||
4) Session-level directives (`/verbose on`, `/think high`, `/new` or `/reset`, `/compact`) apply only to that group’s session; send them as standalone messages so they register. Your personal DM session remains independent.
|
3. The agent prompt will include recent group context plus the trailing `[from: …]` marker so it can address the right person.
|
||||||
|
4. Session-level directives (`/verbose on`, `/think high`, `/new` or `/reset`, `/compact`) apply only to that group’s session; send them as standalone messages so they register. Your personal DM session remains independent.
|
||||||
|
|
||||||
## Testing / verification
|
## Testing / verification
|
||||||
|
|
||||||
- Manual smoke:
|
- Manual smoke:
|
||||||
- Send an `@openclaw` ping in the group and confirm a reply that references the sender name.
|
- Send an `@openclaw` ping in the group and confirm a reply that references the sender name.
|
||||||
- Send a second ping and verify the history block is included then cleared on the next turn.
|
- Send a second ping and verify the history block is included then cleared on the next turn.
|
||||||
- Check gateway logs (run with `--verbose`) to see `inbound web message` entries showing `from: <groupJid>` and the `[from: …]` suffix.
|
- Check gateway logs (run with `--verbose`) to see `inbound web message` entries showing `from: <groupJid>` and the `[from: …]` suffix.
|
||||||
|
|
||||||
## Known considerations
|
## Known considerations
|
||||||
|
|
||||||
- Heartbeats are intentionally skipped for groups to avoid noisy broadcasts.
|
- Heartbeats are intentionally skipped for groups to avoid noisy broadcasts.
|
||||||
- Echo suppression uses the combined batch string; if you send identical text twice without mentions, only the first will get a response.
|
- Echo suppression uses the combined batch string; if you send identical text twice without mentions, only the first will get a response.
|
||||||
- Session store entries will appear as `agent:<agentId>:whatsapp:group:<jid>` in the session store (`~/.openclaw/agents/<agentId>/sessions/sessions.json` by default); a missing entry just means the group hasn’t triggered a run yet.
|
- Session store entries will appear as `agent:<agentId>:whatsapp:group:<jid>` in the session store (`~/.openclaw/agents/<agentId>/sessions/sessions.json` by default); a missing entry just means the group hasn’t triggered a run yet.
|
||||||
|
|||||||
@@ -3,26 +3,31 @@ summary: "Group chat behavior across surfaces (WhatsApp/Telegram/Discord/Slack/S
|
|||||||
read_when:
|
read_when:
|
||||||
- Changing group chat behavior or mention gating
|
- Changing group chat behavior or mention gating
|
||||||
---
|
---
|
||||||
|
|
||||||
# Groups
|
# Groups
|
||||||
|
|
||||||
OpenClaw treats group chats consistently across surfaces: WhatsApp, Telegram, Discord, Slack, Signal, iMessage, Microsoft Teams.
|
OpenClaw treats group chats consistently across surfaces: WhatsApp, Telegram, Discord, Slack, Signal, iMessage, Microsoft Teams.
|
||||||
|
|
||||||
## Beginner intro (2 minutes)
|
## Beginner intro (2 minutes)
|
||||||
|
|
||||||
OpenClaw “lives” on your own messaging accounts. There is no separate WhatsApp bot user.
|
OpenClaw “lives” on your own messaging accounts. There is no separate WhatsApp bot user.
|
||||||
If **you** are in a group, OpenClaw can see that group and respond there.
|
If **you** are in a group, OpenClaw can see that group and respond there.
|
||||||
|
|
||||||
Default behavior:
|
Default behavior:
|
||||||
|
|
||||||
- Groups are restricted (`groupPolicy: "allowlist"`).
|
- Groups are restricted (`groupPolicy: "allowlist"`).
|
||||||
- Replies require a mention unless you explicitly disable mention gating.
|
- Replies require a mention unless you explicitly disable mention gating.
|
||||||
|
|
||||||
Translation: allowlisted senders can trigger OpenClaw by mentioning it.
|
Translation: allowlisted senders can trigger OpenClaw by mentioning it.
|
||||||
|
|
||||||
> TL;DR
|
> TL;DR
|
||||||
|
>
|
||||||
> - **DM access** is controlled by `*.allowFrom`.
|
> - **DM access** is controlled by `*.allowFrom`.
|
||||||
> - **Group access** is controlled by `*.groupPolicy` + allowlists (`*.groups`, `*.groupAllowFrom`).
|
> - **Group access** is controlled by `*.groupPolicy` + allowlists (`*.groups`, `*.groupAllowFrom`).
|
||||||
> - **Reply triggering** is controlled by mention gating (`requireMention`, `/activation`).
|
> - **Reply triggering** is controlled by mention gating (`requireMention`, `/activation`).
|
||||||
|
|
||||||
Quick flow (what happens to a group message):
|
Quick flow (what happens to a group message):
|
||||||
|
|
||||||
```
|
```
|
||||||
groupPolicy? disabled -> drop
|
groupPolicy? disabled -> drop
|
||||||
groupPolicy? allowlist -> group allowed? no -> drop
|
groupPolicy? allowlist -> group allowed? no -> drop
|
||||||
@@ -41,6 +46,7 @@ If you want...
|
|||||||
| Only you can trigger in groups | `groupPolicy: "allowlist"`, `groupAllowFrom: ["+1555..."]` |
|
| Only you can trigger in groups | `groupPolicy: "allowlist"`, `groupAllowFrom: ["+1555..."]` |
|
||||||
|
|
||||||
## Session keys
|
## Session keys
|
||||||
|
|
||||||
- Group sessions use `agent:<agentId>:<channel>:group:<id>` session keys (rooms/channels use `agent:<agentId>:<channel>:channel:<id>`).
|
- Group sessions use `agent:<agentId>:<channel>:group:<id>` session keys (rooms/channels use `agent:<agentId>:<channel>:channel:<id>`).
|
||||||
- Telegram forum topics add `:topic:<threadId>` to the group id so each topic has its own session.
|
- Telegram forum topics add `:topic:<threadId>` to the group id so each topic has its own session.
|
||||||
- Direct chats use the main session (or per-sender if configured).
|
- Direct chats use the main session (or per-sender if configured).
|
||||||
@@ -53,6 +59,7 @@ Yes — this works well if your “personal” traffic is **DMs** and your “pu
|
|||||||
Why: in single-agent mode, DMs typically land in the **main** session key (`agent:main:main`), while groups always use **non-main** session keys (`agent:main:<channel>:group:<id>`). If you enable sandboxing with `mode: "non-main"`, those group sessions run in Docker while your main DM session stays on-host.
|
Why: in single-agent mode, DMs typically land in the **main** session key (`agent:main:main`), while groups always use **non-main** session keys (`agent:main:<channel>:group:<id>`). If you enable sandboxing with `mode: "non-main"`, those group sessions run in Docker while your main DM session stays on-host.
|
||||||
|
|
||||||
This gives you one agent “brain” (shared workspace + memory), but two execution postures:
|
This gives you one agent “brain” (shared workspace + memory), but two execution postures:
|
||||||
|
|
||||||
- **DMs**: full tools (host)
|
- **DMs**: full tools (host)
|
||||||
- **Groups**: sandbox + restricted tools (Docker)
|
- **Groups**: sandbox + restricted tools (Docker)
|
||||||
|
|
||||||
@@ -67,19 +74,19 @@ Example (DMs on host, groups sandboxed + messaging-only tools):
|
|||||||
sandbox: {
|
sandbox: {
|
||||||
mode: "non-main", // groups/channels are non-main -> sandboxed
|
mode: "non-main", // groups/channels are non-main -> sandboxed
|
||||||
scope: "session", // strongest isolation (one container per group/channel)
|
scope: "session", // strongest isolation (one container per group/channel)
|
||||||
workspaceAccess: "none"
|
workspaceAccess: "none",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
tools: {
|
tools: {
|
||||||
sandbox: {
|
sandbox: {
|
||||||
tools: {
|
tools: {
|
||||||
// If allow is non-empty, everything else is blocked (deny still wins).
|
// If allow is non-empty, everything else is blocked (deny still wins).
|
||||||
allow: ["group:messaging", "group:sessions"],
|
allow: ["group:messaging", "group:sessions"],
|
||||||
deny: ["group:runtime", "group:fs", "group:ui", "nodes", "cron", "gateway"]
|
deny: ["group:runtime", "group:fs", "group:ui", "nodes", "cron", "gateway"],
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -96,25 +103,28 @@ Want “groups can only see folder X” instead of “no host access”? Keep `w
|
|||||||
docker: {
|
docker: {
|
||||||
binds: [
|
binds: [
|
||||||
// hostPath:containerPath:mode
|
// hostPath:containerPath:mode
|
||||||
"~/FriendsShared:/data:ro"
|
"~/FriendsShared:/data:ro",
|
||||||
]
|
],
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Related:
|
Related:
|
||||||
|
|
||||||
- Configuration keys and defaults: [Gateway configuration](/gateway/configuration#agentsdefaultssandbox)
|
- Configuration keys and defaults: [Gateway configuration](/gateway/configuration#agentsdefaultssandbox)
|
||||||
- Debugging why a tool is blocked: [Sandbox vs Tool Policy vs Elevated](/gateway/sandbox-vs-tool-policy-vs-elevated)
|
- Debugging why a tool is blocked: [Sandbox vs Tool Policy vs Elevated](/gateway/sandbox-vs-tool-policy-vs-elevated)
|
||||||
- Bind mounts details: [Sandboxing](/gateway/sandboxing#custom-bind-mounts)
|
- Bind mounts details: [Sandboxing](/gateway/sandboxing#custom-bind-mounts)
|
||||||
|
|
||||||
## Display labels
|
## Display labels
|
||||||
|
|
||||||
- UI labels use `displayName` when available, formatted as `<channel>:<token>`.
|
- UI labels use `displayName` when available, formatted as `<channel>:<token>`.
|
||||||
- `#room` is reserved for rooms/channels; group chats use `g-<slug>` (lowercase, spaces -> `-`, keep `#@+._-`).
|
- `#room` is reserved for rooms/channels; group chats use `g-<slug>` (lowercase, spaces -> `-`, keep `#@+._-`).
|
||||||
|
|
||||||
## Group policy
|
## Group policy
|
||||||
|
|
||||||
Control how group/room messages are handled per channel:
|
Control how group/room messages are handled per channel:
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
@@ -122,53 +132,54 @@ Control how group/room messages are handled per channel:
|
|||||||
channels: {
|
channels: {
|
||||||
whatsapp: {
|
whatsapp: {
|
||||||
groupPolicy: "disabled", // "open" | "disabled" | "allowlist"
|
groupPolicy: "disabled", // "open" | "disabled" | "allowlist"
|
||||||
groupAllowFrom: ["+15551234567"]
|
groupAllowFrom: ["+15551234567"],
|
||||||
},
|
},
|
||||||
telegram: {
|
telegram: {
|
||||||
groupPolicy: "disabled",
|
groupPolicy: "disabled",
|
||||||
groupAllowFrom: ["123456789", "@username"]
|
groupAllowFrom: ["123456789", "@username"],
|
||||||
},
|
},
|
||||||
signal: {
|
signal: {
|
||||||
groupPolicy: "disabled",
|
groupPolicy: "disabled",
|
||||||
groupAllowFrom: ["+15551234567"]
|
groupAllowFrom: ["+15551234567"],
|
||||||
},
|
},
|
||||||
imessage: {
|
imessage: {
|
||||||
groupPolicy: "disabled",
|
groupPolicy: "disabled",
|
||||||
groupAllowFrom: ["chat_id:123"]
|
groupAllowFrom: ["chat_id:123"],
|
||||||
},
|
},
|
||||||
msteams: {
|
msteams: {
|
||||||
groupPolicy: "disabled",
|
groupPolicy: "disabled",
|
||||||
groupAllowFrom: ["user@org.com"]
|
groupAllowFrom: ["user@org.com"],
|
||||||
},
|
},
|
||||||
discord: {
|
discord: {
|
||||||
groupPolicy: "allowlist",
|
groupPolicy: "allowlist",
|
||||||
guilds: {
|
guilds: {
|
||||||
"GUILD_ID": { channels: { help: { allow: true } } }
|
GUILD_ID: { channels: { help: { allow: true } } },
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
slack: {
|
slack: {
|
||||||
groupPolicy: "allowlist",
|
groupPolicy: "allowlist",
|
||||||
channels: { "#general": { allow: true } }
|
channels: { "#general": { allow: true } },
|
||||||
},
|
},
|
||||||
matrix: {
|
matrix: {
|
||||||
groupPolicy: "allowlist",
|
groupPolicy: "allowlist",
|
||||||
groupAllowFrom: ["@owner:example.org"],
|
groupAllowFrom: ["@owner:example.org"],
|
||||||
groups: {
|
groups: {
|
||||||
"!roomId:example.org": { allow: true },
|
"!roomId:example.org": { allow: true },
|
||||||
"#alias:example.org": { allow: true }
|
"#alias:example.org": { allow: true },
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
| Policy | Behavior |
|
| Policy | Behavior |
|
||||||
|--------|----------|
|
| ------------- | ------------------------------------------------------------ |
|
||||||
| `"open"` | Groups bypass allowlists; mention-gating still applies. |
|
| `"open"` | Groups bypass allowlists; mention-gating still applies. |
|
||||||
| `"disabled"` | Block all group messages entirely. |
|
| `"disabled"` | Block all group messages entirely. |
|
||||||
| `"allowlist"` | Only allow groups/rooms that match the configured allowlist. |
|
| `"allowlist"` | Only allow groups/rooms that match the configured allowlist. |
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
- `groupPolicy` is separate from mention-gating (which requires @mentions).
|
- `groupPolicy` is separate from mention-gating (which requires @mentions).
|
||||||
- WhatsApp/Telegram/Signal/iMessage/Microsoft Teams: use `groupAllowFrom` (fallback: explicit `allowFrom`).
|
- WhatsApp/Telegram/Signal/iMessage/Microsoft Teams: use `groupAllowFrom` (fallback: explicit `allowFrom`).
|
||||||
- Discord: allowlist uses `channels.discord.guilds.<id>.channels`.
|
- Discord: allowlist uses `channels.discord.guilds.<id>.channels`.
|
||||||
@@ -179,11 +190,13 @@ Notes:
|
|||||||
- Default is `groupPolicy: "allowlist"`; if your group allowlist is empty, group messages are blocked.
|
- Default is `groupPolicy: "allowlist"`; if your group allowlist is empty, group messages are blocked.
|
||||||
|
|
||||||
Quick mental model (evaluation order for group messages):
|
Quick mental model (evaluation order for group messages):
|
||||||
1) `groupPolicy` (open/disabled/allowlist)
|
|
||||||
2) group allowlists (`*.groups`, `*.groupAllowFrom`, channel-specific allowlist)
|
1. `groupPolicy` (open/disabled/allowlist)
|
||||||
3) mention gating (`requireMention`, `/activation`)
|
2. group allowlists (`*.groups`, `*.groupAllowFrom`, channel-specific allowlist)
|
||||||
|
3. mention gating (`requireMention`, `/activation`)
|
||||||
|
|
||||||
## Mention gating (default)
|
## Mention gating (default)
|
||||||
|
|
||||||
Group messages require a mention unless overridden per group. Defaults live per subsystem under `*.groups."*"`.
|
Group messages require a mention unless overridden per group. Defaults live per subsystem under `*.groups."*"`.
|
||||||
|
|
||||||
Replying to a bot message counts as an implicit mention (when the channel supports reply metadata). This applies to Telegram, WhatsApp, Slack, Discord, and Microsoft Teams.
|
Replying to a bot message counts as an implicit mention (when the channel supports reply metadata). This applies to Telegram, WhatsApp, Slack, Discord, and Microsoft Teams.
|
||||||
@@ -194,21 +207,21 @@ Replying to a bot message counts as an implicit mention (when the channel suppor
|
|||||||
whatsapp: {
|
whatsapp: {
|
||||||
groups: {
|
groups: {
|
||||||
"*": { requireMention: true },
|
"*": { requireMention: true },
|
||||||
"123@g.us": { requireMention: false }
|
"123@g.us": { requireMention: false },
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
telegram: {
|
telegram: {
|
||||||
groups: {
|
groups: {
|
||||||
"*": { requireMention: true },
|
"*": { requireMention: true },
|
||||||
"123456789": { requireMention: false }
|
"123456789": { requireMention: false },
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
imessage: {
|
imessage: {
|
||||||
groups: {
|
groups: {
|
||||||
"*": { requireMention: true },
|
"*": { requireMention: true },
|
||||||
"123": { requireMention: false }
|
"123": { requireMention: false },
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
agents: {
|
agents: {
|
||||||
list: [
|
list: [
|
||||||
@@ -216,15 +229,16 @@ Replying to a bot message counts as an implicit mention (when the channel suppor
|
|||||||
id: "main",
|
id: "main",
|
||||||
groupChat: {
|
groupChat: {
|
||||||
mentionPatterns: ["@openclaw", "openclaw", "\\+15555550123"],
|
mentionPatterns: ["@openclaw", "openclaw", "\\+15555550123"],
|
||||||
historyLimit: 50
|
historyLimit: 50,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
- `mentionPatterns` are case-insensitive regexes.
|
- `mentionPatterns` are case-insensitive regexes.
|
||||||
- Surfaces that provide explicit mentions still pass; patterns are a fallback.
|
- Surfaces that provide explicit mentions still pass; patterns are a fallback.
|
||||||
- Per-agent override: `agents.list[].groupChat.mentionPatterns` (useful when multiple agents share a group).
|
- Per-agent override: `agents.list[].groupChat.mentionPatterns` (useful when multiple agents share a group).
|
||||||
@@ -233,16 +247,18 @@ Notes:
|
|||||||
- Group history context is wrapped uniformly across channels and is **pending-only** (messages skipped due to mention gating); use `messages.groupChat.historyLimit` for the global default and `channels.<channel>.historyLimit` (or `channels.<channel>.accounts.*.historyLimit`) for overrides. Set `0` to disable.
|
- Group history context is wrapped uniformly across channels and is **pending-only** (messages skipped due to mention gating); use `messages.groupChat.historyLimit` for the global default and `channels.<channel>.historyLimit` (or `channels.<channel>.accounts.*.historyLimit`) for overrides. Set `0` to disable.
|
||||||
|
|
||||||
## Group/channel tool restrictions (optional)
|
## Group/channel tool restrictions (optional)
|
||||||
|
|
||||||
Some channel configs support restricting which tools are available **inside a specific group/room/channel**.
|
Some channel configs support restricting which tools are available **inside a specific group/room/channel**.
|
||||||
|
|
||||||
- `tools`: allow/deny tools for the whole group.
|
- `tools`: allow/deny tools for the whole group.
|
||||||
- `toolsBySender`: per-sender overrides within the group (keys are sender IDs/usernames/emails/phone numbers depending on the channel). Use `"*"` as a wildcard.
|
- `toolsBySender`: per-sender overrides within the group (keys are sender IDs/usernames/emails/phone numbers depending on the channel). Use `"*"` as a wildcard.
|
||||||
|
|
||||||
Resolution order (most specific wins):
|
Resolution order (most specific wins):
|
||||||
1) group/channel `toolsBySender` match
|
|
||||||
2) group/channel `tools`
|
1. group/channel `toolsBySender` match
|
||||||
3) default (`"*"`) `toolsBySender` match
|
2. group/channel `tools`
|
||||||
4) default (`"*"`) `tools`
|
3. default (`"*"`) `toolsBySender` match
|
||||||
|
4. default (`"*"`) `tools`
|
||||||
|
|
||||||
Example (Telegram):
|
Example (Telegram):
|
||||||
|
|
||||||
@@ -255,78 +271,88 @@ Example (Telegram):
|
|||||||
"-1001234567890": {
|
"-1001234567890": {
|
||||||
tools: { deny: ["exec", "read", "write"] },
|
tools: { deny: ["exec", "read", "write"] },
|
||||||
toolsBySender: {
|
toolsBySender: {
|
||||||
"123456789": { alsoAllow: ["exec"] }
|
"123456789": { alsoAllow: ["exec"] },
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
- Group/channel tool restrictions are applied in addition to global/agent tool policy (deny still wins).
|
- Group/channel tool restrictions are applied in addition to global/agent tool policy (deny still wins).
|
||||||
- Some channels use different nesting for rooms/channels (e.g., Discord `guilds.*.channels.*`, Slack `channels.*`, MS Teams `teams.*.channels.*`).
|
- Some channels use different nesting for rooms/channels (e.g., Discord `guilds.*.channels.*`, Slack `channels.*`, MS Teams `teams.*.channels.*`).
|
||||||
|
|
||||||
## Group allowlists
|
## Group allowlists
|
||||||
|
|
||||||
When `channels.whatsapp.groups`, `channels.telegram.groups`, or `channels.imessage.groups` is configured, the keys act as a group allowlist. Use `"*"` to allow all groups while still setting default mention behavior.
|
When `channels.whatsapp.groups`, `channels.telegram.groups`, or `channels.imessage.groups` is configured, the keys act as a group allowlist. Use `"*"` to allow all groups while still setting default mention behavior.
|
||||||
|
|
||||||
Common intents (copy/paste):
|
Common intents (copy/paste):
|
||||||
|
|
||||||
1) Disable all group replies
|
1. Disable all group replies
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
channels: { whatsapp: { groupPolicy: "disabled" } }
|
channels: { whatsapp: { groupPolicy: "disabled" } },
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
2) Allow only specific groups (WhatsApp)
|
2. Allow only specific groups (WhatsApp)
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
channels: {
|
channels: {
|
||||||
whatsapp: {
|
whatsapp: {
|
||||||
groups: {
|
groups: {
|
||||||
"123@g.us": { requireMention: true },
|
"123@g.us": { requireMention: true },
|
||||||
"456@g.us": { requireMention: false }
|
"456@g.us": { requireMention: false },
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
3) Allow all groups but require mention (explicit)
|
3. Allow all groups but require mention (explicit)
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
channels: {
|
channels: {
|
||||||
whatsapp: {
|
whatsapp: {
|
||||||
groups: { "*": { requireMention: true } }
|
groups: { "*": { requireMention: true } },
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
4) Only the owner can trigger in groups (WhatsApp)
|
4. Only the owner can trigger in groups (WhatsApp)
|
||||||
|
|
||||||
```json5
|
```json5
|
||||||
{
|
{
|
||||||
channels: {
|
channels: {
|
||||||
whatsapp: {
|
whatsapp: {
|
||||||
groupPolicy: "allowlist",
|
groupPolicy: "allowlist",
|
||||||
groupAllowFrom: ["+15551234567"],
|
groupAllowFrom: ["+15551234567"],
|
||||||
groups: { "*": { requireMention: true } }
|
groups: { "*": { requireMention: true } },
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Activation (owner-only)
|
## Activation (owner-only)
|
||||||
|
|
||||||
Group owners can toggle per-group activation:
|
Group owners can toggle per-group activation:
|
||||||
|
|
||||||
- `/activation mention`
|
- `/activation mention`
|
||||||
- `/activation always`
|
- `/activation always`
|
||||||
|
|
||||||
Owner is determined by `channels.whatsapp.allowFrom` (or the bot’s self E.164 when unset). Send the command as a standalone message. Other surfaces currently ignore `/activation`.
|
Owner is determined by `channels.whatsapp.allowFrom` (or the bot’s self E.164 when unset). Send the command as a standalone message. Other surfaces currently ignore `/activation`.
|
||||||
|
|
||||||
## Context fields
|
## Context fields
|
||||||
|
|
||||||
Group inbound payloads set:
|
Group inbound payloads set:
|
||||||
|
|
||||||
- `ChatType=group`
|
- `ChatType=group`
|
||||||
- `GroupSubject` (if known)
|
- `GroupSubject` (if known)
|
||||||
- `GroupMembers` (if known)
|
- `GroupMembers` (if known)
|
||||||
@@ -336,9 +362,11 @@ Group inbound payloads set:
|
|||||||
The agent system prompt includes a group intro on the first turn of a new group session. It reminds the model to respond like a human, avoid Markdown tables, and avoid typing literal `\n` sequences.
|
The agent system prompt includes a group intro on the first turn of a new group session. It reminds the model to respond like a human, avoid Markdown tables, and avoid typing literal `\n` sequences.
|
||||||
|
|
||||||
## iMessage specifics
|
## iMessage specifics
|
||||||
|
|
||||||
- Prefer `chat_id:<id>` when routing or allowlisting.
|
- Prefer `chat_id:<id>` when routing or allowlisting.
|
||||||
- List chats: `imsg chats --limit 20`.
|
- List chats: `imsg chats --limit 20`.
|
||||||
- Group replies always go back to the same `chat_id`.
|
- Group replies always go back to the same `chat_id`.
|
||||||
|
|
||||||
## WhatsApp specifics
|
## WhatsApp specifics
|
||||||
|
|
||||||
See [Group messages](/concepts/group-messages) for WhatsApp-only behavior (history injection, mention handling details).
|
See [Group messages](/concepts/group-messages) for WhatsApp-only behavior (history injection, mention handling details).
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ read_when:
|
|||||||
- You are adding a new channel formatter or style mapping
|
- You are adding a new channel formatter or style mapping
|
||||||
- You are debugging formatting regressions across channels
|
- You are debugging formatting regressions across channels
|
||||||
---
|
---
|
||||||
|
|
||||||
# Markdown formatting
|
# Markdown formatting
|
||||||
|
|
||||||
OpenClaw formats outbound Markdown by converting it into a shared intermediate
|
OpenClaw formats outbound Markdown by converting it into a shared intermediate
|
||||||
@@ -47,12 +48,8 @@ IR (schematic):
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"text": "Hello world — see docs.",
|
"text": "Hello world — see docs.",
|
||||||
"styles": [
|
"styles": [{ "start": 6, "end": 11, "style": "bold" }],
|
||||||
{ "start": 6, "end": 11, "style": "bold" }
|
"links": [{ "start": 19, "end": 23, "href": "https://docs.openclaw.ai" }]
|
||||||
],
|
|
||||||
"links": [
|
|
||||||
{ "start": 19, "end": 23, "href": "https://docs.openclaw.ai" }
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ read_when:
|
|||||||
- You want the memory file layout and workflow
|
- You want the memory file layout and workflow
|
||||||
- You want to tune the automatic pre-compaction memory flush
|
- You want to tune the automatic pre-compaction memory flush
|
||||||
---
|
---
|
||||||
|
|
||||||
# Memory
|
# Memory
|
||||||
|
|
||||||
OpenClaw memory is **plain Markdown in the agent workspace**. The files are the
|
OpenClaw memory is **plain Markdown in the agent workspace**. The files are the
|
||||||
@@ -38,7 +39,7 @@ These files live under the workspace (`agents.defaults.workspace`, default
|
|||||||
|
|
||||||
When a session is **close to auto-compaction**, OpenClaw triggers a **silent,
|
When a session is **close to auto-compaction**, OpenClaw triggers a **silent,
|
||||||
agentic turn** that reminds the model to write durable memory **before** the
|
agentic turn** that reminds the model to write durable memory **before** the
|
||||||
context is compacted. The default prompts explicitly say the model *may reply*,
|
context is compacted. The default prompts explicitly say the model _may reply_,
|
||||||
but usually `NO_REPLY` is the correct response so the user never sees this turn.
|
but usually `NO_REPLY` is the correct response so the user never sees this turn.
|
||||||
|
|
||||||
This is controlled by `agents.defaults.compaction.memoryFlush`:
|
This is controlled by `agents.defaults.compaction.memoryFlush`:
|
||||||
@@ -53,15 +54,16 @@ This is controlled by `agents.defaults.compaction.memoryFlush`:
|
|||||||
enabled: true,
|
enabled: true,
|
||||||
softThresholdTokens: 4000,
|
softThresholdTokens: 4000,
|
||||||
systemPrompt: "Session nearing compaction. Store durable memories now.",
|
systemPrompt: "Session nearing compaction. Store durable memories now.",
|
||||||
prompt: "Write any lasting notes to memory/YYYY-MM-DD.md; reply with NO_REPLY if nothing to store."
|
prompt: "Write any lasting notes to memory/YYYY-MM-DD.md; reply with NO_REPLY if nothing to store.",
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Details:
|
Details:
|
||||||
|
|
||||||
- **Soft threshold**: flush triggers when the session token estimate crosses
|
- **Soft threshold**: flush triggers when the session token estimate crosses
|
||||||
`contextWindow - reserveTokensFloor - softThresholdTokens`.
|
`contextWindow - reserveTokensFloor - softThresholdTokens`.
|
||||||
- **Silent** by default: prompts include `NO_REPLY` so nothing is delivered.
|
- **Silent** by default: prompts include `NO_REPLY` so nothing is delivered.
|
||||||
@@ -80,6 +82,7 @@ any extra directories or files you opt in) so semantic queries can find related
|
|||||||
notes even when wording differs.
|
notes even when wording differs.
|
||||||
|
|
||||||
Defaults:
|
Defaults:
|
||||||
|
|
||||||
- Enabled by default.
|
- Enabled by default.
|
||||||
- Watches memory files for changes (debounced).
|
- Watches memory files for changes (debounced).
|
||||||
- Uses remote embeddings by default. If `memorySearch.provider` is not set, OpenClaw auto-selects:
|
- Uses remote embeddings by default. If `memorySearch.provider` is not set, OpenClaw auto-selects:
|
||||||
@@ -113,6 +116,7 @@ agents: {
|
|||||||
```
|
```
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
- Paths can be absolute or workspace-relative.
|
- Paths can be absolute or workspace-relative.
|
||||||
- Directories are scanned recursively for `.md` files.
|
- Directories are scanned recursively for `.md` files.
|
||||||
- Only Markdown files are indexed.
|
- Only Markdown files are indexed.
|
||||||
@@ -137,6 +141,7 @@ agents: {
|
|||||||
```
|
```
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
- `remote.baseUrl` is optional (defaults to the Gemini API base URL).
|
- `remote.baseUrl` is optional (defaults to the Gemini API base URL).
|
||||||
- `remote.headers` lets you add extra headers if needed.
|
- `remote.headers` lets you add extra headers if needed.
|
||||||
- Default model: `gemini-embedding-001`.
|
- Default model: `gemini-embedding-001`.
|
||||||
@@ -164,10 +169,12 @@ If you don't want to set an API key, use `memorySearch.provider = "local"` or se
|
|||||||
`memorySearch.fallback = "none"`.
|
`memorySearch.fallback = "none"`.
|
||||||
|
|
||||||
Fallbacks:
|
Fallbacks:
|
||||||
|
|
||||||
- `memorySearch.fallback` can be `openai`, `gemini`, `local`, or `none`.
|
- `memorySearch.fallback` can be `openai`, `gemini`, `local`, or `none`.
|
||||||
- The fallback provider is only used when the primary embedding provider fails.
|
- The fallback provider is only used when the primary embedding provider fails.
|
||||||
|
|
||||||
Batch indexing (OpenAI + Gemini):
|
Batch indexing (OpenAI + Gemini):
|
||||||
|
|
||||||
- Enabled by default for OpenAI and Gemini embeddings. Set `agents.defaults.memorySearch.remote.batch.enabled = false` to disable.
|
- Enabled by default for OpenAI and Gemini embeddings. Set `agents.defaults.memorySearch.remote.batch.enabled = false` to disable.
|
||||||
- Default behavior waits for batch completion; tune `remote.batch.wait`, `remote.batch.pollIntervalMs`, and `remote.batch.timeoutMinutes` if needed.
|
- Default behavior waits for batch completion; tune `remote.batch.wait`, `remote.batch.pollIntervalMs`, and `remote.batch.timeoutMinutes` if needed.
|
||||||
- Set `remote.batch.concurrency` to control how many batch jobs we submit in parallel (default: 2).
|
- Set `remote.batch.concurrency` to control how many batch jobs we submit in parallel (default: 2).
|
||||||
@@ -175,6 +182,7 @@ Batch indexing (OpenAI + Gemini):
|
|||||||
- Gemini batch jobs use the async embeddings batch endpoint and require Gemini Batch API availability.
|
- Gemini batch jobs use the async embeddings batch endpoint and require Gemini Batch API availability.
|
||||||
|
|
||||||
Why OpenAI batch is fast + cheap:
|
Why OpenAI batch is fast + cheap:
|
||||||
|
|
||||||
- For large backfills, OpenAI is typically the fastest option we support because we can submit many embedding requests in a single batch job and let OpenAI process them asynchronously.
|
- For large backfills, OpenAI is typically the fastest option we support because we can submit many embedding requests in a single batch job and let OpenAI process them asynchronously.
|
||||||
- OpenAI offers discounted pricing for Batch API workloads, so large indexing runs are usually cheaper than sending the same requests synchronously.
|
- OpenAI offers discounted pricing for Batch API workloads, so large indexing runs are usually cheaper than sending the same requests synchronously.
|
||||||
- See the OpenAI Batch API docs and pricing for details:
|
- See the OpenAI Batch API docs and pricing for details:
|
||||||
@@ -200,10 +208,12 @@ agents: {
|
|||||||
```
|
```
|
||||||
|
|
||||||
Tools:
|
Tools:
|
||||||
|
|
||||||
- `memory_search` — returns snippets with file + line ranges.
|
- `memory_search` — returns snippets with file + line ranges.
|
||||||
- `memory_get` — read memory file content by path.
|
- `memory_get` — read memory file content by path.
|
||||||
|
|
||||||
Local mode:
|
Local mode:
|
||||||
|
|
||||||
- Set `agents.defaults.memorySearch.provider = "local"`.
|
- Set `agents.defaults.memorySearch.provider = "local"`.
|
||||||
- Provide `agents.defaults.memorySearch.local.modelPath` (GGUF or `hf:` URI).
|
- Provide `agents.defaults.memorySearch.local.modelPath` (GGUF or `hf:` URI).
|
||||||
- Optional: set `agents.defaults.memorySearch.fallback = "none"` to avoid remote fallback.
|
- Optional: set `agents.defaults.memorySearch.fallback = "none"` to avoid remote fallback.
|
||||||
@@ -224,6 +234,7 @@ Local mode:
|
|||||||
### Hybrid search (BM25 + vector)
|
### Hybrid search (BM25 + vector)
|
||||||
|
|
||||||
When enabled, OpenClaw combines:
|
When enabled, OpenClaw combines:
|
||||||
|
|
||||||
- **Vector similarity** (semantic match, wording can differ)
|
- **Vector similarity** (semantic match, wording can differ)
|
||||||
- **BM25 keyword relevance** (exact tokens like IDs, env vars, code symbols)
|
- **BM25 keyword relevance** (exact tokens like IDs, env vars, code symbols)
|
||||||
|
|
||||||
@@ -232,10 +243,12 @@ If full-text search is unavailable on your platform, OpenClaw falls back to vect
|
|||||||
#### Why hybrid?
|
#### Why hybrid?
|
||||||
|
|
||||||
Vector search is great at “this means the same thing”:
|
Vector search is great at “this means the same thing”:
|
||||||
|
|
||||||
- “Mac Studio gateway host” vs “the machine running the gateway”
|
- “Mac Studio gateway host” vs “the machine running the gateway”
|
||||||
- “debounce file updates” vs “avoid indexing on every write”
|
- “debounce file updates” vs “avoid indexing on every write”
|
||||||
|
|
||||||
But it can be weak at exact, high-signal tokens:
|
But it can be weak at exact, high-signal tokens:
|
||||||
|
|
||||||
- IDs (`a828e60`, `b3b9895a…`)
|
- IDs (`a828e60`, `b3b9895a…`)
|
||||||
- code symbols (`memorySearch.query.hybrid`)
|
- code symbols (`memorySearch.query.hybrid`)
|
||||||
- error strings (“sqlite-vec unavailable”)
|
- error strings (“sqlite-vec unavailable”)
|
||||||
@@ -248,17 +261,21 @@ good results for both “natural language” queries and “needle in a haystack
|
|||||||
|
|
||||||
Implementation sketch:
|
Implementation sketch:
|
||||||
|
|
||||||
1) Retrieve a candidate pool from both sides:
|
1. Retrieve a candidate pool from both sides:
|
||||||
|
|
||||||
- **Vector**: top `maxResults * candidateMultiplier` by cosine similarity.
|
- **Vector**: top `maxResults * candidateMultiplier` by cosine similarity.
|
||||||
- **BM25**: top `maxResults * candidateMultiplier` by FTS5 BM25 rank (lower is better).
|
- **BM25**: top `maxResults * candidateMultiplier` by FTS5 BM25 rank (lower is better).
|
||||||
|
|
||||||
2) Convert BM25 rank into a 0..1-ish score:
|
2. Convert BM25 rank into a 0..1-ish score:
|
||||||
|
|
||||||
- `textScore = 1 / (1 + max(0, bm25Rank))`
|
- `textScore = 1 / (1 + max(0, bm25Rank))`
|
||||||
|
|
||||||
3) Union candidates by chunk id and compute a weighted score:
|
3. Union candidates by chunk id and compute a weighted score:
|
||||||
|
|
||||||
- `finalScore = vectorWeight * vectorScore + textWeight * textScore`
|
- `finalScore = vectorWeight * vectorScore + textWeight * textScore`
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
- `vectorWeight` + `textWeight` is normalized to 1.0 in config resolution, so weights behave as percentages.
|
- `vectorWeight` + `textWeight` is normalized to 1.0 in config resolution, so weights behave as percentages.
|
||||||
- If embeddings are unavailable (or the provider returns a zero-vector), we still run BM25 and return keyword matches.
|
- If embeddings are unavailable (or the provider returns a zero-vector), we still run BM25 and return keyword matches.
|
||||||
- If FTS5 can’t be created, we keep vector-only search (no hard failure).
|
- If FTS5 can’t be created, we keep vector-only search (no hard failure).
|
||||||
@@ -322,6 +339,7 @@ agents: {
|
|||||||
```
|
```
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
- Session indexing is **opt-in** (off by default).
|
- Session indexing is **opt-in** (off by default).
|
||||||
- Session updates are debounced and **indexed asynchronously** once they cross delta thresholds (best-effort).
|
- Session updates are debounced and **indexed asynchronously** once they cross delta thresholds (best-effort).
|
||||||
- `memory_search` never blocks on indexing; results can be slightly stale until background sync finishes.
|
- `memory_search` never blocks on indexing; results can be slightly stale until background sync finishes.
|
||||||
@@ -370,6 +388,7 @@ agents: {
|
|||||||
```
|
```
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
- `enabled` defaults to true; when disabled, search falls back to in-process
|
- `enabled` defaults to true; when disabled, search falls back to in-process
|
||||||
cosine similarity over stored embeddings.
|
cosine similarity over stored embeddings.
|
||||||
- If the sqlite-vec extension is missing or fails to load, OpenClaw logs the
|
- If the sqlite-vec extension is missing or fails to load, OpenClaw logs the
|
||||||
@@ -406,5 +425,6 @@ agents: {
|
|||||||
```
|
```
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
|
|
||||||
- `remote.*` takes precedence over `models.providers.openai.*`.
|
- `remote.*` takes precedence over `models.providers.openai.*`.
|
||||||
- `remote.headers` merge with OpenAI headers; remote wins on key conflicts. Omit `remote.headers` to use the OpenAI defaults.
|
- `remote.headers` merge with OpenAI headers; remote wins on key conflicts. Omit `remote.headers` to use the OpenAI defaults.
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user