mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-28 18:31:39 +08:00
Compare commits
8 Commits
feat/qmd-e
...
codex/exec
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e1426b2a9 | ||
|
|
c625a3c306 | ||
|
|
330266545b | ||
|
|
e7e27b4994 | ||
|
|
454943a1a5 | ||
|
|
9d0d99c713 | ||
|
|
b0c746d726 | ||
|
|
614c42ac8e |
380
.agent/workflows/update_clawdbot.md
Normal file
380
.agent/workflows/update_clawdbot.md
Normal file
@@ -0,0 +1,380 @@
|
||||
---
|
||||
description: Update OpenClaw from upstream when branch has diverged (ahead/behind)
|
||||
---
|
||||
|
||||
# OpenClaw Upstream Sync Workflow
|
||||
|
||||
Use this workflow when your fork has diverged from upstream (e.g., "18 commits ahead, 29 commits behind").
|
||||
|
||||
## Quick Reference
|
||||
|
||||
```bash
|
||||
# Check divergence status
|
||||
git fetch upstream && git rev-list --left-right --count main...upstream/main
|
||||
|
||||
# Full sync (rebase preferred)
|
||||
git fetch upstream && git rebase upstream/main && pnpm install && pnpm build && ./scripts/restart-mac.sh
|
||||
|
||||
# Check for Swift 6.2 issues after sync
|
||||
grep -r "FileManager\.default\|Thread\.isMainThread" src/ apps/ --include="*.swift"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Assess Divergence
|
||||
|
||||
```bash
|
||||
git fetch upstream
|
||||
git log --oneline --left-right main...upstream/main | head -20
|
||||
```
|
||||
|
||||
This shows:
|
||||
|
||||
- `<` = your local commits (ahead)
|
||||
- `>` = upstream commits you're missing (behind)
|
||||
|
||||
**Decision point:**
|
||||
|
||||
- Few local commits, many upstream → **Rebase** (cleaner history)
|
||||
- Many local commits or shared branch → **Merge** (preserves history)
|
||||
|
||||
---
|
||||
|
||||
## Step 2A: Rebase Strategy (Preferred)
|
||||
|
||||
Replays your commits on top of upstream. Results in linear history.
|
||||
|
||||
```bash
|
||||
# Ensure working tree is clean
|
||||
git status
|
||||
|
||||
# Rebase onto upstream
|
||||
git rebase upstream/main
|
||||
```
|
||||
|
||||
### Handling Rebase Conflicts
|
||||
|
||||
```bash
|
||||
# When conflicts occur:
|
||||
# 1. Fix conflicts in the listed files
|
||||
# 2. Stage resolved files
|
||||
git add <resolved-files>
|
||||
|
||||
# 3. Continue rebase
|
||||
git rebase --continue
|
||||
|
||||
# If a commit is no longer needed (already in upstream):
|
||||
git rebase --skip
|
||||
|
||||
# To abort and return to original state:
|
||||
git rebase --abort
|
||||
```
|
||||
|
||||
### Common Conflict Patterns
|
||||
|
||||
| File | Resolution |
|
||||
| ---------------- | ------------------------------------------------ |
|
||||
| `package.json` | Take upstream deps, keep local scripts if needed |
|
||||
| `pnpm-lock.yaml` | Accept upstream, regenerate with `pnpm install` |
|
||||
| `*.patch` files | Usually take upstream version |
|
||||
| Source files | Merge logic carefully, prefer upstream structure |
|
||||
|
||||
---
|
||||
|
||||
## Step 2B: Merge Strategy (Alternative)
|
||||
|
||||
Preserves all history with a merge commit.
|
||||
|
||||
```bash
|
||||
git merge upstream/main --no-edit
|
||||
```
|
||||
|
||||
Resolve conflicts same as rebase, then:
|
||||
|
||||
```bash
|
||||
git add <resolved-files>
|
||||
git commit
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Rebuild Everything
|
||||
|
||||
After sync completes:
|
||||
|
||||
```bash
|
||||
# Install dependencies (regenerates lock if needed)
|
||||
pnpm install
|
||||
|
||||
# Build TypeScript
|
||||
pnpm build
|
||||
|
||||
# Build UI assets
|
||||
pnpm ui:build
|
||||
|
||||
# Run diagnostics
|
||||
pnpm clawdbot doctor
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Rebuild macOS App
|
||||
|
||||
```bash
|
||||
# Full rebuild, sign, and launch
|
||||
./scripts/restart-mac.sh
|
||||
|
||||
# Or just package without restart
|
||||
pnpm mac:package
|
||||
```
|
||||
|
||||
### Install to /Applications
|
||||
|
||||
```bash
|
||||
# Kill running app
|
||||
pkill -x "OpenClaw" || true
|
||||
|
||||
# Move old version
|
||||
mv /Applications/OpenClaw.app /tmp/OpenClaw-backup.app
|
||||
|
||||
# Install new build
|
||||
cp -R dist/OpenClaw.app /Applications/
|
||||
|
||||
# Launch
|
||||
open /Applications/OpenClaw.app
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 4A: Verify macOS App & Agent
|
||||
|
||||
After rebuilding the macOS app, always verify it works correctly:
|
||||
|
||||
```bash
|
||||
# Check gateway health
|
||||
pnpm clawdbot health
|
||||
|
||||
# Verify no zombie processes
|
||||
ps aux | grep -E "(clawdbot|gateway)" | grep -v grep
|
||||
|
||||
# Test agent functionality by sending a verification message
|
||||
pnpm clawdbot agent --message "Verification: macOS app rebuild successful - agent is responding." --session-id YOUR_TELEGRAM_SESSION_ID
|
||||
|
||||
# Confirm the message was received on Telegram
|
||||
# (Check your Telegram chat with the bot)
|
||||
```
|
||||
|
||||
**Important:** Always wait for the Telegram verification message before proceeding. If the agent doesn't respond, troubleshoot the gateway or model configuration before pushing.
|
||||
|
||||
---
|
||||
|
||||
## Step 5: Handle Swift/macOS Build Issues (Common After Upstream Sync)
|
||||
|
||||
Upstream updates may introduce Swift 6.2 / macOS 26 SDK incompatibilities. Use analyze-mode for systematic debugging:
|
||||
|
||||
### Analyze-Mode Investigation
|
||||
|
||||
```bash
|
||||
# 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="Locate Peekaboo submodule and macOS app Swift files with concurrency issues" repo_path="/Volumes/Main SSD/Developer/clawdis"
|
||||
```
|
||||
|
||||
### Common Swift 6.2 Fixes
|
||||
|
||||
**FileManager.default Deprecation:**
|
||||
|
||||
```bash
|
||||
# Search for deprecated usage
|
||||
grep -r "FileManager\.default" src/ apps/ --include="*.swift"
|
||||
|
||||
# Replace with proper initialization
|
||||
# OLD: FileManager.default
|
||||
# NEW: FileManager()
|
||||
```
|
||||
|
||||
**Thread.isMainThread Deprecation:**
|
||||
|
||||
```bash
|
||||
# Search for deprecated usage
|
||||
grep -r "Thread\.isMainThread" src/ apps/ --include="*.swift"
|
||||
|
||||
# Replace with modern concurrency check
|
||||
# OLD: Thread.isMainThread
|
||||
# NEW: await MainActor.run { ... } or DispatchQueue.main.sync { ... }
|
||||
```
|
||||
|
||||
### Peekaboo Submodule Fixes
|
||||
|
||||
```bash
|
||||
# Check Peekaboo for concurrency issues
|
||||
cd src/canvas-host/a2ui
|
||||
grep -r "Thread\.isMainThread\|FileManager\.default" . --include="*.swift"
|
||||
|
||||
# Fix and rebuild submodule
|
||||
cd /Volumes/Main SSD/Developer/clawdis
|
||||
pnpm canvas:a2ui:bundle
|
||||
```
|
||||
|
||||
### macOS App Concurrency Fixes
|
||||
|
||||
```bash
|
||||
# Check macOS app for issues
|
||||
grep -r "Thread\.isMainThread\|FileManager\.default" apps/macos/ --include="*.swift"
|
||||
|
||||
# Clean and rebuild after fixes
|
||||
cd apps/macos && rm -rf .build .swiftpm
|
||||
./scripts/restart-mac.sh
|
||||
```
|
||||
|
||||
### Model Configuration Updates
|
||||
|
||||
If upstream introduced new model configurations:
|
||||
|
||||
```bash
|
||||
# Check for OpenRouter API key requirements
|
||||
grep -r "openrouter\|OPENROUTER" src/ --include="*.ts" --include="*.js"
|
||||
|
||||
# Update openclaw.json with fallback chains
|
||||
# Add model fallback configurations as needed
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 6: Verify & Push
|
||||
|
||||
```bash
|
||||
# Verify everything works
|
||||
pnpm clawdbot health
|
||||
pnpm test
|
||||
|
||||
# Push (force required after rebase)
|
||||
git push origin main --force-with-lease
|
||||
|
||||
# Or regular push after merge
|
||||
git push origin main
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Build Fails After Sync
|
||||
|
||||
```bash
|
||||
# Clean and rebuild
|
||||
rm -rf node_modules dist
|
||||
pnpm install
|
||||
pnpm build
|
||||
```
|
||||
|
||||
### Type Errors (Bun/Node Incompatibility)
|
||||
|
||||
Common issue: `fetch.preconnect` type mismatch. Fix by using `FetchLike` type instead of `typeof fetch`.
|
||||
|
||||
### macOS App Crashes on Launch
|
||||
|
||||
Usually resource bundle mismatch. Full rebuild required:
|
||||
|
||||
```bash
|
||||
cd apps/macos && rm -rf .build .swiftpm
|
||||
./scripts/restart-mac.sh
|
||||
```
|
||||
|
||||
### Patch Failures
|
||||
|
||||
```bash
|
||||
# Check patch status
|
||||
pnpm install 2>&1 | grep -i patch
|
||||
|
||||
# If patches fail, they may need updating for new dep versions
|
||||
# Check patches/ directory against package.json patchedDependencies
|
||||
```
|
||||
|
||||
### Swift 6.2 / macOS 26 SDK Build Failures
|
||||
|
||||
**Symptoms:** Build fails with deprecation warnings about `FileManager.default` or `Thread.isMainThread`
|
||||
|
||||
**Search-Mode Investigation:**
|
||||
|
||||
```bash
|
||||
# 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"
|
||||
```
|
||||
|
||||
**Quick Fix Commands:**
|
||||
|
||||
```bash
|
||||
# Find all affected files
|
||||
find . -name "*.swift" -exec grep -l "FileManager\.default\|Thread\.isMainThread" {} \;
|
||||
|
||||
# Replace FileManager.default with FileManager()
|
||||
find . -name "*.swift" -exec sed -i '' 's/FileManager\.default/FileManager()/g' {} \;
|
||||
|
||||
# For Thread.isMainThread, need manual review of each usage
|
||||
grep -rn "Thread\.isMainThread" --include="*.swift" .
|
||||
```
|
||||
|
||||
**Rebuild After Fixes:**
|
||||
|
||||
```bash
|
||||
# Clean all build artifacts
|
||||
rm -rf apps/macos/.build apps/macos/.swiftpm
|
||||
rm -rf src/canvas-host/a2ui/.build
|
||||
|
||||
# Rebuild Peekaboo bundle
|
||||
pnpm canvas:a2ui:bundle
|
||||
|
||||
# Full macOS rebuild
|
||||
./scripts/restart-mac.sh
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Automation Script
|
||||
|
||||
Save as `scripts/sync-upstream.sh`:
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
echo "==> Fetching upstream..."
|
||||
git fetch upstream
|
||||
|
||||
echo "==> Current divergence:"
|
||||
git rev-list --left-right --count main...upstream/main
|
||||
|
||||
echo "==> Rebasing onto upstream/main..."
|
||||
git rebase upstream/main
|
||||
|
||||
echo "==> Installing dependencies..."
|
||||
pnpm install
|
||||
|
||||
echo "==> Building..."
|
||||
pnpm build
|
||||
pnpm ui:build
|
||||
|
||||
echo "==> Running doctor..."
|
||||
pnpm clawdbot doctor
|
||||
|
||||
echo "==> Rebuilding macOS app..."
|
||||
./scripts/restart-mac.sh
|
||||
|
||||
echo "==> Verifying gateway health..."
|
||||
pnpm clawdbot health
|
||||
|
||||
echo "==> Checking for Swift 6.2 compatibility issues..."
|
||||
if grep -r "FileManager\.default\|Thread\.isMainThread" src/ apps/ --include="*.swift" --quiet; then
|
||||
echo "⚠️ Found potential Swift 6.2 deprecated API usage"
|
||||
echo " Run manual fixes or use analyze-mode investigation"
|
||||
else
|
||||
echo "✅ No obvious Swift deprecation issues found"
|
||||
fi
|
||||
|
||||
echo "==> Testing agent functionality..."
|
||||
# Note: Update YOUR_TELEGRAM_SESSION_ID with actual session ID
|
||||
pnpm clawdbot agent --message "Verification: Upstream sync and macOS rebuild completed successfully." --session-id YOUR_TELEGRAM_SESSION_ID || echo "Warning: Agent test failed - check Telegram for verification message"
|
||||
|
||||
echo "==> Done! Check Telegram for verification message, then run 'git push --force-with-lease' when ready."
|
||||
```
|
||||
@@ -1,11 +1,11 @@
|
||||
---
|
||||
name: openclaw-test-heap-leaks
|
||||
description: Investigate `pnpm test` memory growth, Vitest worker OOMs, and suspicious RSS increases in OpenClaw using the `scripts/test-parallel.mjs` heap snapshot tooling. Use when Codex needs to reproduce test-lane memory growth, collect repeated `.heapsnapshot` files, compare snapshots from the same worker PID, triage likely transformed-module retention versus likely runtime leaks, and fix or reduce the impact by patching cleanup logic or isolating hotspot tests.
|
||||
description: Investigate `pnpm test` memory growth, Vitest worker OOMs, and suspicious RSS increases in OpenClaw using the `scripts/test-parallel.mjs` heap snapshot tooling. Use when Codex needs to reproduce test-lane memory growth, collect repeated `.heapsnapshot` files, compare snapshots from the same worker PID, distinguish transformed-module retention from real data leaks, and fix or reduce the impact by patching cleanup logic or isolating hotspot tests.
|
||||
---
|
||||
|
||||
# OpenClaw Test Heap Leaks
|
||||
|
||||
Use this skill for test-memory investigations. Do not guess from RSS alone when heap snapshots are available. Treat snapshot-name deltas as triage evidence, not proof, until retainers or dominators support the call.
|
||||
Use this skill for test-memory investigations. Do not guess from RSS alone when heap snapshots are available.
|
||||
|
||||
## Workflow
|
||||
|
||||
@@ -14,23 +14,19 @@ Use this skill for test-memory investigations. Do not guess from RSS alone when
|
||||
- `pnpm canvas:a2ui:bundle && OPENCLAW_TEST_MEMORY_TRACE=1 OPENCLAW_TEST_HEAPSNAPSHOT_INTERVAL_MS=60000 OPENCLAW_TEST_HEAPSNAPSHOT_DIR=.tmp/heapsnap OPENCLAW_TEST_WORKERS=2 OPENCLAW_TEST_MAX_OLD_SPACE_SIZE_MB=6144 pnpm test`
|
||||
- Keep `OPENCLAW_TEST_MEMORY_TRACE=1` enabled so the wrapper prints per-file RSS summaries alongside the snapshots.
|
||||
- If the report is about a specific shard or worker budget, preserve that shape.
|
||||
- Before you analyze snapshots, identify the real lane names from `[test-parallel] start ...` lines or `pnpm test --plan`. Do not assume a single `unit-fast` lane; local plans often split into `unit-fast-batch-*`.
|
||||
|
||||
2. Wait for repeated snapshots before concluding anything.
|
||||
- Take at least two intervals from the same lane.
|
||||
- Compare snapshots from the same PID inside the real lane directory such as `.tmp/heapsnap/unit-fast-batch-2/`.
|
||||
- Use `.agents/skills/openclaw-test-heap-leaks/scripts/heapsnapshot-delta.mjs` to compare either two files directly or the earliest/latest pair per PID in one lane directory.
|
||||
- If the helper suggests transformed-module retention, confirm the top entries in DevTools retainers/dominators before calling it solved.
|
||||
- Compare snapshots from the same PID inside one lane directory such as `.tmp/heapsnap/unit-fast/`.
|
||||
- Use `scripts/heapsnapshot-delta.mjs` to compare either two files directly or the earliest/latest pair per PID in one lane directory.
|
||||
|
||||
3. Classify the growth before choosing a fix.
|
||||
- If growth is dominated by Vite/Vitest transformed source strings, `Module`, `system / Context`, bytecode, descriptor arrays, or property maps, treat it as likely retained module graph growth in long-lived workers.
|
||||
- If growth is dominated by Vite/Vitest transformed source strings, `Module`, `system / Context`, bytecode, descriptor arrays, or property maps, treat it as retained module graph growth in long-lived workers.
|
||||
- If growth is dominated by app objects, caches, buffers, server handles, timers, mock state, sqlite state, or similar runtime objects, treat it as a likely cleanup or lifecycle leak.
|
||||
- If the names are ambiguous, stop short of a confident label and inspect retainers/dominators in DevTools for the top deltas.
|
||||
|
||||
4. Fix the right layer.
|
||||
- For likely retained transformed-module growth in shared workers:
|
||||
- Prefer timing and hotspot-driven scheduling fixes first. Check whether the file is already represented in `test/fixtures/test-timings.unit.json` and whether `scripts/test-update-memory-hotspots.mjs` should refresh the measured hotspot manifest before hand-editing behavior overrides.
|
||||
- Move hotspot files out of the real shared lane by updating `test/fixtures/test-parallel.behavior.json` only when timing-driven peeling is insufficient.
|
||||
- For retained transformed-module growth in shared workers:
|
||||
- Move hotspot files out of `unit-fast` by updating `test/fixtures/test-parallel.behavior.json`.
|
||||
- Prefer `singletonIsolated` for files that are safe alone but inflate shared worker heaps.
|
||||
- If the file should already have been peeled out by timings but is absent from `test/fixtures/test-timings.unit.json`, call that out explicitly. Missing timings are a scheduling blind spot.
|
||||
- For real leaks:
|
||||
@@ -44,24 +40,24 @@ Use this skill for test-memory investigations. Do not guess from RSS alone when
|
||||
|
||||
## Heuristics
|
||||
|
||||
- Do not call everything a leak. In this repo, large `unit-fast` or `unit-fast-batch-*` growth can be a worker-lifetime problem rather than an application object leak.
|
||||
- Do not call everything a leak. In this repo, large `unit-fast` growth can be a worker-lifetime problem rather than an application object leak.
|
||||
- `scripts/test-parallel.mjs` and `scripts/test-parallel-memory.mjs` are the primary control points for wrapper diagnostics.
|
||||
- The lane names printed by `[test-parallel] start ...` and `[test-parallel][mem] summary ...` tell you where to focus.
|
||||
- When one or two files account for most of the delta and they are missing from timings, reducing impact by isolating them is usually the first pragmatic fix.
|
||||
- When the same retained object families grow across multiple intervals in the same worker PID, trust the snapshots over intuition, then confirm ambiguous calls with retainer evidence.
|
||||
- When the same retained object families grow across multiple intervals in the same worker PID, trust the snapshots over intuition.
|
||||
|
||||
## Snapshot Comparison
|
||||
|
||||
- Direct comparison:
|
||||
- `node .agents/skills/openclaw-test-heap-leaks/scripts/heapsnapshot-delta.mjs before.heapsnapshot after.heapsnapshot`
|
||||
- Auto-select earliest/latest snapshots per PID within one lane:
|
||||
- `node .agents/skills/openclaw-test-heap-leaks/scripts/heapsnapshot-delta.mjs --lane-dir .tmp/heapsnap/unit-fast-batch-2`
|
||||
- `node .agents/skills/openclaw-test-heap-leaks/scripts/heapsnapshot-delta.mjs --lane-dir .tmp/heapsnap/unit-fast`
|
||||
- Useful flags:
|
||||
- `--top 40`
|
||||
- `--min-kb 32`
|
||||
- `--pid 16133`
|
||||
|
||||
Read the top positive deltas first. Large positive growth in module-transform artifacts suggests lane isolation; large positive growth in runtime objects suggests a real leak. If the names alone do not settle it, open the same snapshot pair in DevTools and inspect retainers/dominators for the top rows before declaring root cause.
|
||||
Read the top positive deltas first. Large positive growth in module-transform artifacts suggests lane isolation; large positive growth in runtime objects suggests a real leak.
|
||||
|
||||
## Output Expectations
|
||||
|
||||
@@ -70,6 +66,6 @@ When using this skill, report:
|
||||
- The exact reproduce command.
|
||||
- Which lane and PID were compared.
|
||||
- The dominant retained object families from the snapshot delta.
|
||||
- Whether the issue is a likely real leak or likely shared-worker retained module growth, plus whether retainers/dominators confirmed it.
|
||||
- Whether the issue is a real leak or shared-worker retained module growth.
|
||||
- The concrete fix or impact-reduction patch.
|
||||
- What you verified, and what snapshot overhead prevented you from verifying.
|
||||
|
||||
@@ -64,243 +64,6 @@ function parseArgs(argv) {
|
||||
return options;
|
||||
}
|
||||
|
||||
class JsonStreamScanner {
|
||||
constructor(filePath) {
|
||||
this.stream = fs.createReadStream(filePath, {
|
||||
encoding: "utf8",
|
||||
highWaterMark: 1024 * 1024,
|
||||
});
|
||||
this.iterator = this.stream[Symbol.asyncIterator]();
|
||||
this.buffer = "";
|
||||
this.offset = 0;
|
||||
this.done = false;
|
||||
}
|
||||
|
||||
compactBuffer() {
|
||||
if (this.offset > 65536) {
|
||||
this.buffer = this.buffer.slice(this.offset);
|
||||
this.offset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
async ensureAvailable(count = 1) {
|
||||
while (!this.done && this.buffer.length - this.offset < count) {
|
||||
const next = await this.iterator.next();
|
||||
if (next.done) {
|
||||
this.done = true;
|
||||
break;
|
||||
}
|
||||
this.buffer += next.value;
|
||||
}
|
||||
}
|
||||
|
||||
async peek() {
|
||||
await this.ensureAvailable(1);
|
||||
return this.buffer[this.offset] ?? null;
|
||||
}
|
||||
|
||||
async next() {
|
||||
await this.ensureAvailable(1);
|
||||
if (this.offset >= this.buffer.length) {
|
||||
return null;
|
||||
}
|
||||
const char = this.buffer[this.offset];
|
||||
this.offset += 1;
|
||||
this.compactBuffer();
|
||||
return char;
|
||||
}
|
||||
|
||||
async skipWhitespace() {
|
||||
while (true) {
|
||||
const char = await this.peek();
|
||||
if (char === null || !/\s/u.test(char)) {
|
||||
return;
|
||||
}
|
||||
await this.next();
|
||||
}
|
||||
}
|
||||
|
||||
async expectChar(expected) {
|
||||
const char = await this.next();
|
||||
if (char !== expected) {
|
||||
fail(`Expected ${expected} but found ${char ?? "<eof>"}`);
|
||||
}
|
||||
}
|
||||
|
||||
async find(sequence) {
|
||||
let matched = 0;
|
||||
while (true) {
|
||||
const char = await this.next();
|
||||
if (char === null) {
|
||||
fail(`Could not find ${sequence}`);
|
||||
}
|
||||
if (char === sequence[matched]) {
|
||||
matched += 1;
|
||||
if (matched === sequence.length) {
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
matched = char === sequence[0] ? 1 : 0;
|
||||
if (matched === sequence.length) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async readBalancedObject() {
|
||||
const start = await this.next();
|
||||
if (start !== "{") {
|
||||
fail(`Expected { but found ${start ?? "<eof>"}`);
|
||||
}
|
||||
let text = "{";
|
||||
let depth = 1;
|
||||
let inString = false;
|
||||
let escaped = false;
|
||||
while (depth > 0) {
|
||||
const char = await this.next();
|
||||
if (char === null) {
|
||||
fail("Unexpected EOF while reading JSON object");
|
||||
}
|
||||
text += char;
|
||||
if (inString) {
|
||||
if (escaped) {
|
||||
escaped = false;
|
||||
} else if (char === "\\") {
|
||||
escaped = true;
|
||||
} else if (char === '"') {
|
||||
inString = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (char === '"') {
|
||||
inString = true;
|
||||
} else if (char === "{") {
|
||||
depth += 1;
|
||||
} else if (char === "}") {
|
||||
depth -= 1;
|
||||
}
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
async parseNumberArray(onValue) {
|
||||
await this.skipWhitespace();
|
||||
await this.expectChar("[");
|
||||
await this.skipWhitespace();
|
||||
if ((await this.peek()) === "]") {
|
||||
await this.next();
|
||||
return;
|
||||
}
|
||||
|
||||
let token = "";
|
||||
let index = 0;
|
||||
const flush = () => {
|
||||
if (token.length === 0) {
|
||||
fail("Unexpected empty number token");
|
||||
}
|
||||
const value = Number.parseInt(token, 10);
|
||||
if (!Number.isFinite(value)) {
|
||||
fail(`Invalid numeric token: ${token}`);
|
||||
}
|
||||
onValue(value, index);
|
||||
index += 1;
|
||||
token = "";
|
||||
};
|
||||
|
||||
while (true) {
|
||||
const char = await this.next();
|
||||
if (char === null) {
|
||||
fail("Unexpected EOF while reading number array");
|
||||
}
|
||||
if (char === "]") {
|
||||
flush();
|
||||
return;
|
||||
}
|
||||
if (char === ",") {
|
||||
flush();
|
||||
continue;
|
||||
}
|
||||
if (/\s/u.test(char)) {
|
||||
continue;
|
||||
}
|
||||
token += char;
|
||||
}
|
||||
}
|
||||
|
||||
async readJsonString() {
|
||||
await this.expectChar('"');
|
||||
let value = "";
|
||||
while (true) {
|
||||
const char = await this.next();
|
||||
if (char === null) {
|
||||
fail("Unexpected EOF while reading JSON string");
|
||||
}
|
||||
if (char === '"') {
|
||||
return value;
|
||||
}
|
||||
if (char !== "\\") {
|
||||
value += char;
|
||||
continue;
|
||||
}
|
||||
const escaped = await this.next();
|
||||
if (escaped === null) {
|
||||
fail("Unexpected EOF while reading JSON string escape");
|
||||
}
|
||||
if (escaped === "u") {
|
||||
let hex = "";
|
||||
for (let index = 0; index < 4; index += 1) {
|
||||
const hexChar = await this.next();
|
||||
if (hexChar === null) {
|
||||
fail("Unexpected EOF while reading JSON unicode escape");
|
||||
}
|
||||
hex += hexChar;
|
||||
}
|
||||
value += String.fromCharCode(Number.parseInt(hex, 16));
|
||||
continue;
|
||||
}
|
||||
value +=
|
||||
escaped === "b"
|
||||
? "\b"
|
||||
: escaped === "f"
|
||||
? "\f"
|
||||
: escaped === "n"
|
||||
? "\n"
|
||||
: escaped === "r"
|
||||
? "\r"
|
||||
: escaped === "t"
|
||||
? "\t"
|
||||
: escaped;
|
||||
}
|
||||
}
|
||||
|
||||
async parseStringArray(onValue) {
|
||||
await this.skipWhitespace();
|
||||
await this.expectChar("[");
|
||||
await this.skipWhitespace();
|
||||
if ((await this.peek()) === "]") {
|
||||
await this.next();
|
||||
return;
|
||||
}
|
||||
|
||||
let index = 0;
|
||||
while (true) {
|
||||
const value = await this.readJsonString();
|
||||
onValue(value, index);
|
||||
index += 1;
|
||||
await this.skipWhitespace();
|
||||
const separator = await this.next();
|
||||
if (separator === "]") {
|
||||
return;
|
||||
}
|
||||
if (separator !== ",") {
|
||||
fail(`Expected , or ] but found ${separator ?? "<eof>"}`);
|
||||
}
|
||||
await this.skipWhitespace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function parseHeapFilename(filePath) {
|
||||
const base = path.basename(filePath);
|
||||
const match = base.match(
|
||||
@@ -388,89 +151,38 @@ function resolvePair(options) {
|
||||
};
|
||||
}
|
||||
|
||||
async function parseSnapshotMeta(scanner) {
|
||||
await scanner.find('"snapshot":');
|
||||
await scanner.skipWhitespace();
|
||||
const metaObjectText = await scanner.readBalancedObject();
|
||||
const parsed = JSON.parse(metaObjectText);
|
||||
return parsed?.meta ?? null;
|
||||
}
|
||||
|
||||
async function buildSummary(filePath) {
|
||||
const scanner = new JsonStreamScanner(filePath);
|
||||
const meta = await parseSnapshotMeta(scanner);
|
||||
function loadSummary(filePath) {
|
||||
const data = JSON.parse(fs.readFileSync(filePath, "utf8"));
|
||||
const meta = data.snapshot?.meta;
|
||||
if (!meta) {
|
||||
fail(`Invalid heap snapshot: ${filePath}`);
|
||||
}
|
||||
|
||||
const nodeFieldCount = meta.node_fields.length;
|
||||
const typeNames = meta.node_types[0];
|
||||
const strings = data.strings;
|
||||
const typeIndex = meta.node_fields.indexOf("type");
|
||||
const nameIndex = meta.node_fields.indexOf("name");
|
||||
const selfSizeIndex = meta.node_fields.indexOf("self_size");
|
||||
if (typeIndex === -1 || nameIndex === -1 || selfSizeIndex === -1) {
|
||||
fail(`Unsupported heap snapshot schema: ${filePath}`);
|
||||
}
|
||||
|
||||
const summaryByIndex = new Map();
|
||||
let nodeCount = 0;
|
||||
let currentTypeId = 0;
|
||||
let currentNameId = 0;
|
||||
let currentSelfSize = 0;
|
||||
await scanner.find('"nodes":');
|
||||
await scanner.parseNumberArray((value, index) => {
|
||||
const fieldIndex = index % nodeFieldCount;
|
||||
if (fieldIndex === typeIndex) {
|
||||
currentTypeId = value;
|
||||
return;
|
||||
}
|
||||
if (fieldIndex === nameIndex) {
|
||||
currentNameId = value;
|
||||
return;
|
||||
}
|
||||
if (fieldIndex === selfSizeIndex) {
|
||||
currentSelfSize = value;
|
||||
}
|
||||
if (fieldIndex !== nodeFieldCount - 1) {
|
||||
return;
|
||||
}
|
||||
const key = `${currentTypeId}\t${currentNameId}`;
|
||||
const current = summaryByIndex.get(key) ?? {
|
||||
typeId: currentTypeId,
|
||||
nameId: currentNameId,
|
||||
const summary = new Map();
|
||||
for (let offset = 0; offset < data.nodes.length; offset += nodeFieldCount) {
|
||||
const type = typeNames[data.nodes[offset + typeIndex]];
|
||||
const name = strings[data.nodes[offset + nameIndex]];
|
||||
const selfSize = data.nodes[offset + selfSizeIndex];
|
||||
const key = `${type}\t${name}`;
|
||||
const current = summary.get(key) ?? {
|
||||
type,
|
||||
name,
|
||||
selfSize: 0,
|
||||
count: 0,
|
||||
};
|
||||
current.selfSize += currentSelfSize;
|
||||
current.selfSize += selfSize;
|
||||
current.count += 1;
|
||||
summaryByIndex.set(key, current);
|
||||
nodeCount += 1;
|
||||
});
|
||||
|
||||
const requiredNameIds = new Set(
|
||||
Array.from(summaryByIndex.values(), (entry) => entry.nameId).filter((value) => value >= 0),
|
||||
);
|
||||
const nameStrings = new Map();
|
||||
await scanner.find('"strings":');
|
||||
await scanner.parseStringArray((value, index) => {
|
||||
if (requiredNameIds.has(index)) {
|
||||
nameStrings.set(index, value);
|
||||
}
|
||||
});
|
||||
|
||||
const summary = new Map();
|
||||
for (const entry of summaryByIndex.values()) {
|
||||
const key = `${typeNames[entry.typeId] ?? "unknown"}\t${nameStrings.get(entry.nameId) ?? ""}`;
|
||||
summary.set(key, {
|
||||
type: typeNames[entry.typeId] ?? "unknown",
|
||||
name: nameStrings.get(entry.nameId) ?? "",
|
||||
selfSize: entry.selfSize,
|
||||
count: entry.count,
|
||||
});
|
||||
summary.set(key, current);
|
||||
}
|
||||
|
||||
return {
|
||||
nodeCount,
|
||||
nodeCount: data.snapshot.node_count,
|
||||
summary,
|
||||
};
|
||||
}
|
||||
@@ -493,11 +205,11 @@ function truncate(text, maxLength) {
|
||||
return text.length <= maxLength ? text : `${text.slice(0, maxLength - 1)}…`;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
function main() {
|
||||
const options = parseArgs(process.argv.slice(2));
|
||||
const pair = resolvePair(options);
|
||||
const before = await buildSummary(pair.before);
|
||||
const after = await buildSummary(pair.after);
|
||||
const before = loadSummary(pair.before);
|
||||
const after = loadSummary(pair.after);
|
||||
const minBytes = options.minKb * 1024;
|
||||
|
||||
const rows = [];
|
||||
@@ -550,4 +262,4 @@ async function main() {
|
||||
}
|
||||
}
|
||||
|
||||
await main();
|
||||
main();
|
||||
|
||||
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
@@ -302,17 +302,11 @@ jobs:
|
||||
- name: Run ${{ matrix.task }} (${{ matrix.runtime }})
|
||||
env:
|
||||
TASK: ${{ matrix.task }}
|
||||
SHARD_COUNT: ${{ matrix.shard_count || '' }}
|
||||
SHARD_INDEX: ${{ matrix.shard_index || '' }}
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
case "$TASK" in
|
||||
extensions)
|
||||
if [ -n "$SHARD_COUNT" ] && [ -n "$SHARD_INDEX" ]; then
|
||||
export OPENCLAW_TEST_SHARDS="$SHARD_COUNT"
|
||||
export OPENCLAW_TEST_SHARD_INDEX="$SHARD_INDEX"
|
||||
fi
|
||||
pnpm test:extensions
|
||||
;;
|
||||
contracts|contracts-protocol)
|
||||
@@ -465,8 +459,6 @@ jobs:
|
||||
use-sticky-disk: "false"
|
||||
|
||||
- name: Check types and lint and oxfmt
|
||||
env:
|
||||
OPENCLAW_LOCAL_CHECK: "0"
|
||||
run: pnpm check
|
||||
|
||||
- name: Strict TS build smoke
|
||||
|
||||
2
.github/workflows/openclaw-npm-release.yml
vendored
2
.github/workflows/openclaw-npm-release.yml
vendored
@@ -71,8 +71,6 @@ jobs:
|
||||
echo "Publishing openclaw@${PACKAGE_VERSION}"
|
||||
|
||||
- name: Check
|
||||
env:
|
||||
OPENCLAW_LOCAL_CHECK: "0"
|
||||
run: pnpm check
|
||||
|
||||
- name: Build
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
{
|
||||
"globs": ["docs/**/*.md", "docs/**/*.mdx", "README.md"],
|
||||
"ignores": ["docs/zh-CN/**", "docs/.i18n/**", "docs/reference/templates/**", "**/.local/**"],
|
||||
"ignores": [
|
||||
"docs/zh-CN/**",
|
||||
"docs/.i18n/**",
|
||||
"docs/reference/templates/**",
|
||||
"**/.local/**"
|
||||
],
|
||||
"config": {
|
||||
"default": true,
|
||||
|
||||
|
||||
@@ -112,7 +112,6 @@
|
||||
- Type-check/build: `pnpm build`
|
||||
- TypeScript checks: `pnpm tsgo`
|
||||
- Lint/format: `pnpm check`
|
||||
- Local agent/dev shells default to lower-memory `OPENCLAW_LOCAL_CHECK=1` behavior for `pnpm tsgo` and `pnpm lint`; set `OPENCLAW_LOCAL_CHECK=0` in CI/shared runs.
|
||||
- Format check: `pnpm format` (oxfmt --check)
|
||||
- Format fix: `pnpm format:fix` (oxfmt --write)
|
||||
- Terminology:
|
||||
|
||||
33
CHANGELOG.md
33
CHANGELOG.md
@@ -22,13 +22,6 @@ Docs: https://docs.openclaw.ai
|
||||
- Android/notifications: add notification-forwarding controls with package filtering, quiet hours, rate limiting, and safer picker behavior for forwarded notification events. (#40175) Thanks @nimbleenigma.
|
||||
- Matrix/network: add explicit `channels.matrix.proxy` config for routing Matrix traffic through an HTTP(S) proxy, including account-level overrides and matching probe/runtime behavior. (#56931) thanks @patrick-yingxi-pan.
|
||||
- Background tasks: turn tasks into a real shared background-run control plane instead of ACP-only bookkeeping by unifying ACP, subagent, cron, and background CLI execution under one SQLite-backed ledger, routing detached lifecycle updates through the executor seam, adding audit/maintenance/status visibility, tightening auto-cleanup and lost-run recovery, improving task awareness in internal status/tool surfaces, and clarifying the split between heartbeat/main-session automation and detached scheduled runs. Thanks @vincentkoc and @mbelinky.
|
||||
- Flows/tasks: add a minimal SQLite-backed flow registry plus task-to-flow linkage scaffolding, so orchestrated work can start gaining a first-class parent record without changing current task delivery behavior.
|
||||
- Flows/tasks: route one-task ACP and subagent updates through a parent flow owner context, so detached work can emerge back through the intended parent thread/session instead of speaking only as a raw child task.
|
||||
- Matrix/history: add optional room history context for Matrix group triggers via `channels.matrix.historyLimit`, with per-agent watermarks and retry-safe snapshots so failed trigger retries do not drift into newer room messages. (#57022) thanks @chain710.
|
||||
- Diffs: skip unused viewer-versus-file SSR preload work so `diffs` view-only and file-only runs do less render work while keeping mode outputs aligned. (#57909) thanks @gumadeiras.
|
||||
- Matrix/threads: add per-DM `threadReplies` overrides and keep thread session isolation aligned with the effective room or DM thread policy from the triggering message onward. (#57995) thanks @teconomix.
|
||||
- TTS: Add structured provider diagnostics and fallback attempt analytics. (#57954) Thanks @joshavant.
|
||||
- Memory/QMD: add per-agent `memorySearch.qmd.extraCollections` so agents can opt into cross-agent session search without flattening every transcript collection into one shared QMD namespace. Thanks @vincentkoc.
|
||||
|
||||
### Fixes
|
||||
|
||||
@@ -44,7 +37,6 @@ Docs: https://docs.openclaw.ai
|
||||
- Memory/QMD: point `QMD_CONFIG_DIR` at the nested `xdg-config/qmd` directory so per-agent collection config resolves correctly. (#39078) Thanks @smart-tinker and @vincentkoc.
|
||||
- Memory/QMD: include deduplicated default plus per-agent `memorySearch.extraPaths` when building QMD custom collections, so shared and agent-specific extra roots both get indexed consistently. (#57315) Thanks @Vitalcheffe and @vincentkoc.
|
||||
- Memory/session indexer: include `.jsonl.reset.*` and `.jsonl.deleted.*` transcripts in the memory host session scan while still excluding `.jsonl.bak.*` compaction backups and lock files, so memory search sees archived session history without duplicating stale snapshots. Thanks @hclsys and @vincentkoc.
|
||||
- CI/dev checks: default local `pnpm check` to a lower-memory typecheck/lint path while keeping CI on the normal parallel path, and harden Telegram test typing/literals around native TypeScript-Go tooling crashes.
|
||||
- Agents/sandbox: honor `tools.sandbox.tools.alsoAllow`, let explicit sandbox re-allows remove matching built-in default-deny tools, and keep sandbox explain/error guidance aligned with the effective sandbox tool policy. (#54492) Thanks @ngutman.
|
||||
- LINE/ACP: add current-conversation binding and inbound binding-routing parity so `/acp spawn ... --thread here`, configured ACP bindings, and active conversation-bound ACP sessions work on LINE like the other conversation channels.
|
||||
- LINE/markdown: preserve underscores inside Latin, Cyrillic, and CJK words when stripping markdown, while still removing standalone `_italic_` markers on the shared text-runtime path used by LINE and TTS. (#47465) Thanks @jackjin1997.
|
||||
@@ -70,7 +62,6 @@ Docs: https://docs.openclaw.ai
|
||||
- Agents/memory flush: keep daily memory flush files append-only during embedded attempts so compaction writes do not overwrite earlier notes. (#53725) Thanks @HPluseven.
|
||||
- Web UI/markdown: stop bare auto-links from swallowing adjacent CJK text while preserving valid mixed-script path and query characters in rendered links. (#48410) Thanks @jnuyao.
|
||||
- BlueBubbles/iMessage: coalesce URL-only inbound messages with their link-preview balloon again so sharing a bare link no longer drops the URL from agent context. Thanks @vincentkoc.
|
||||
- Telegram/media: allow RFC 2544 benchmark-range Telegram CDN resolutions during media downloads, so voice messages, PDFs, and other attachments no longer fail with `Failed to download media`. (#57624) Thanks @MoerAI.
|
||||
- Sandbox/browser: install `fonts-noto-cjk` in the sandbox browser image so screenshots render Chinese, Japanese, and Korean text correctly instead of tofu boxes. Fixes #35597. Thanks @carrotRakko and @vincentkoc.
|
||||
- Memory/FTS: add configurable trigram tokenization plus short-CJK substring fallback so memory search can find Chinese, Japanese, and Korean text without breaking mixed long-and-short queries. Thanks @carrotRakko.
|
||||
- Hooks/config: accept runtime channel plugin ids in `hooks.mappings[].channel` (for example `feishu`) instead of rejecting non-core channels during config validation. (#56226) Thanks @AiKrai001.
|
||||
@@ -110,28 +101,6 @@ Docs: https://docs.openclaw.ai
|
||||
- Matrix/delivery recovery: treat Synapse `User not in room` replay failures as permanent during startup recovery so poisoned queued messages move to `failed/` instead of crash-looping Matrix after restart. (#57426) thanks @dlardo.
|
||||
- Plugins/facades: guard bundled plugin facade loads with a cache-first sentinel so circular re-entry stops crashing `xai`, `sglang`, and `vllm` during gateway plugin startup. (#57508) Thanks @openperf.
|
||||
- Agents/MCP: dispose bundled MCP runtimes after one-shot `openclaw agent --local` runs finish, while preserving bundled MCP state across in-run retries so local JSON runs exit cleanly without restarting stateful MCP tools mid-run.
|
||||
- Gateway/auth: keep shared-auth rate limiting active during WebSocket handshake attempts even when callers also send device-token candidates, so bogus device-token fields no longer suppress shared-secret brute-force tracking. Thanks @kexinoh and @vincentkoc.
|
||||
- Heartbeat/auth: prevent exec-event heartbeat runs from inheriting owner-only tool access from the session delivery target, so node exec output stays on the non-owner tool surface even when the target session belongs to the owner. Thanks @AntAISecurityLab and @vincentkoc.
|
||||
- Gateway/device tokens: disconnect active device sessions after token rotation so newly rotated credentials revoke existing live connections immediately instead of waiting for those sockets to close naturally. Thanks @zsxsoft and @vincentkoc.
|
||||
- Gateway/OpenAI HTTP: restore default operator scopes for bearer-authenticated requests that omit `x-openclaw-scopes`, so headless `/v1/chat/completions` and session-history callers work again after the recent method-scope hardening. (#57596) Thanks @openperf.
|
||||
- Gateway/attachments: offload large inbound images without leaking `media://` markers into text-only runs, preserve mixed attachment order for model input/transcripts, and fail closed when model image capability cannot be resolved. (#55513) Thanks @Syysean.
|
||||
- Agents/subagents: fix interim subagent runtime display so `/subagents list` and `/subagents info` stop inflating short runtimes and show second-level durations correctly. (#57739) Thanks @samzong.
|
||||
- Diffs/config: preserve schema-shaped plugin config parsing from `diffsPluginConfigSchema.safeParse()`, so direct callers keep `defaults` and `security` sections instead of receiving flattened tool defaults. (#57904) Thanks @gumadeiras.
|
||||
- Diffs: fall back to plain text when `lang` hints are invalid during diff render and viewer hydration, so bad or stale language values no longer break the diff viewer. (#57902) Thanks @gumadeiras.
|
||||
- Doctor/plugins: skip false Matrix legacy-helper warnings when no migration plans exist, and keep bundled `enabledByDefault` plugins in the gateway startup set. (#57931) Thanks @dinakars777.
|
||||
- Matrix/CLI send: start one-off Matrix send clients before outbound delivery so `openclaw message send --channel matrix` restores E2EE in encrypted rooms instead of sending plain events. (#57936) Thanks @gumadeiras.
|
||||
- xAI/Responses: normalize image-bearing tool results for xAI responses payloads, including OpenResponses-style `input_image.source` parts, so image tool replays no longer 422 on the follow-up turn. (#58017) Thanks @neeravmakwana.
|
||||
- Cron/isolated sessions: carry the full live-session provider, model, and auth-profile selection across retry restarts so cron jobs with model overrides no longer fail or loop on mid-run model-switch requests. (#57972) Thanks @issaba1.
|
||||
- Matrix/direct rooms: stop trusting remote `is_direct`, honor explicit local `is_direct: false` for discovered DM candidates, and avoid extra member-state lookups for shared rooms so DM routing and repair stay aligned. (#57124) Thanks @w-sss.
|
||||
- Agents/sandbox: make remote FS bridge reads pin the parent path and open the file atomically in the helper so read access cannot race path resolution. Thanks @AntAISecurityLab and @vincentkoc.
|
||||
- Tools/web_fetch: add an explicit trusted env-proxy path for proxy-only installs while keeping strict SSRF fetches on the pinned direct path, so trusted proxy routing does not weaken strict destination binding. (#50650) Thanks @kkav004.
|
||||
- Exec/env: block Python package index override variables from request-scoped host exec environment sanitization so package fetches cannot be redirected through a caller-supplied index. Thanks @nexrin and @vincentkoc.
|
||||
- Telegram/audio: transcode Telegram voice-note `.ogg` attachments before the local `whisper-cli` auto fallback runs, and keep mention-preflight transcription enabled in auto mode when `tools.media.audio` is unset.
|
||||
- Matrix/direct rooms: recover fresh auto-joined 1:1 DMs without eagerly persisting invite-only `m.direct` mappings, while keeping named, aliased, and explicitly configured rooms on the room path. (#58024) Thanks @gumadeiras.
|
||||
- TTS: Restore 3.28 schema compatibility and fallback observability. (#57953) Thanks @joshavant.
|
||||
- Telegram/forum topics: restore reply routing to the active topic and keep ACP `sessions_spawn(..., thread=true, mode="session")` bound to that same topic instead of falling back to root chat or losing follow-up routing. (#56060) Thanks @one27001.
|
||||
- Config/SecretRef + Control UI: harden SecretRef redaction round-trip restore, block unsafe raw fallback (force Form mode when raw is unavailable), and preflight submitted-config SecretRefs before config write RPC persistence. (#58044) Thanks @joshavant.
|
||||
- Config/Telegram: migrate removed `channels.telegram.groupMentionsOnly` into `channels.telegram.groups["*"].requireMention` on load so legacy configs no longer crash at startup. (#55336) thanks @jameslcowan.
|
||||
|
||||
## 2026.3.28
|
||||
|
||||
@@ -348,7 +317,6 @@ Docs: https://docs.openclaw.ai
|
||||
- Runtime/install: lower the supported Node 22 floor to `22.14+` while continuing to recommend Node 24, so npm installs and self-updates do not strand Node 22.14 users on older releases.
|
||||
- CLI/update: preflight the target npm package `engines.node` before `openclaw update` runs a global package install, so outdated Node runtimes fail with a clear upgrade message instead of attempting an unsupported latest release.
|
||||
- Tests/security audit: isolate audit-test home and personal skill resolution so local `~/.agents/skills` installs no longer make maintainer prep runs fail nondeterministically. (#54473) thanks @huntharo
|
||||
- Memory/QMD: preserve explicit custom collection names for shared paths outside the agent workspace so `memory_search` stops appending `-<agentId>` to externally managed QMD collections. (#52539) Thanks @lobsrice and @vincentkoc.
|
||||
|
||||
## 2026.3.24-beta.1
|
||||
|
||||
@@ -730,7 +698,6 @@ Docs: https://docs.openclaw.ai
|
||||
- Z.AI/onboarding: add `glm-5-turbo` to the default Z.AI provider catalog so onboarding-generated configs expose the new model alongside the existing GLM defaults. (#46670) Thanks @tomsun28.
|
||||
- Zalo Personal/group gating: stop reapplying `dmPolicy.allowFrom` as a sender gate for already-allowlisted groups when `groupAllowFrom` is unset, so any member of an allowed group can trigger replies while DMs stay restricted. (#46663) Fixes #40146. Thanks @Takhoffman.
|
||||
- Zalo/plugin runtime: export `resolveClientIp` from `openclaw/plugin-sdk/zalo` so installed builds no longer crash on startup when the webhook monitor loads from the packaged extension instead of the monorepo source tree. (#46549) Thanks @No898.
|
||||
- Onboarding/custom providers: usage of pi-ai's specialized "azure-openai-responses" implementation for onboarded Azure OpenAI endpoints. (#50851) Thanks @kunalk16.
|
||||
- Docker/live tests: mount external CLI auth homes into writable container copies, derive Codex OAuth expiry from JWT `exp`, refresh synced CLI creds instead of trusting stale cached expiry, and make gateway live probes wait on transcript output so `pnpm test:docker:all` stays green in Linux.
|
||||
- Gateway/watch mode: restart on bundled-plugin package and manifest metadata changes, rebuild `dist` for extension source and `tsdown.config.ts` changes, and still ignore extension docs. (#47571) Thanks @gumadeiras.
|
||||
- Gateway/watch mode: recreate bundled plugin runtime metadata after clean or stale `dist` states, so `pnpm gateway:watch` no longer fails on missing bundled-plugin manifests after a rebuild. Thanks @gumadeiras.
|
||||
|
||||
@@ -261,22 +261,6 @@ class MainViewModel(app: Application) : AndroidViewModel(app) {
|
||||
ensureRuntime().connect(endpoint)
|
||||
}
|
||||
|
||||
fun connect(
|
||||
endpoint: GatewayEndpoint,
|
||||
token: String?,
|
||||
bootstrapToken: String?,
|
||||
password: String?,
|
||||
) {
|
||||
ensureRuntime().connect(
|
||||
endpoint,
|
||||
NodeRuntime.GatewayConnectAuth(
|
||||
token = token,
|
||||
bootstrapToken = bootstrapToken,
|
||||
password = password,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun connectManual() {
|
||||
ensureRuntime().connectManual()
|
||||
}
|
||||
|
||||
@@ -45,12 +45,6 @@ class NodeRuntime(
|
||||
context: Context,
|
||||
val prefs: SecurePrefs = SecurePrefs(context.applicationContext),
|
||||
) {
|
||||
data class GatewayConnectAuth(
|
||||
val token: String?,
|
||||
val bootstrapToken: String?,
|
||||
val password: String?,
|
||||
)
|
||||
|
||||
private val appContext = context.applicationContext
|
||||
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
||||
private val deviceAuthStore = DeviceAuthStore(prefs)
|
||||
@@ -781,51 +775,28 @@ class NodeRuntime(
|
||||
}
|
||||
operatorStatusText = "Connecting…"
|
||||
updateStatus()
|
||||
connectWithAuth(endpoint = endpoint, auth = resolveGatewayConnectAuth(), reconnect = true)
|
||||
}
|
||||
|
||||
private fun connectWithAuth(
|
||||
endpoint: GatewayEndpoint,
|
||||
auth: GatewayConnectAuth,
|
||||
reconnect: Boolean = false,
|
||||
) {
|
||||
val token = prefs.loadGatewayToken()
|
||||
val bootstrapToken = prefs.loadGatewayBootstrapToken()
|
||||
val password = prefs.loadGatewayPassword()
|
||||
val tls = connectionManager.resolveTlsParams(endpoint)
|
||||
val connectOperator =
|
||||
shouldConnectOperatorSession(
|
||||
auth.token,
|
||||
auth.bootstrapToken,
|
||||
auth.password,
|
||||
loadStoredRoleDeviceToken("operator"),
|
||||
)
|
||||
if (!connectOperator) {
|
||||
operatorConnected = false
|
||||
operatorStatusText = "Offline"
|
||||
operatorSession.disconnect()
|
||||
updateStatus()
|
||||
} else {
|
||||
operatorSession.connect(
|
||||
endpoint,
|
||||
auth.token,
|
||||
auth.bootstrapToken,
|
||||
auth.password,
|
||||
connectionManager.buildOperatorConnectOptions(),
|
||||
tls,
|
||||
)
|
||||
}
|
||||
operatorSession.connect(
|
||||
endpoint,
|
||||
token,
|
||||
bootstrapToken,
|
||||
password,
|
||||
connectionManager.buildOperatorConnectOptions(),
|
||||
tls,
|
||||
)
|
||||
nodeSession.connect(
|
||||
endpoint,
|
||||
auth.token,
|
||||
auth.bootstrapToken,
|
||||
auth.password,
|
||||
token,
|
||||
bootstrapToken,
|
||||
password,
|
||||
connectionManager.buildNodeConnectOptions(),
|
||||
tls,
|
||||
)
|
||||
if (reconnect && connectOperator) {
|
||||
operatorSession.reconnect()
|
||||
}
|
||||
if (reconnect) {
|
||||
nodeSession.reconnect()
|
||||
}
|
||||
operatorSession.reconnect()
|
||||
nodeSession.reconnect()
|
||||
}
|
||||
|
||||
fun connect(endpoint: GatewayEndpoint) {
|
||||
@@ -847,27 +818,25 @@ class NodeRuntime(
|
||||
operatorStatusText = "Connecting…"
|
||||
nodeStatusText = "Connecting…"
|
||||
updateStatus()
|
||||
connectWithAuth(endpoint = endpoint, auth = resolveGatewayConnectAuth())
|
||||
}
|
||||
|
||||
fun connect(
|
||||
endpoint: GatewayEndpoint,
|
||||
auth: GatewayConnectAuth,
|
||||
) {
|
||||
connectedEndpoint = endpoint
|
||||
operatorStatusText = "Connecting…"
|
||||
nodeStatusText = "Connecting…"
|
||||
updateStatus()
|
||||
connectWithAuth(endpoint = endpoint, auth = resolveGatewayConnectAuth(auth))
|
||||
}
|
||||
|
||||
internal fun resolveGatewayConnectAuth(explicitAuth: GatewayConnectAuth? = null): GatewayConnectAuth {
|
||||
return explicitAuth
|
||||
?: GatewayConnectAuth(
|
||||
token = prefs.loadGatewayToken(),
|
||||
bootstrapToken = prefs.loadGatewayBootstrapToken(),
|
||||
password = prefs.loadGatewayPassword(),
|
||||
)
|
||||
val token = prefs.loadGatewayToken()
|
||||
val bootstrapToken = prefs.loadGatewayBootstrapToken()
|
||||
val password = prefs.loadGatewayPassword()
|
||||
operatorSession.connect(
|
||||
endpoint,
|
||||
token,
|
||||
bootstrapToken,
|
||||
password,
|
||||
connectionManager.buildOperatorConnectOptions(),
|
||||
tls,
|
||||
)
|
||||
nodeSession.connect(
|
||||
endpoint,
|
||||
token,
|
||||
bootstrapToken,
|
||||
password,
|
||||
connectionManager.buildNodeConnectOptions(),
|
||||
tls,
|
||||
)
|
||||
}
|
||||
|
||||
fun acceptGatewayTrustPrompt() {
|
||||
@@ -899,11 +868,6 @@ class NodeRuntime(
|
||||
connect(GatewayEndpoint.manual(host = host, port = port))
|
||||
}
|
||||
|
||||
private fun loadStoredRoleDeviceToken(role: String): String? {
|
||||
val deviceId = identityStore.loadOrCreate().deviceId
|
||||
return deviceAuthStore.loadToken(deviceId, role)
|
||||
}
|
||||
|
||||
fun disconnect() {
|
||||
connectedEndpoint = null
|
||||
_pendingGatewayTrust.value = null
|
||||
@@ -1233,20 +1197,6 @@ class NodeRuntime(
|
||||
|
||||
}
|
||||
|
||||
internal fun shouldConnectOperatorSession(
|
||||
token: String?,
|
||||
bootstrapToken: String?,
|
||||
password: String?,
|
||||
storedOperatorToken: String?,
|
||||
): Boolean {
|
||||
return (
|
||||
!token.isNullOrBlank() ||
|
||||
!bootstrapToken.isNullOrBlank() ||
|
||||
!password.isNullOrBlank() ||
|
||||
!storedOperatorToken.isNullOrBlank()
|
||||
)
|
||||
}
|
||||
|
||||
private enum class HomeCanvasGatewayState {
|
||||
Connected,
|
||||
Connecting,
|
||||
|
||||
@@ -53,7 +53,6 @@ import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.unit.dp
|
||||
import ai.openclaw.app.MainViewModel
|
||||
import ai.openclaw.app.gateway.GatewayEndpoint
|
||||
import ai.openclaw.app.ui.mobileCardSurface
|
||||
|
||||
private enum class ConnectInputMode {
|
||||
@@ -270,12 +269,7 @@ fun ConnectTabScreen(viewModel: MainViewModel) {
|
||||
viewModel.setGatewayToken("")
|
||||
}
|
||||
viewModel.setGatewayPassword(config.password)
|
||||
viewModel.connect(
|
||||
GatewayEndpoint.manual(host = config.host, port = config.port),
|
||||
token = config.token.ifEmpty { null },
|
||||
bootstrapToken = config.bootstrapToken.ifEmpty { null },
|
||||
password = config.password.ifEmpty { null },
|
||||
)
|
||||
viewModel.connectManual()
|
||||
},
|
||||
modifier = Modifier.fillMaxWidth().height(52.dp),
|
||||
shape = RoundedCornerShape(14.dp),
|
||||
|
||||
@@ -96,7 +96,6 @@ import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||
import ai.openclaw.app.BuildConfig
|
||||
import ai.openclaw.app.LocationMode
|
||||
import ai.openclaw.app.MainViewModel
|
||||
import ai.openclaw.app.gateway.GatewayEndpoint
|
||||
import ai.openclaw.app.node.DeviceNotificationListenerService
|
||||
import com.google.mlkit.vision.barcode.common.Barcode
|
||||
import com.google.mlkit.vision.codescanner.GmsBarcodeScannerOptions
|
||||
@@ -212,7 +211,6 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) {
|
||||
val context = androidx.compose.ui.platform.LocalContext.current
|
||||
val statusText by viewModel.statusText.collectAsState()
|
||||
val isConnected by viewModel.isConnected.collectAsState()
|
||||
val isNodeConnected by viewModel.isNodeConnected.collectAsState()
|
||||
val serverName by viewModel.serverName.collectAsState()
|
||||
val remoteAddress by viewModel.remoteAddress.collectAsState()
|
||||
val persistedGatewayToken by viewModel.gatewayToken.collectAsState()
|
||||
@@ -229,8 +227,6 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) {
|
||||
var manualTls by rememberSaveable { mutableStateOf(false) }
|
||||
var gatewayError by rememberSaveable { mutableStateOf<String?>(null) }
|
||||
var attemptedConnect by rememberSaveable { mutableStateOf(false) }
|
||||
val canFinishOnboarding =
|
||||
isConnected || (gatewayInputMode == GatewayInputMode.SetupCode && isNodeConnected)
|
||||
|
||||
val lifecycleOwner = LocalLifecycleOwner.current
|
||||
val qrScannerOptions =
|
||||
@@ -736,7 +732,7 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) {
|
||||
FinalStep(
|
||||
parsedGateway = parseGatewayEndpoint(gatewayUrl),
|
||||
statusText = statusText,
|
||||
isConnected = canFinishOnboarding,
|
||||
isConnected = isConnected,
|
||||
serverName = serverName,
|
||||
remoteAddress = remoteAddress,
|
||||
attemptedConnect = attemptedConnect,
|
||||
@@ -852,7 +848,7 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) {
|
||||
}
|
||||
}
|
||||
OnboardingStep.FinalCheck -> {
|
||||
if (canFinishOnboarding) {
|
||||
if (isConnected) {
|
||||
Button(
|
||||
onClick = { viewModel.setOnboardingCompleted(true) },
|
||||
modifier = Modifier.weight(1f).height(52.dp),
|
||||
@@ -886,17 +882,7 @@ fun OnboardingFlow(viewModel: MainViewModel, modifier: Modifier = Modifier) {
|
||||
viewModel.setGatewayToken("")
|
||||
}
|
||||
viewModel.setGatewayPassword(password)
|
||||
viewModel.connect(
|
||||
GatewayEndpoint.manual(host = parsed.host, port = parsed.port),
|
||||
token = token.ifEmpty { null },
|
||||
bootstrapToken =
|
||||
if (gatewayInputMode == GatewayInputMode.SetupCode) {
|
||||
decodeGatewaySetupCode(setupCode)?.bootstrapToken?.trim()?.ifEmpty { null }
|
||||
} else {
|
||||
null
|
||||
},
|
||||
password = password.ifEmpty { null },
|
||||
)
|
||||
viewModel.connectManual()
|
||||
},
|
||||
modifier = Modifier.weight(1f).height(52.dp),
|
||||
shape = RoundedCornerShape(14.dp),
|
||||
@@ -1691,22 +1677,21 @@ private fun FinalStep(
|
||||
)
|
||||
}
|
||||
}
|
||||
Text("Status", style = onboardingCaption1Style.copy(fontWeight = FontWeight.Bold), color = onboardingTextSecondary)
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
color = onboardingCommandBg,
|
||||
border = BorderStroke(1.dp, onboardingCommandBorder),
|
||||
) {
|
||||
Text(
|
||||
statusLabel,
|
||||
modifier = Modifier.padding(horizontal = 14.dp, vertical = 12.dp),
|
||||
style = onboardingCalloutStyle.copy(fontFamily = FontFamily.Monospace),
|
||||
color = onboardingCommandText,
|
||||
)
|
||||
}
|
||||
if (showDiagnostics) {
|
||||
Text("Error", style = onboardingCaption1Style.copy(fontWeight = FontWeight.Bold), color = onboardingTextSecondary)
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
shape = RoundedCornerShape(12.dp),
|
||||
color = onboardingCommandBg,
|
||||
border = BorderStroke(1.dp, onboardingCommandBorder),
|
||||
) {
|
||||
Text(
|
||||
statusLabel,
|
||||
modifier = Modifier.padding(horizontal = 14.dp, vertical = 12.dp),
|
||||
style = onboardingCalloutStyle.copy(fontFamily = FontFamily.Monospace),
|
||||
color = onboardingCommandText,
|
||||
)
|
||||
}
|
||||
Text(
|
||||
"OpenClaw Android ${openClawAndroidVersionLabel()}",
|
||||
style = onboardingCaption1Style,
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
package ai.openclaw.app
|
||||
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Assert.assertFalse
|
||||
import org.junit.Assert.assertNull
|
||||
import org.junit.Assert.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import org.robolectric.RuntimeEnvironment
|
||||
import org.robolectric.annotation.Config
|
||||
import java.util.UUID
|
||||
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
@Config(sdk = [34])
|
||||
class GatewayBootstrapAuthTest {
|
||||
@Test
|
||||
fun connectsOperatorSessionWhenBootstrapAuthExists() {
|
||||
assertTrue(shouldConnectOperatorSession(token = "", bootstrapToken = "bootstrap-1", password = "", storedOperatorToken = ""))
|
||||
assertTrue(shouldConnectOperatorSession(token = null, bootstrapToken = "bootstrap-1", password = null, storedOperatorToken = null))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun skipsOperatorSessionOnlyWhenNoSharedBootstrapOrStoredAuthExists() {
|
||||
assertTrue(shouldConnectOperatorSession(token = "shared-token", bootstrapToken = "bootstrap-1", password = null, storedOperatorToken = null))
|
||||
assertTrue(shouldConnectOperatorSession(token = null, bootstrapToken = "bootstrap-1", password = "shared-password", storedOperatorToken = null))
|
||||
assertTrue(shouldConnectOperatorSession(token = null, bootstrapToken = null, password = null, storedOperatorToken = "stored-token"))
|
||||
assertFalse(shouldConnectOperatorSession(token = null, bootstrapToken = "", password = null, storedOperatorToken = null))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun resolveGatewayConnectAuth_prefersExplicitSetupAuthOverStoredPrefs() {
|
||||
val app = RuntimeEnvironment.getApplication()
|
||||
val securePrefs =
|
||||
app.getSharedPreferences(
|
||||
"openclaw.node.secure.test.${UUID.randomUUID()}",
|
||||
android.content.Context.MODE_PRIVATE,
|
||||
)
|
||||
val prefs = SecurePrefs(app, securePrefsOverride = securePrefs)
|
||||
prefs.setGatewayToken("stale-shared-token")
|
||||
prefs.setGatewayBootstrapToken("")
|
||||
prefs.setGatewayPassword("stale-password")
|
||||
val runtime = NodeRuntime(app, prefs)
|
||||
|
||||
val auth =
|
||||
runtime.resolveGatewayConnectAuth(
|
||||
NodeRuntime.GatewayConnectAuth(
|
||||
token = null,
|
||||
bootstrapToken = "setup-bootstrap-token",
|
||||
password = null,
|
||||
),
|
||||
)
|
||||
|
||||
assertNull(auth.token)
|
||||
assertEquals("setup-bootstrap-token", auth.bootstrapToken)
|
||||
assertNull(auth.password)
|
||||
}
|
||||
}
|
||||
@@ -16,17 +16,9 @@ enum HostEnvSecurityPolicy {
|
||||
"RUBYOPT",
|
||||
"BASH_ENV",
|
||||
"ENV",
|
||||
"BROWSER",
|
||||
"GIT_EDITOR",
|
||||
"GIT_EXTERNAL_DIFF",
|
||||
"GIT_EXEC_PATH",
|
||||
"GIT_SEQUENCE_EDITOR",
|
||||
"GIT_TEMPLATE_DIR",
|
||||
"CC",
|
||||
"CXX",
|
||||
"CARGO_BUILD_RUSTC",
|
||||
"CMAKE_C_COMPILER",
|
||||
"CMAKE_CXX_COMPILER",
|
||||
"SHELL",
|
||||
"SHELLOPTS",
|
||||
"PS4",
|
||||
@@ -82,13 +74,6 @@ enum HostEnvSecurityPolicy {
|
||||
"PHP_INI_SCAN_DIR",
|
||||
"DENO_DIR",
|
||||
"BUN_CONFIG_REGISTRY",
|
||||
"PIP_INDEX_URL",
|
||||
"PIP_PYPI_URL",
|
||||
"PIP_EXTRA_INDEX_URL",
|
||||
"UV_INDEX",
|
||||
"UV_INDEX_URL",
|
||||
"UV_EXTRA_INDEX_URL",
|
||||
"UV_DEFAULT_INDEX",
|
||||
"LUA_PATH",
|
||||
"LUA_CPATH",
|
||||
"GEM_HOME",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,4 +1,4 @@
|
||||
{"generatedBy":"scripts/generate-config-doc-baseline.ts","recordType":"meta","totalPaths":5625}
|
||||
{"generatedBy":"scripts/generate-config-doc-baseline.ts","recordType":"meta","totalPaths":5593}
|
||||
{"recordType":"path","path":"acp","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"ACP","help":"ACP runtime controls for enabling dispatch, selecting backends, constraining allowed agent targets, and tuning streamed turn projection behavior.","hasChildren":true}
|
||||
{"recordType":"path","path":"acp.allowedAgents","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"ACP Allowed Agents","help":"Allowlist of ACP target agent ids permitted for ACP runtime sessions. Empty means no additional allowlist restriction.","hasChildren":true}
|
||||
{"recordType":"path","path":"acp.allowedAgents.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
@@ -159,8 +159,6 @@
|
||||
{"recordType":"path","path":"agents.defaults.imageModel.fallbacks","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["media","models","reliability"],"label":"Image Model Fallbacks","help":"Ordered fallback image models (provider/model).","hasChildren":true}
|
||||
{"recordType":"path","path":"agents.defaults.imageModel.fallbacks.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"agents.defaults.imageModel.primary","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["media","models"],"label":"Image Model","help":"Optional image model (provider/model) used when the primary model lacks image input.","hasChildren":false}
|
||||
{"recordType":"path","path":"agents.defaults.llm","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"agents.defaults.llm.idleTimeoutSeconds","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"agents.defaults.maxConcurrent","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"agents.defaults.mediaMaxMb","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"agents.defaults.memorySearch","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Memory Search","help":"Vector search over MEMORY.md and memory/*.md (per-agent overrides supported).","hasChildren":true}
|
||||
@@ -206,7 +204,7 @@
|
||||
{"recordType":"path","path":"agents.defaults.memorySearch.remote.apiKey.id","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"agents.defaults.memorySearch.remote.apiKey.provider","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"agents.defaults.memorySearch.remote.apiKey.source","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"agents.defaults.memorySearch.remote.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","url-secret"],"label":"Remote Embedding Base URL","help":"Overrides the embedding API endpoint, such as an OpenAI-compatible proxy or custom Gemini base URL. Use this only when routing through your own gateway or vendor endpoint; keep provider defaults otherwise.","hasChildren":false}
|
||||
{"recordType":"path","path":"agents.defaults.memorySearch.remote.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Remote Embedding Base URL","help":"Overrides the embedding API endpoint, such as an OpenAI-compatible proxy or custom Gemini base URL. Use this only when routing through your own gateway or vendor endpoint; keep provider defaults otherwise.","hasChildren":false}
|
||||
{"recordType":"path","path":"agents.defaults.memorySearch.remote.batch","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"agents.defaults.memorySearch.remote.batch.concurrency","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance"],"label":"Remote Batch Concurrency","help":"Limits how many embedding batch jobs run at the same time during indexing (default: 2). Increase carefully for faster bulk indexing, but watch provider rate limits and queue errors.","hasChildren":false}
|
||||
{"recordType":"path","path":"agents.defaults.memorySearch.remote.batch.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Remote Batch Embedding Enabled","help":"Enables provider batch APIs for embedding jobs when supported (OpenAI/Gemini), improving throughput on larger index runs. Keep this enabled unless debugging provider batch failures or running very small workloads.","hasChildren":false}
|
||||
@@ -344,7 +342,6 @@
|
||||
{"recordType":"path","path":"agents.defaults.subagents.model.fallbacks","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"agents.defaults.subagents.model.fallbacks.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"agents.defaults.subagents.model.primary","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"agents.defaults.subagents.requireAgentId","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"agents.defaults.subagents.runTimeoutSeconds","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"agents.defaults.subagents.thinking","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"agents.defaults.thinkingDefault","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
@@ -435,7 +432,7 @@
|
||||
{"recordType":"path","path":"agents.list.*.memorySearch.remote.apiKey.id","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"agents.list.*.memorySearch.remote.apiKey.provider","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"agents.list.*.memorySearch.remote.apiKey.source","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"agents.list.*.memorySearch.remote.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","url-secret"],"hasChildren":false}
|
||||
{"recordType":"path","path":"agents.list.*.memorySearch.remote.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"agents.list.*.memorySearch.remote.batch","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"agents.list.*.memorySearch.remote.batch.concurrency","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"agents.list.*.memorySearch.remote.batch.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
@@ -569,7 +566,6 @@
|
||||
{"recordType":"path","path":"agents.list.*.subagents.model.fallbacks","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"agents.list.*.subagents.model.fallbacks.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"agents.list.*.subagents.model.primary","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"agents.list.*.subagents.requireAgentId","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"agents.list.*.subagents.thinking","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"agents.list.*.thinkingDefault","kind":"core","type":"string","required":false,"enumValues":["off","minimal","low","medium","high","xhigh","adaptive"],"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Agent Thinking Default","help":"Optional per-agent default thinking level. Overrides agents.defaults.thinkingDefault for this agent when no per-message or session override is set.","hasChildren":false}
|
||||
{"recordType":"path","path":"agents.list.*.tools","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
@@ -603,7 +599,7 @@
|
||||
{"recordType":"path","path":"agents.list.*.tools.exec.ask","kind":"core","type":"string","required":false,"enumValues":["off","on-miss","always"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"agents.list.*.tools.exec.backgroundMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"agents.list.*.tools.exec.cleanupMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"agents.list.*.tools.exec.host","kind":"core","type":"string","required":false,"enumValues":["auto","sandbox","gateway","node"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"agents.list.*.tools.exec.host","kind":"core","type":"string","required":false,"enumValues":["sandbox","gateway","node"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"agents.list.*.tools.exec.node","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"agents.list.*.tools.exec.notifyOnExit","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"agents.list.*.tools.exec.notifyOnExitEmptySuccess","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
@@ -1997,7 +1993,6 @@
|
||||
{"recordType":"path","path":"channels.matrix.groups.*.tools.deny.*","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.matrix.groups.*.users","kind":"channel","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"channels.matrix.groups.*.users.*","kind":"channel","type":["number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.matrix.historyLimit","kind":"channel","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.matrix.homeserver","kind":"channel","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.matrix.initialSyncLimit","kind":"channel","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"channels.matrix.markdown","kind":"channel","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
@@ -3652,7 +3647,7 @@
|
||||
{"recordType":"path","path":"gateway.push","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["network"],"label":"Gateway Push Delivery","help":"Push-delivery settings used by the gateway when it needs to wake or notify paired devices. Configure relay-backed APNs here for official iOS builds; direct APNs auth remains env-based for local/manual builds.","hasChildren":true}
|
||||
{"recordType":"path","path":"gateway.push.apns","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["network"],"label":"Gateway APNs Delivery","help":"APNs delivery settings for iOS devices paired to this gateway. Use relay settings for official/TestFlight builds that register through the external push relay.","hasChildren":true}
|
||||
{"recordType":"path","path":"gateway.push.apns.relay","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["network"],"label":"Gateway APNs Relay","help":"External relay settings for relay-backed APNs sends. The gateway uses this relay for push.test, wake nudges, and reconnect wakes after a paired official iOS build publishes a relay-backed registration.","hasChildren":true}
|
||||
{"recordType":"path","path":"gateway.push.apns.relay.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","network","url-secret"],"label":"Gateway APNs Relay Base URL","help":"Base HTTPS URL for the external APNs relay service used by official/TestFlight iOS builds. Keep this aligned with the relay URL baked into the iOS build so registration and send traffic hit the same deployment.","hasChildren":false}
|
||||
{"recordType":"path","path":"gateway.push.apns.relay.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","network"],"label":"Gateway APNs Relay Base URL","help":"Base HTTPS URL for the external APNs relay service used by official/TestFlight iOS builds. Keep this aligned with the relay URL baked into the iOS build so registration and send traffic hit the same deployment.","hasChildren":false}
|
||||
{"recordType":"path","path":"gateway.push.apns.relay.timeoutMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["network","performance"],"label":"Gateway APNs Relay Timeout (ms)","help":"Timeout in milliseconds for relay send requests from the gateway to the APNs relay (default: 10000). Increase for slower relays or networks, or lower to fail wake attempts faster.","hasChildren":false}
|
||||
{"recordType":"path","path":"gateway.reload","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["network","reliability"],"label":"Config Reload","help":"Live config-reload policy for how edits are applied and when full restarts are triggered. Keep hybrid behavior for safest operational updates unless debugging reload internals.","hasChildren":true}
|
||||
{"recordType":"path","path":"gateway.reload.debounceMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["network","performance","reliability"],"label":"Config Reload Debounce (ms)","help":"Debounce window (ms) before applying config changes.","hasChildren":false}
|
||||
@@ -3801,9 +3796,7 @@
|
||||
{"recordType":"path","path":"mcp.servers.*.cwd","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"mcp.servers.*.env","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"mcp.servers.*.env.*","kind":"core","type":["boolean","number","string"],"required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"mcp.servers.*.headers","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"mcp.servers.*.headers.*","kind":"core","type":["boolean","number","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["security"],"hasChildren":false}
|
||||
{"recordType":"path","path":"mcp.servers.*.url","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","url-secret"],"hasChildren":false}
|
||||
{"recordType":"path","path":"mcp.servers.*.url","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"mcp.servers.*.workingDirectory","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"media","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Media","help":"Top-level media behavior shared across providers and tools that handle inbound files. Keep defaults unless you need stable filenames for external processing pipelines or longer-lived inbound media retention.","hasChildren":true}
|
||||
{"recordType":"path","path":"media.preserveFilenames","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Preserve Media Filenames","help":"When enabled, uploaded media keeps its original filename instead of a generated temp-safe name. Turn this on when downstream automations depend on stable names, and leave off to reduce accidental filename leakage.","hasChildren":false}
|
||||
@@ -3839,7 +3832,6 @@
|
||||
{"recordType":"path","path":"memory.qmd.scope.rules.*.match.keyPrefix","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"memory.qmd.scope.rules.*.match.rawKeyPrefix","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"memory.qmd.searchMode","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"QMD Search Mode","help":"Selects the QMD retrieval path: \"query\" uses standard query flow, \"search\" uses search-oriented retrieval, and \"vsearch\" emphasizes vector retrieval. Keep default unless tuning relevance quality.","hasChildren":false}
|
||||
{"recordType":"path","path":"memory.qmd.searchTool","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"QMD Search Tool Override","help":"Overrides the exact mcporter tool name used for QMD searches while preserving `searchMode` as the semantic retrieval mode. Use this only when your QMD MCP server exposes a custom tool such as `hybrid_search` and keep it unset for the normal built-in tool mapping.","hasChildren":false}
|
||||
{"recordType":"path","path":"memory.qmd.sessions","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"memory.qmd.sessions.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"QMD Session Indexing","help":"Indexes session transcripts into QMD so recall can include prior conversation content (experimental, default: false). Enable only when transcript memory is required and you accept larger index churn.","hasChildren":false}
|
||||
{"recordType":"path","path":"memory.qmd.sessions.exportDir","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"QMD Session Export Directory","help":"Overrides where sanitized session exports are written before QMD indexing. Use this when default state storage is constrained or when exports must land on a managed volume.","hasChildren":false}
|
||||
@@ -3945,14 +3937,14 @@
|
||||
{"recordType":"path","path":"models.mode","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["models"],"label":"Model Catalog Mode","help":"Controls provider catalog behavior: \"merge\" keeps built-ins and overlays your custom providers, while \"replace\" uses only your configured providers. In \"merge\", matching provider IDs preserve non-empty agent models.json baseUrl values, while apiKey values are preserved only when the provider is not SecretRef-managed in current config/auth-profile context; SecretRef-managed providers refresh apiKey from current source markers, and matching model contextWindow/maxTokens use the higher value between explicit and implicit entries.","hasChildren":false}
|
||||
{"recordType":"path","path":"models.providers","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["models"],"label":"Model Providers","help":"Provider map keyed by provider ID containing connection/auth settings and concrete model definitions. Use stable provider keys so references from agents and tooling remain portable across environments.","hasChildren":true}
|
||||
{"recordType":"path","path":"models.providers.*","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"models.providers.*.api","kind":"core","type":"string","required":false,"enumValues":["openai-completions","openai-responses","openai-codex-responses","anthropic-messages","google-generative-ai","github-copilot","bedrock-converse-stream","ollama","azure-openai-responses"],"deprecated":false,"sensitive":false,"tags":["models"],"label":"Model Provider API Adapter","help":"Provider API adapter selection controlling request/response compatibility handling for model calls. Use the adapter that matches your upstream provider protocol to avoid feature mismatch.","hasChildren":false}
|
||||
{"recordType":"path","path":"models.providers.*.api","kind":"core","type":"string","required":false,"enumValues":["openai-completions","openai-responses","openai-codex-responses","anthropic-messages","google-generative-ai","github-copilot","bedrock-converse-stream","ollama"],"deprecated":false,"sensitive":false,"tags":["models"],"label":"Model Provider API Adapter","help":"Provider API adapter selection controlling request/response compatibility handling for model calls. Use the adapter that matches your upstream provider protocol to avoid feature mismatch.","hasChildren":false}
|
||||
{"recordType":"path","path":"models.providers.*.apiKey","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","models","security"],"label":"Model Provider API Key","help":"Provider credential used for API-key based authentication when the provider requires direct key auth. Use secret/env substitution and avoid storing real keys in committed config files.","hasChildren":true}
|
||||
{"recordType":"path","path":"models.providers.*.apiKey.id","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"models.providers.*.apiKey.provider","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"models.providers.*.apiKey.source","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"models.providers.*.auth","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["models"],"label":"Model Provider Auth Mode","help":"Selects provider auth style: \"api-key\" for API key auth, \"token\" for bearer token auth, \"oauth\" for OAuth credentials, and \"aws-sdk\" for AWS credential resolution. Match this to your provider requirements.","hasChildren":false}
|
||||
{"recordType":"path","path":"models.providers.*.authHeader","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["models"],"label":"Model Provider Authorization Header","help":"When true, credentials are sent via the HTTP Authorization header even if alternate auth is possible. Use this only when your provider or proxy explicitly requires Authorization forwarding.","hasChildren":false}
|
||||
{"recordType":"path","path":"models.providers.*.baseUrl","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":["models","url-secret"],"label":"Model Provider Base URL","help":"Base URL for the provider endpoint used to serve model requests for that provider entry. Use HTTPS endpoints and keep URLs environment-specific through config templating where needed.","hasChildren":false}
|
||||
{"recordType":"path","path":"models.providers.*.baseUrl","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":["models"],"label":"Model Provider Base URL","help":"Base URL for the provider endpoint used to serve model requests for that provider entry. Use HTTPS endpoints and keep URLs environment-specific through config templating where needed.","hasChildren":false}
|
||||
{"recordType":"path","path":"models.providers.*.headers","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["models"],"label":"Model Provider Headers","help":"Static HTTP headers merged into provider requests for tenant routing, proxy auth, or custom gateway requirements. Use this sparingly and keep sensitive header values in secrets.","hasChildren":true}
|
||||
{"recordType":"path","path":"models.providers.*.headers.*","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["models","security"],"hasChildren":true}
|
||||
{"recordType":"path","path":"models.providers.*.headers.*.id","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
@@ -3961,7 +3953,7 @@
|
||||
{"recordType":"path","path":"models.providers.*.injectNumCtxForOpenAICompat","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["models"],"label":"Model Provider Inject num_ctx (OpenAI Compat)","help":"Controls whether OpenClaw injects `options.num_ctx` for Ollama providers configured with the OpenAI-compatible adapter (`openai-completions`). Default is true. Set false only if your proxy/upstream rejects unknown `options` payload fields.","hasChildren":false}
|
||||
{"recordType":"path","path":"models.providers.*.models","kind":"core","type":"array","required":true,"deprecated":false,"sensitive":false,"tags":["models"],"label":"Model Provider Model List","help":"Declared model list for a provider including identifiers, metadata, and optional compatibility/cost hints. Keep IDs exact to provider catalog values so selection and fallback resolve correctly.","hasChildren":true}
|
||||
{"recordType":"path","path":"models.providers.*.models.*","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"models.providers.*.models.*.api","kind":"core","type":"string","required":false,"enumValues":["openai-completions","openai-responses","openai-codex-responses","anthropic-messages","google-generative-ai","github-copilot","bedrock-converse-stream","ollama","azure-openai-responses"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"models.providers.*.models.*.api","kind":"core","type":"string","required":false,"enumValues":["openai-completions","openai-responses","openai-codex-responses","anthropic-messages","google-generative-ai","github-copilot","bedrock-converse-stream","ollama"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"models.providers.*.models.*.compat","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"models.providers.*.models.*.compat.maxTokensField","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"models.providers.*.models.*.compat.nativeWebSearchTool","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
@@ -4031,7 +4023,6 @@
|
||||
{"recordType":"path","path":"plugins.entries.acpx.config.mcpServers.*.env.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.acpx.config.nonInteractivePermissions","kind":"plugin","type":"string","required":false,"enumValues":["deny","fail"],"deprecated":false,"sensitive":false,"tags":["access"],"label":"Non-Interactive Permission Policy","help":"acpx policy when interactive permission prompts are unavailable.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.acpx.config.permissionMode","kind":"plugin","type":"string","required":false,"enumValues":["approve-all","approve-reads","deny-all"],"deprecated":false,"sensitive":false,"tags":["access"],"label":"Permission Mode","help":"Default acpx permission policy for runtime prompts.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.acpx.config.pluginToolsMcpBridge","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Tools MCP Bridge","help":"Default off. When enabled, inject the built-in OpenClaw plugin-tools MCP server into ACPX sessions so ACP agents can call plugin-registered tools.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.acpx.config.queueOwnerTtlSeconds","kind":"plugin","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":["access","advanced"],"label":"Queue Owner TTL Seconds","help":"Idle queue-owner TTL for acpx prompt turns. Keep this short in OpenClaw to avoid delayed completion after each turn.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.acpx.config.strictWindowsCmdWrapper","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Strict Windows cmd Wrapper","help":"Enabled by default. On Windows, reject unresolved .cmd/.bat wrappers instead of shell fallback. Disable only for compatibility with non-standard wrappers.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.acpx.config.timeoutSeconds","kind":"plugin","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","performance"],"label":"Prompt Timeout Seconds","help":"Optional acpx timeout for each runtime turn.","hasChildren":false}
|
||||
@@ -4052,15 +4043,6 @@
|
||||
{"recordType":"path","path":"plugins.entries.amazon-bedrock.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.amazon-bedrock.subagent.allowModelOverride","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Allow Plugin Subagent Model Override","help":"Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.anthropic","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/anthropic-provider","help":"OpenClaw Anthropic provider plugin (plugin: anthropic)","hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.anthropic-vertex","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/anthropic-vertex-provider","help":"OpenClaw Anthropic Vertex provider plugin (plugin: anthropic-vertex)","hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.anthropic-vertex.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/anthropic-vertex-provider Config","help":"Plugin-defined config payload for anthropic-vertex.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.anthropic-vertex.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/anthropic-vertex-provider","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.anthropic-vertex.hooks","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Hook Policy","help":"Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.","hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.anthropic-vertex.hooks.allowPromptInjection","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Allow Prompt Injection Hooks","help":"Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.anthropic-vertex.subagent","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Subagent Policy","help":"Per-plugin subagent runtime controls for model override trust and allowlists. Keep this unset unless a plugin must explicitly steer subagent model selection.","hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.anthropic-vertex.subagent.allowedModels","kind":"plugin","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Plugin Subagent Allowed Models","help":"Allowed override targets for trusted plugin subagent runs as canonical \"provider/model\" refs. Use \"*\" only when you intentionally allow any model.","hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.anthropic-vertex.subagent.allowedModels.*","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.anthropic-vertex.subagent.allowModelOverride","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Allow Plugin Subagent Model Override","help":"Explicitly allows this plugin to request provider/model overrides in background subagent runs. Keep false unless the plugin is trusted to steer model selection.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.anthropic.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/anthropic-provider Config","help":"Plugin-defined config payload for anthropic.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.anthropic.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/anthropic-provider","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.anthropic.hooks","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Hook Policy","help":"Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.","hasChildren":true}
|
||||
@@ -4178,9 +4160,9 @@
|
||||
{"recordType":"path","path":"plugins.entries.diffs.config.defaults.background","kind":"plugin","type":"boolean","required":false,"defaultValue":true,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Default Background Highlights","help":"Show added/removed background highlights by default.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.diffs.config.defaults.diffIndicators","kind":"plugin","type":"string","required":false,"enumValues":["bars","classic","none"],"defaultValue":"bars","deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Diff Indicator Style","help":"Choose added/removed indicators style.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.diffs.config.defaults.fileFormat","kind":"plugin","type":"string","required":false,"enumValues":["png","pdf"],"defaultValue":"png","deprecated":false,"sensitive":false,"tags":["storage"],"label":"Default File Format","help":"Rendered file format for file mode (PNG or PDF).","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.diffs.config.defaults.fileMaxWidth","kind":"plugin","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":["performance","storage"],"label":"Default File Max Width","help":"Maximum file render width in CSS pixels.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.diffs.config.defaults.fileMaxWidth","kind":"plugin","type":"number","required":false,"defaultValue":960,"deprecated":false,"sensitive":false,"tags":["performance","storage"],"label":"Default File Max Width","help":"Maximum file render width in CSS pixels.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.diffs.config.defaults.fileQuality","kind":"plugin","type":"string","required":false,"enumValues":["standard","hq","print"],"defaultValue":"standard","deprecated":false,"sensitive":false,"tags":["storage"],"label":"Default File Quality","help":"Quality preset for PNG/PDF rendering.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.diffs.config.defaults.fileScale","kind":"plugin","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Default File Scale","help":"Device scale factor used while rendering file artifacts.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.diffs.config.defaults.fileScale","kind":"plugin","type":"number","required":false,"defaultValue":2,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Default File Scale","help":"Device scale factor used while rendering file artifacts.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.diffs.config.defaults.fontFamily","kind":"plugin","type":"string","required":false,"defaultValue":"Fira Code","deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Default Font","help":"Preferred font family name for diff content and headers.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.diffs.config.defaults.fontSize","kind":"plugin","type":"number","required":false,"defaultValue":15,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Default Font Size","help":"Base diff font size in pixels.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.diffs.config.defaults.format","kind":"plugin","type":"string","required":false,"enumValues":["png","pdf"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
@@ -4266,7 +4248,7 @@
|
||||
{"recordType":"path","path":"plugins.entries.firecrawl.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/firecrawl-plugin Config","help":"Plugin-defined config payload for firecrawl.","hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.firecrawl.config.webSearch","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.firecrawl.config.webSearch.apiKey","kind":"plugin","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","security"],"label":"Firecrawl Search API Key","help":"Firecrawl API key for web search (fallback: FIRECRAWL_API_KEY env var).","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.firecrawl.config.webSearch.baseUrl","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","url-secret"],"label":"Firecrawl Search Base URL","help":"Firecrawl Search base URL override.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.firecrawl.config.webSearch.baseUrl","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Firecrawl Search Base URL","help":"Firecrawl Search base URL override.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.firecrawl.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/firecrawl-plugin","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.firecrawl.hooks","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Hook Policy","help":"Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.","hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.firecrawl.hooks.allowPromptInjection","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Allow Prompt Injection Hooks","help":"Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.","hasChildren":false}
|
||||
@@ -4436,7 +4418,7 @@
|
||||
{"recordType":"path","path":"plugins.entries.memory-lancedb.config.dbPath","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","storage"],"label":"Database Path","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.memory-lancedb.config.embedding","kind":"plugin","type":"object","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.memory-lancedb.config.embedding.apiKey","kind":"plugin","type":"string","required":true,"deprecated":false,"sensitive":true,"tags":["auth","security","storage"],"label":"OpenAI API Key","help":"API key for OpenAI embeddings (or use ${OPENAI_API_KEY})","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.memory-lancedb.config.embedding.baseUrl","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","storage","url-secret"],"label":"Base URL","help":"Base URL for compatible providers (e.g. http://localhost:11434/v1)","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.memory-lancedb.config.embedding.baseUrl","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","storage"],"label":"Base URL","help":"Base URL for compatible providers (e.g. http://localhost:11434/v1)","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.memory-lancedb.config.embedding.dimensions","kind":"plugin","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","storage"],"label":"Dimensions","help":"Vector dimensions for custom models (required for non-standard models)","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.memory-lancedb.config.embedding.model","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["models","storage"],"label":"Embedding Model","help":"OpenAI embedding model to use","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.memory-lancedb.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Enable @openclaw/memory-lancedb","hasChildren":false}
|
||||
@@ -4495,7 +4477,7 @@
|
||||
{"recordType":"path","path":"plugins.entries.moonshot.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/moonshot-provider Config","help":"Plugin-defined config payload for moonshot.","hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.moonshot.config.webSearch","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.moonshot.config.webSearch.apiKey","kind":"plugin","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","security"],"label":"Kimi Search API Key","help":"Moonshot/Kimi API key (fallback: KIMI_API_KEY or MOONSHOT_API_KEY env var).","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.moonshot.config.webSearch.baseUrl","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","url-secret"],"label":"Kimi Search Base URL","help":"Kimi base URL override.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.moonshot.config.webSearch.baseUrl","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Kimi Search Base URL","help":"Kimi base URL override.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.moonshot.config.webSearch.model","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["models"],"label":"Kimi Search Model","help":"Kimi model override.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.moonshot.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/moonshot-provider","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.moonshot.hooks","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Hook Policy","help":"Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.","hasChildren":true}
|
||||
@@ -4620,7 +4602,7 @@
|
||||
{"recordType":"path","path":"plugins.entries.perplexity.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/perplexity-plugin Config","help":"Plugin-defined config payload for perplexity.","hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.perplexity.config.webSearch","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.perplexity.config.webSearch.apiKey","kind":"plugin","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","security"],"label":"Perplexity API Key","help":"Perplexity or OpenRouter API key for web search.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.perplexity.config.webSearch.baseUrl","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","url-secret"],"label":"Perplexity Base URL","help":"Optional Perplexity/OpenRouter chat-completions base URL override.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.perplexity.config.webSearch.baseUrl","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Perplexity Base URL","help":"Optional Perplexity/OpenRouter chat-completions base URL override.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.perplexity.config.webSearch.model","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["models"],"label":"Perplexity Model","help":"Optional Sonar/OpenRouter model override.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.perplexity.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/perplexity-plugin","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.perplexity.hooks","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Hook Policy","help":"Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.","hasChildren":true}
|
||||
@@ -4705,7 +4687,7 @@
|
||||
{"recordType":"path","path":"plugins.entries.tavily.config","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"@openclaw/tavily-plugin Config","help":"Plugin-defined config payload for tavily.","hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.tavily.config.webSearch","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.tavily.config.webSearch.apiKey","kind":"plugin","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","security"],"label":"Tavily API Key","help":"Tavily API key for web search and extraction (fallback: TAVILY_API_KEY env var).","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.tavily.config.webSearch.baseUrl","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","url-secret"],"label":"Tavily Base URL","help":"Tavily API base URL override.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.tavily.config.webSearch.baseUrl","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Tavily Base URL","help":"Tavily API base URL override.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.tavily.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Enable @openclaw/tavily-plugin","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.tavily.hooks","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Hook Policy","help":"Per-plugin typed hook policy controls for core-enforced safety gates. Use this to constrain high-impact hook categories without disabling the entire plugin.","hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.tavily.hooks.allowPromptInjection","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["access"],"label":"Allow Prompt Injection Hooks","help":"Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior.","hasChildren":false}
|
||||
@@ -4844,6 +4826,31 @@
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.transcriptTimeoutMs","kind":"plugin","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.auto","kind":"plugin","type":"string","required":false,"enumValues":["off","always","inbound","tagged"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.edge","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.edge.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.edge.lang","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.edge.outputFormat","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.edge.pitch","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.edge.proxy","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.edge.rate","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.edge.saveSubtitles","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.edge.timeoutMs","kind":"plugin","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.edge.voice","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.edge.volume","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.elevenlabs","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.elevenlabs.apiKey","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":true,"tags":["advanced","auth","media","security"],"label":"ElevenLabs API Key","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.elevenlabs.applyTextNormalization","kind":"plugin","type":"string","required":false,"enumValues":["auto","on","off"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.elevenlabs.baseUrl","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","media"],"label":"ElevenLabs Base URL","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.elevenlabs.languageCode","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.elevenlabs.modelId","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","media","models"],"label":"ElevenLabs Model ID","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.elevenlabs.seed","kind":"plugin","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.elevenlabs.voiceId","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","media"],"label":"ElevenLabs Voice ID","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.elevenlabs.voiceSettings","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.elevenlabs.voiceSettings.similarityBoost","kind":"plugin","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.elevenlabs.voiceSettings.speed","kind":"plugin","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.elevenlabs.voiceSettings.stability","kind":"plugin","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.elevenlabs.voiceSettings.style","kind":"plugin","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.elevenlabs.voiceSettings.useSpeakerBoost","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.maxTextLength","kind":"plugin","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.mode","kind":"plugin","type":"string","required":false,"enumValues":["final","all"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
@@ -4856,54 +4863,15 @@
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.modelOverrides.allowVoice","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.modelOverrides.allowVoiceSettings","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.modelOverrides.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.openai","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.openai.apiKey","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":true,"tags":["advanced","auth","media","security"],"label":"OpenAI API Key","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.openai.baseUrl","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.openai.instructions","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.openai.model","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","media","models"],"label":"OpenAI TTS Model","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.openai.speed","kind":"plugin","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.openai.voice","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","media"],"label":"OpenAI TTS Voice","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.prefsPath","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.provider","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","media"],"label":"TTS Provider Override","help":"Deep-merges with messages.tts (Microsoft is ignored for calls).","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.*","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.*.apiKey","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":true,"tags":["auth","media","security"],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.edge","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.edge.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.edge.lang","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.edge.outputFormat","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.edge.pitch","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.edge.proxy","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.edge.rate","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.edge.saveSubtitles","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.edge.timeoutMs","kind":"plugin","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.edge.voice","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.edge.volume","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.elevenlabs","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.elevenlabs.apiKey","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":true,"tags":["advanced","auth","media","security"],"label":"ElevenLabs API Key","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.elevenlabs.applyTextNormalization","kind":"plugin","type":"string","required":false,"enumValues":["auto","on","off"],"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.elevenlabs.baseUrl","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","media","url-secret"],"label":"ElevenLabs Base URL","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.elevenlabs.languageCode","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.elevenlabs.modelId","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","media","models"],"label":"ElevenLabs Model ID","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.elevenlabs.seed","kind":"plugin","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.elevenlabs.voiceId","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","media"],"label":"ElevenLabs Voice ID","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.elevenlabs.voiceSettings","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.elevenlabs.voiceSettings.similarityBoost","kind":"plugin","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.elevenlabs.voiceSettings.speed","kind":"plugin","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.elevenlabs.voiceSettings.stability","kind":"plugin","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.elevenlabs.voiceSettings.style","kind":"plugin","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.elevenlabs.voiceSettings.useSpeakerBoost","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.microsoft","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.microsoft.enabled","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.microsoft.lang","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.microsoft.outputFormat","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.microsoft.pitch","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.microsoft.proxy","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.microsoft.rate","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.microsoft.saveSubtitles","kind":"plugin","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.microsoft.timeoutMs","kind":"plugin","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.microsoft.voice","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.microsoft.volume","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.openai","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.openai.apiKey","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":true,"tags":["advanced","auth","media","security"],"label":"OpenAI API Key","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.openai.baseUrl","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["media","url-secret"],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.openai.instructions","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.openai.model","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","media","models"],"label":"OpenAI TTS Model","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.openai.speed","kind":"plugin","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.providers.openai.voice","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced","media"],"label":"OpenAI TTS Voice","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.summaryModel","kind":"plugin","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tts.timeoutMs","kind":"plugin","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.entries.voice-call.config.tunnel","kind":"plugin","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
@@ -5006,7 +4974,7 @@
|
||||
{"recordType":"path","path":"plugins.installs.*.clawhubPackage","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.installs.*.clawhubUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.installs.*.installedAt","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Install Time","help":"ISO timestamp of last install/update.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.installs.*.installPath","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Plugin Install Path","help":"Resolved install directory for the installed plugin bundle.","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.installs.*.installPath","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["storage"],"label":"Plugin Install Path","help":"Resolved install directory (usually ~/.openclaw/extensions/<id>).","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.installs.*.integrity","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Resolved Integrity","help":"Resolved npm dist integrity hash for the fetched artifact (if reported by npm).","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.installs.*.marketplaceName","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Marketplace Name","help":"Marketplace display name recorded for marketplace-backed plugin installs (if available).","hasChildren":false}
|
||||
{"recordType":"path","path":"plugins.installs.*.marketplacePlugin","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["advanced"],"label":"Plugin Marketplace Plugin","help":"Plugin entry name inside the source marketplace, used for later updates.","hasChildren":false}
|
||||
@@ -5210,7 +5178,7 @@
|
||||
{"recordType":"path","path":"tools.exec.ask","kind":"core","type":"string","required":false,"enumValues":["off","on-miss","always"],"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Exec Ask","help":"Approval strategy for when exec commands require human confirmation before running. Use stricter ask behavior in shared channels and lower-friction settings in private operator contexts.","hasChildren":false}
|
||||
{"recordType":"path","path":"tools.exec.backgroundMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.exec.cleanupMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.exec.host","kind":"core","type":"string","required":false,"enumValues":["auto","sandbox","gateway","node"],"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Exec Target","help":"Selects execution target strategy for shell commands. Use \"auto\" for runtime-aware behavior (sandbox when available, otherwise gateway), or pin sandbox/gateway/node explicitly when you need a fixed surface.","hasChildren":false}
|
||||
{"recordType":"path","path":"tools.exec.host","kind":"core","type":"string","required":false,"enumValues":["sandbox","gateway","node"],"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Exec Host","help":"Selects execution host strategy for shell commands, typically controlling local vs delegated execution environment. Use the safest host mode that still satisfies your automation requirements.","hasChildren":false}
|
||||
{"recordType":"path","path":"tools.exec.node","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Exec Node Binding","help":"Node binding configuration for exec tooling when command execution is delegated through connected nodes. Use explicit node binding only when multi-node routing is required.","hasChildren":false}
|
||||
{"recordType":"path","path":"tools.exec.notifyOnExit","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Exec Notify On Exit","help":"When true (default), backgrounded exec sessions on exit and node exec lifecycle events enqueue a system event and request a heartbeat.","hasChildren":false}
|
||||
{"recordType":"path","path":"tools.exec.notifyOnExitEmptySuccess","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Exec Notify On Empty Success","help":"When true, successful backgrounded exec exits with empty output still enqueue a completion system event (default: false).","hasChildren":false}
|
||||
@@ -5270,7 +5238,7 @@
|
||||
{"recordType":"path","path":"tools.media.audio.attachments.maxAttachments","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.media.audio.attachments.mode","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.media.audio.attachments.prefer","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.media.audio.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["media","tools","url-secret"],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.media.audio.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.media.audio.deepgram","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"tools.media.audio.deepgram.detectLanguage","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.media.audio.deepgram.punctuate","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
@@ -5287,7 +5255,7 @@
|
||||
{"recordType":"path","path":"tools.media.audio.models.*","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"tools.media.audio.models.*.args","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"tools.media.audio.models.*.args.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.media.audio.models.*.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["media","tools","url-secret"],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.media.audio.models.*.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.media.audio.models.*.capabilities","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"tools.media.audio.models.*.capabilities.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.media.audio.models.*.command","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
@@ -5331,7 +5299,7 @@
|
||||
{"recordType":"path","path":"tools.media.image.attachments.maxAttachments","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.media.image.attachments.mode","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.media.image.attachments.prefer","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.media.image.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["media","tools","url-secret"],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.media.image.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.media.image.deepgram","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"tools.media.image.deepgram.detectLanguage","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.media.image.deepgram.punctuate","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
@@ -5348,7 +5316,7 @@
|
||||
{"recordType":"path","path":"tools.media.image.models.*","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"tools.media.image.models.*.args","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"tools.media.image.models.*.args.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.media.image.models.*.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["media","tools","url-secret"],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.media.image.models.*.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.media.image.models.*.capabilities","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"tools.media.image.models.*.capabilities.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.media.image.models.*.command","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
@@ -5390,7 +5358,7 @@
|
||||
{"recordType":"path","path":"tools.media.models.*","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"tools.media.models.*.args","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"tools.media.models.*.args.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.media.models.*.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["media","tools","url-secret"],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.media.models.*.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.media.models.*.capabilities","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"tools.media.models.*.capabilities.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.media.models.*.command","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
@@ -5418,7 +5386,7 @@
|
||||
{"recordType":"path","path":"tools.media.video.attachments.maxAttachments","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.media.video.attachments.mode","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.media.video.attachments.prefer","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.media.video.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["media","tools","url-secret"],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.media.video.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.media.video.deepgram","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"tools.media.video.deepgram.detectLanguage","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.media.video.deepgram.punctuate","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
@@ -5435,7 +5403,7 @@
|
||||
{"recordType":"path","path":"tools.media.video.models.*","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"tools.media.video.models.*.args","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"tools.media.video.models.*.args.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.media.video.models.*.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["media","tools","url-secret"],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.media.video.models.*.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.media.video.models.*.capabilities","kind":"core","type":"array","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"tools.media.video.models.*.capabilities.*","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.media.video.models.*.command","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
@@ -5519,7 +5487,7 @@
|
||||
{"recordType":"path","path":"tools.web.fetch.firecrawl.apiKey.id","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.fetch.firecrawl.apiKey.provider","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.fetch.firecrawl.apiKey.source","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.fetch.firecrawl.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["tools","url-secret"],"label":"Firecrawl Base URL","help":"Firecrawl base URL (e.g. https://api.firecrawl.dev or custom endpoint).","hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.fetch.firecrawl.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Firecrawl Base URL","help":"Firecrawl base URL (e.g. https://api.firecrawl.dev or custom endpoint).","hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.fetch.firecrawl.enabled","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Enable Firecrawl Fallback","help":"Enable Firecrawl fallback for web_fetch (if configured).","hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.fetch.firecrawl.maxAgeMs","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance","tools"],"label":"Firecrawl Cache Max Age (ms)","help":"Firecrawl maxAge (ms) for cached results when supported by the API.","hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.fetch.firecrawl.onlyMainContent","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Firecrawl Main Content Only","help":"When true, Firecrawl returns only the main content (default: true).","hasChildren":false}
|
||||
@@ -5541,7 +5509,7 @@
|
||||
{"recordType":"path","path":"tools.web.search.brave.apiKey.id","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.brave.apiKey.provider","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.brave.apiKey.source","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.brave.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["tools","url-secret"],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.brave.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.brave.mode","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.brave.model","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.cacheTtlMinutes","kind":"core","type":"number","required":false,"deprecated":false,"sensitive":false,"tags":["performance","storage","tools"],"label":"Web Search Cache TTL (min)","help":"Cache TTL in minutes for web_search results.","hasChildren":false}
|
||||
@@ -5551,21 +5519,21 @@
|
||||
{"recordType":"path","path":"tools.web.search.firecrawl.apiKey.id","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.firecrawl.apiKey.provider","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.firecrawl.apiKey.source","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.firecrawl.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["tools","url-secret"],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.firecrawl.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.firecrawl.model","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.gemini","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"tools.web.search.gemini.apiKey","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","security","tools"],"hasChildren":true}
|
||||
{"recordType":"path","path":"tools.web.search.gemini.apiKey.id","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.gemini.apiKey.provider","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.gemini.apiKey.source","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.gemini.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["tools","url-secret"],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.gemini.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.gemini.model","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.grok","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
{"recordType":"path","path":"tools.web.search.grok.apiKey","kind":"core","type":["object","string"],"required":false,"deprecated":false,"sensitive":true,"tags":["auth","security","tools"],"hasChildren":true}
|
||||
{"recordType":"path","path":"tools.web.search.grok.apiKey.id","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.grok.apiKey.provider","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.grok.apiKey.source","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.grok.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["tools","url-secret"],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.grok.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.grok.inlineCitations","kind":"core","type":"boolean","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.grok.model","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.kimi","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
@@ -5573,7 +5541,7 @@
|
||||
{"recordType":"path","path":"tools.web.search.kimi.apiKey.id","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.kimi.apiKey.provider","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.kimi.apiKey.source","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.kimi.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["tools","url-secret"],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.kimi.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.kimi.model","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.maxResults","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance","tools"],"label":"Web Search Max Results","help":"Number of results to return (1-10).","hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.perplexity","kind":"core","type":"object","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":true}
|
||||
@@ -5581,7 +5549,7 @@
|
||||
{"recordType":"path","path":"tools.web.search.perplexity.apiKey.id","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.perplexity.apiKey.provider","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.perplexity.apiKey.source","kind":"core","type":"string","required":true,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.perplexity.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["tools","url-secret"],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.perplexity.baseUrl","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.perplexity.model","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":[],"hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.provider","kind":"core","type":"string","required":false,"deprecated":false,"sensitive":false,"tags":["tools"],"label":"Web Search Provider","help":"Search provider id. Auto-detected from available API keys if omitted.","hasChildren":false}
|
||||
{"recordType":"path","path":"tools.web.search.timeoutSeconds","kind":"core","type":"integer","required":false,"deprecated":false,"sensitive":false,"tags":["performance","tools"],"label":"Web Search Timeout (sec)","help":"Timeout in seconds for web_search requests.","hasChildren":false}
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -270,7 +270,7 @@ Isolated jobs (`agentTurn`) can set `lightContext: true` to run with lightweight
|
||||
Isolated jobs can deliver output to a channel via the top-level `delivery` config:
|
||||
|
||||
- `delivery.mode`: `announce` (channel delivery), `webhook` (HTTP POST), or `none`.
|
||||
- `delivery.channel`: `last` or any deliverable channel id, for example `discord`, `matrix`, `telegram`, or `whatsapp`.
|
||||
- `delivery.channel`: `whatsapp` / `telegram` / `discord` / `slack` / `signal` / `imessage` / `irc` / `googlechat` / `line` / `last`, plus extension channels like `msteams` / `mattermost` (plugins).
|
||||
- `delivery.to`: channel-specific recipient target.
|
||||
|
||||
`announce` delivery is only valid for isolated jobs (`sessionTarget: "isolated"`).
|
||||
|
||||
@@ -13,34 +13,46 @@ OpenClaw provides several automation mechanisms, each suited to different use ca
|
||||
|
||||
## Quick decision guide
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A{Run on a schedule?} -->|Yes| B{Exact timing needed?}
|
||||
A -->|No| C{React to events?}
|
||||
B -->|Yes| D[Cron]
|
||||
B -->|No| E[Heartbeat]
|
||||
C -->|Yes| F[Hooks]
|
||||
C -->|No| G[Standing Orders]
|
||||
```
|
||||
Do you need something to run on a schedule?
|
||||
YES → Is exact timing critical?
|
||||
YES → Cron (isolated)
|
||||
NO → Can it batch with other checks?
|
||||
YES → Heartbeat
|
||||
NO → Cron
|
||||
NO → Continue...
|
||||
|
||||
Do you need to react to an event (message, tool call, session change)?
|
||||
YES → Hooks (or plugin hooks)
|
||||
|
||||
Do you need to receive external HTTP events?
|
||||
YES → Webhooks
|
||||
|
||||
Do you want persistent instructions the agent always follows?
|
||||
YES → Standing Orders
|
||||
|
||||
Do you want to track what background work happened?
|
||||
→ Background Tasks (automatic for cron, ACP, subagents)
|
||||
```
|
||||
|
||||
## Mechanisms at a glance
|
||||
|
||||
| Mechanism | What it does | Runs in | Creates task record |
|
||||
| ---------------------------------------------- | -------------------------------------------------------- | ------------------------ | ------------------- |
|
||||
| [Heartbeat](/gateway/heartbeat) | Periodic main-session turn — batches multiple checks | Main session | No |
|
||||
| [Cron](/automation/cron-jobs) | Scheduled jobs with precise timing | Main or isolated session | Yes (all types) |
|
||||
| [Background Tasks](/automation/tasks) | Tracks detached work (cron, ACP, subagents, CLI) | N/A (ledger) | N/A |
|
||||
| [Hooks](/automation/hooks) | Event-driven scripts triggered by agent lifecycle events | Hook runner | No |
|
||||
| [Standing Orders](/automation/standing-orders) | Persistent instructions injected into the system prompt | Main session | No |
|
||||
| [Webhooks](/automation/webhook) | Receive inbound HTTP events and route to the agent | Gateway HTTP | No |
|
||||
| Mechanism | What it does | Runs in | Creates task record |
|
||||
|---|---|---|---|
|
||||
| [Heartbeat](/gateway/heartbeat) | Periodic main-session turn — batches multiple checks | Main session | No |
|
||||
| [Cron](/automation/cron-jobs) | Scheduled jobs with precise timing | Main or isolated session | Yes (all types) |
|
||||
| [Background Tasks](/automation/tasks) | Tracks detached work (cron, ACP, subagents, CLI) | N/A (ledger) | N/A |
|
||||
| [Hooks](/automation/hooks) | Event-driven scripts triggered by agent lifecycle events | Hook runner | No |
|
||||
| [Standing Orders](/automation/standing-orders) | Persistent instructions injected into the system prompt | Main session | No |
|
||||
| [Webhooks](/automation/webhook) | Receive inbound HTTP events and route to the agent | Gateway HTTP | No |
|
||||
|
||||
### Specialized automation
|
||||
|
||||
| Mechanism | What it does |
|
||||
| ---------------------------------------------- | ----------------------------------------------- |
|
||||
| [Gmail PubSub](/automation/gmail-pubsub) | Real-time Gmail notifications via Google PubSub |
|
||||
| [Polling](/automation/poll) | Periodic data source checks (RSS, APIs, etc.) |
|
||||
| [Auth Monitoring](/automation/auth-monitoring) | Credential health and expiry alerts |
|
||||
| Mechanism | What it does |
|
||||
|---|---|
|
||||
| [Gmail PubSub](/automation/gmail-pubsub) | Real-time Gmail notifications via Google PubSub |
|
||||
| [Polling](/automation/poll) | Periodic data source checks (RSS, APIs, etc.) |
|
||||
| [Auth Monitoring](/automation/auth-monitoring) | Credential health and expiry alerts |
|
||||
|
||||
## How they work together
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ Payload:
|
||||
- `sessionKey` optional (string): The key used to identify the agent's session. By default this field is rejected unless `hooks.allowRequestSessionKey=true`.
|
||||
- `wakeMode` optional (`now` | `next-heartbeat`): Whether to trigger an immediate heartbeat (default `now`) or wait for the next periodic check.
|
||||
- `deliver` optional (boolean): If `true`, the agent's response will be sent to the messaging channel. Defaults to `true`. Responses that are only heartbeat acknowledgments are automatically skipped.
|
||||
- `channel` optional (string): The messaging channel for delivery. Use `last` or any configured channel or plugin id, for example `discord`, `matrix`, `telegram`, or `whatsapp`. Defaults to `last`.
|
||||
- `channel` optional (string): The messaging channel for delivery. Core channels: `last`, `whatsapp`, `telegram`, `discord`, `slack`, `signal`, `imessage`, `irc`, `googlechat`, `line`. Extension channels (plugins): `msteams`, `mattermost`, and others. Defaults to `last`.
|
||||
- `to` optional (string): The recipient identifier for the channel (e.g., phone number for WhatsApp/Signal, chat ID for Telegram, channel ID for Discord/Slack/Mattermost (plugin), conversation ID for Microsoft Teams). Defaults to the last recipient in the main session.
|
||||
- `model` optional (string): Model override (e.g., `anthropic/claude-sonnet-4-6` or an alias). Must be in the allowed model list if restricted.
|
||||
- `thinking` optional (string): Thinking level override (e.g., `low`, `medium`, `high`).
|
||||
|
||||
@@ -143,7 +143,6 @@ This is a practical baseline config with DM pairing, room allowlist, and E2EE en
|
||||
|
||||
dm: {
|
||||
policy: "pairing",
|
||||
threadReplies: "off",
|
||||
},
|
||||
|
||||
groupPolicy: "allowlist",
|
||||
@@ -502,10 +501,9 @@ The repair flow does not delete old rooms automatically. It only picks the healt
|
||||
|
||||
Matrix supports native Matrix threads for both automatic replies and message-tool sends.
|
||||
|
||||
- `threadReplies: "off"` keeps replies top-level and keeps inbound threaded messages on the parent session.
|
||||
- `threadReplies: "off"` keeps replies top-level.
|
||||
- `threadReplies: "inbound"` replies inside a thread only when the inbound message was already in that thread.
|
||||
- `threadReplies: "always"` keeps room replies in a thread rooted at the triggering message and routes that conversation through the matching thread-scoped session from the first triggering message.
|
||||
- `dm.threadReplies` overrides the top-level setting for DMs only. For example, you can keep room threads isolated while keeping DMs flat.
|
||||
- `threadReplies: "always"` keeps room replies in a thread rooted at the triggering message.
|
||||
- Inbound threaded messages include the thread root message as extra agent context.
|
||||
- Message-tool sends now auto-inherit the current Matrix thread when the target is the same room, or the same DM user target, unless an explicit `threadId` is provided.
|
||||
- Runtime thread bindings are supported for Matrix. `/focus`, `/unfocus`, `/agents`, `/session idle`, `/session max-age`, and thread-bound `/acp spawn` now work in Matrix rooms and DMs.
|
||||
@@ -579,15 +577,6 @@ Current behavior:
|
||||
- `reactionNotifications: "off"` disables reaction system events.
|
||||
- Reaction removals are still not synthesized into system events because Matrix surfaces those as redactions, not as standalone `m.reaction` removals.
|
||||
|
||||
## History context
|
||||
|
||||
- `channels.matrix.historyLimit` controls how many recent room messages are included as `InboundHistory` when a Matrix room message triggers the agent.
|
||||
- It falls back to `messages.groupChat.historyLimit`. Set `0` to disable.
|
||||
- Matrix room history is room-only. DMs keep using normal session history.
|
||||
- Matrix room history is pending-only: OpenClaw buffers room messages that did not trigger a reply yet, then snapshots that window when a mention or other trigger arrives.
|
||||
- The current trigger message is not included in `InboundHistory`; it stays in the main inbound body for that turn.
|
||||
- Retries of the same Matrix event reuse the original history snapshot instead of drifting forward to newer room messages.
|
||||
|
||||
## DM and room policy example
|
||||
|
||||
```json5
|
||||
@@ -597,7 +586,6 @@ Current behavior:
|
||||
dm: {
|
||||
policy: "allowlist",
|
||||
allowFrom: ["@admin:example.org"],
|
||||
threadReplies: "off",
|
||||
},
|
||||
groupPolicy: "allowlist",
|
||||
groupAllowFrom: ["@admin:example.org"],
|
||||
@@ -645,7 +633,6 @@ See [Pairing](/channels/pairing) for the shared DM pairing flow and storage layo
|
||||
dm: {
|
||||
policy: "allowlist",
|
||||
allowFrom: ["@ops:example.org"],
|
||||
threadReplies: "off",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -745,7 +732,6 @@ Live directory lookup uses the logged-in Matrix account:
|
||||
- `groupPolicy`: `open`, `allowlist`, or `disabled`.
|
||||
- `groupAllowFrom`: allowlist of user IDs for room traffic.
|
||||
- `groupAllowFrom` entries should be full Matrix user IDs. Unresolved names are ignored at runtime.
|
||||
- `historyLimit`: max room messages to include as group history context. Falls back to `messages.groupChat.historyLimit`. Set `0` to disable.
|
||||
- `replyToMode`: `off`, `first`, or `all`.
|
||||
- `streaming`: `off` (default) or `partial`. `partial` enables single-message draft previews with edit-in-place updates.
|
||||
- `threadReplies`: `off`, `inbound`, or `always`.
|
||||
@@ -761,9 +747,8 @@ Live directory lookup uses the logged-in Matrix account:
|
||||
- `mediaMaxMb`: media size cap in MB for Matrix media handling. It applies to outbound sends and inbound media processing.
|
||||
- `autoJoin`: invite auto-join policy (`always`, `allowlist`, `off`). Default: `off`.
|
||||
- `autoJoinAllowlist`: rooms/aliases allowed when `autoJoin` is `allowlist`. Alias entries are resolved to room IDs during invite handling; OpenClaw does not trust alias state claimed by the invited room.
|
||||
- `dm`: DM policy block (`enabled`, `policy`, `allowFrom`, `threadReplies`).
|
||||
- `dm`: DM policy block (`enabled`, `policy`, `allowFrom`).
|
||||
- `dm.allowFrom` entries should be full Matrix user IDs unless you already resolved them through live directory lookup.
|
||||
- `dm.threadReplies`: DM-only thread policy override (`off`, `inbound`, `always`). It overrides the top-level `threadReplies` setting for both reply placement and session isolation in DMs.
|
||||
- `accounts`: named per-account overrides. Top-level `channels.matrix` values act as defaults for these entries.
|
||||
- `groups`: per-room policy map. Prefer room IDs or aliases; unresolved room names are ignored at runtime. Session/group identity uses the stable room ID after resolution, while human-readable labels still come from room names.
|
||||
- `rooms`: legacy alias for `groups`.
|
||||
|
||||
@@ -905,14 +905,12 @@ Subcommands:
|
||||
|
||||
Common RPCs:
|
||||
|
||||
- `config.set` (validate + write full config; use `baseHash` for optimistic concurrency)
|
||||
- `config.apply` (validate + write config + restart + wake)
|
||||
- `config.patch` (merge a partial update + restart + wake)
|
||||
- `update.run` (run update + restart + wake)
|
||||
|
||||
Tip: when calling `config.set`/`config.apply`/`config.patch` directly, pass `baseHash` from
|
||||
`config.get` if a config already exists.
|
||||
Tip: these config write RPCs preflight active SecretRef resolution for refs in the submitted config payload and reject writes when an effectively active submitted ref is unresolved.
|
||||
|
||||
## Models
|
||||
|
||||
|
||||
@@ -234,10 +234,3 @@ Suggested `.gitignore` starter:
|
||||
[Channel routing](/channels/channel-routing) for routing configuration.
|
||||
- If `agents.defaults.sandbox` is enabled, non-main sessions can use per-session sandbox
|
||||
workspaces under `agents.defaults.sandbox.workspaceRoot`.
|
||||
|
||||
## Related
|
||||
|
||||
- [Standing Orders](/automation/standing-orders) — persistent instructions in workspace files
|
||||
- [Heartbeat](/gateway/heartbeat) — HEARTBEAT.md workspace file
|
||||
- [Session](/concepts/session) — session storage paths
|
||||
- [Sandboxing](/gateway/sandboxing) — workspace access in sandboxed environments
|
||||
|
||||
@@ -135,10 +135,3 @@ Details: [Gateway protocol](/gateway/protocol), [Pairing](/channels/pairing),
|
||||
- Exactly one Gateway controls a single Baileys session per host.
|
||||
- Handshake is mandatory; any non‑JSON or non‑connect first frame is a hard close.
|
||||
- Events are not replayed; clients must refresh on gaps.
|
||||
|
||||
## Related
|
||||
|
||||
- [Agent Loop](/concepts/agent-loop) — detailed agent execution cycle
|
||||
- [Gateway Protocol](/gateway/protocol) — WebSocket protocol contract
|
||||
- [Queue](/concepts/queue) — command queue and concurrency
|
||||
- [Security](/gateway/security) — trust model and hardening
|
||||
|
||||
@@ -84,10 +84,3 @@ survive.
|
||||
For advanced configuration (reserve tokens, identifier preservation, custom
|
||||
context engines, OpenAI server-side compaction), see the
|
||||
[Session Management Deep Dive](/reference/session-management-compaction).
|
||||
|
||||
## Related
|
||||
|
||||
- [Session](/concepts/session) — session management and lifecycle
|
||||
- [Session Pruning](/concepts/session-pruning) — trimming tool results
|
||||
- [Context](/concepts/context) — how context is built for agent turns
|
||||
- [Hooks](/automation/hooks) — compaction lifecycle hooks (before_compaction, after_compaction)
|
||||
|
||||
@@ -266,9 +266,3 @@ OpenClaw resolves when it needs a context engine.
|
||||
|
||||
See also: [Compaction](/concepts/compaction), [Context](/concepts/context),
|
||||
[Plugins](/tools/plugin), [Plugin manifest](/plugins/manifest).
|
||||
|
||||
## Related
|
||||
|
||||
- [Context](/concepts/context) — how context is built for agent turns
|
||||
- [Plugin Architecture](/plugins/architecture) — registering context engine plugins
|
||||
- [Compaction](/concepts/compaction) — summarizing long conversations
|
||||
|
||||
@@ -170,10 +170,3 @@ pluggable interface, lifecycle hooks, and configuration.
|
||||
- `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).
|
||||
|
||||
Either way, it reports sizes and top contributors; it does **not** dump the full system prompt or tool schemas.
|
||||
|
||||
## Related
|
||||
|
||||
- [Context Engine](/concepts/context-engine) — custom context injection via plugins
|
||||
- [Compaction](/concepts/compaction) — summarizing long conversations
|
||||
- [System Prompt](/concepts/system-prompt) — how the system prompt is built
|
||||
- [Agent Loop](/concepts/agent-loop) — the full agent execution cycle
|
||||
|
||||
@@ -25,7 +25,7 @@ binary, and can index content beyond your workspace memory files.
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- Install QMD: `bun install -g @tobilu/qmd`
|
||||
- Install QMD: `bun install -g https://github.com/tobi/qmd`
|
||||
- SQLite build that allows extensions (`brew install sqlite` on macOS).
|
||||
- QMD must be on the gateway's `PATH`.
|
||||
- macOS and Linux work out of the box. Windows is best supported via WSL2.
|
||||
|
||||
@@ -152,10 +152,3 @@ Outbound message formatting is centralized in `messages`:
|
||||
- Reply threading via `replyToMode` and per-channel defaults
|
||||
|
||||
Details: [Configuration](/gateway/configuration-reference#messages) and channel docs.
|
||||
|
||||
## Related
|
||||
|
||||
- [Streaming](/concepts/streaming) — real-time message delivery
|
||||
- [Retry](/concepts/retry) — message delivery retry behavior
|
||||
- [Queue](/concepts/queue) — message processing queue
|
||||
- [Channels](/channels) — messaging platform integrations
|
||||
|
||||
@@ -590,10 +590,3 @@ openclaw models list
|
||||
```
|
||||
|
||||
See also: [/gateway/configuration](/gateway/configuration) for full configuration examples.
|
||||
|
||||
## Related
|
||||
|
||||
- [Models](/concepts/models) — model configuration and aliases
|
||||
- [Model Failover](/concepts/model-failover) — fallback chains and retry behavior
|
||||
- [Configuration Reference](/gateway/configuration-reference#agent-defaults) — model config keys
|
||||
- [Providers](/providers) — per-provider setup guides
|
||||
|
||||
@@ -223,10 +223,3 @@ Merge mode precedence for matching provider IDs:
|
||||
|
||||
Marker persistence is source-authoritative: OpenClaw writes markers from the active source config snapshot (pre-resolution), not from resolved runtime secret values.
|
||||
This applies whenever OpenClaw regenerates `models.json`, including command-driven paths like `openclaw agent`.
|
||||
|
||||
## Related
|
||||
|
||||
- [Model Providers](/concepts/model-providers) — provider routing and auth
|
||||
- [Model Failover](/concepts/model-failover) — fallback chains
|
||||
- [Image Generation](/tools/image-generation) — image model configuration
|
||||
- [Configuration Reference](/gateway/configuration-reference#agent-defaults) — model config keys
|
||||
|
||||
@@ -129,48 +129,6 @@ With **multiple agents**, each `agentId` becomes a **fully isolated persona**:
|
||||
|
||||
This lets **multiple people** share one Gateway server while keeping their AI “brains” and data isolated.
|
||||
|
||||
## Cross-agent QMD memory search
|
||||
|
||||
If one agent should search another agent's QMD session transcripts, add
|
||||
extra collections under `agents.list[].memorySearch.qmd.extraCollections`.
|
||||
Use `agents.defaults.memorySearch.qmd.extraCollections` only when every agent
|
||||
should inherit the same shared transcript collections.
|
||||
|
||||
```json5
|
||||
{
|
||||
agents: {
|
||||
defaults: {
|
||||
workspace: "~/workspaces/main",
|
||||
memorySearch: {
|
||||
qmd: {
|
||||
extraCollections: [{ path: "~/agents/family/sessions", name: "family-sessions" }],
|
||||
},
|
||||
},
|
||||
},
|
||||
list: [
|
||||
{
|
||||
id: "main",
|
||||
workspace: "~/workspaces/main",
|
||||
memorySearch: {
|
||||
qmd: {
|
||||
extraCollections: [{ path: "notes", name: "notes" }],
|
||||
},
|
||||
},
|
||||
},
|
||||
{ id: "family", workspace: "~/workspaces/family" },
|
||||
],
|
||||
},
|
||||
memory: {
|
||||
backend: "qmd",
|
||||
qmd: { includeDefaultMemory: false },
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
The extra collection path can be shared across agents, but the collection name
|
||||
stays explicit when the path is outside the agent workspace. Paths inside the
|
||||
workspace remain agent-scoped so each agent keeps its own transcript search set.
|
||||
|
||||
## One WhatsApp number, multiple people (DM split)
|
||||
|
||||
You can route **different WhatsApp DMs** to different agents while staying on **one WhatsApp account**. Match on sender E.164 (like `+15551234567`) with `peer.kind: "direct"`. Replies still come from the same WhatsApp number (no per‑agent sender identity).
|
||||
|
||||
@@ -185,9 +185,3 @@ Related docs:
|
||||
|
||||
- [/concepts/model-failover](/concepts/model-failover) (rotation + cooldown rules)
|
||||
- [/tools/slash-commands](/tools/slash-commands) (command surface)
|
||||
|
||||
## Related
|
||||
|
||||
- [Authentication](/gateway/authentication) — model provider auth overview
|
||||
- [Secrets](/gateway/secrets) — credential storage and SecretRef
|
||||
- [Configuration Reference](/gateway/configuration-reference#auth-storage) — auth config keys
|
||||
|
||||
@@ -153,9 +153,3 @@ Slack:
|
||||
- `partial` can use Slack native streaming (`chat.startStream`/`append`/`stop`) when available.
|
||||
- `block` uses append-style draft previews.
|
||||
- `progress` uses status preview text, then final answer.
|
||||
|
||||
## Related
|
||||
|
||||
- [Messages](/concepts/messages) — message lifecycle and delivery
|
||||
- [Retry](/concepts/retry) — retry behavior on delivery failure
|
||||
- [Channels](/channels) — per-channel streaming support
|
||||
|
||||
@@ -1366,7 +1366,11 @@
|
||||
},
|
||||
{
|
||||
"group": "Node features",
|
||||
"pages": ["nodes/talk", "nodes/voicewake", "nodes/location-command"]
|
||||
"pages": [
|
||||
"nodes/talk",
|
||||
"nodes/voicewake",
|
||||
"nodes/location-command"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -6,11 +6,7 @@ read_when:
|
||||
title: "Authentication"
|
||||
---
|
||||
|
||||
# Authentication (Model Providers)
|
||||
|
||||
<Note>
|
||||
This page covers **model provider** authentication (API keys, OAuth, setup tokens). For **gateway connection** authentication (token, password, trusted-proxy), see [Configuration](/gateway/configuration) and [Trusted Proxy Auth](/gateway/trusted-proxy-auth).
|
||||
</Note>
|
||||
# Authentication
|
||||
|
||||
OpenClaw supports OAuth and API keys for model providers. For always-on gateway
|
||||
hosts, API keys are usually the most predictable option. Subscription/OAuth
|
||||
|
||||
@@ -9,9 +9,14 @@ title: "Bridge Protocol"
|
||||
|
||||
# Bridge protocol (legacy node transport)
|
||||
|
||||
<Warning>
|
||||
The TCP bridge has been **removed**. Current OpenClaw builds do not ship the bridge listener and `bridge.*` config keys are no longer in the schema. This page is kept for historical reference only. Use the [Gateway Protocol](/gateway/protocol) for all node/operator clients.
|
||||
</Warning>
|
||||
The Bridge protocol is a **legacy** node transport (TCP JSONL). New node clients
|
||||
should use the unified Gateway WebSocket protocol instead.
|
||||
|
||||
If you are building an operator or node client, use the
|
||||
[Gateway protocol](/gateway/protocol).
|
||||
|
||||
**Note:** Current OpenClaw builds no longer ship the TCP bridge listener; this document is kept for historical reference.
|
||||
Legacy `bridge.*` config keys are no longer part of the config schema.
|
||||
|
||||
## Why we have both
|
||||
|
||||
|
||||
@@ -119,6 +119,7 @@ The provider id becomes the left side of your model ref:
|
||||
input: "arg",
|
||||
modelArg: "--model",
|
||||
modelAliases: {
|
||||
"claude-opus-4-6": "opus",
|
||||
"claude-opus-4-6": "opus",
|
||||
"claude-sonnet-4-6": "sonnet",
|
||||
},
|
||||
|
||||
@@ -154,7 +154,6 @@ Save to `~/.openclaw/openclaw.json` and you can DM the bot from that number.
|
||||
// Session behavior
|
||||
session: {
|
||||
scope: "per-sender",
|
||||
dmScope: "per-channel-peer", // recommended for multi-user inboxes
|
||||
reset: {
|
||||
mode: "daily",
|
||||
atHour: 4,
|
||||
@@ -289,7 +288,7 @@ Save to `~/.openclaw/openclaw.json` and you can DM the bot from that number.
|
||||
},
|
||||
sandbox: {
|
||||
mode: "non-main",
|
||||
scope: "session", // preferred over legacy perSession: true
|
||||
perSession: true,
|
||||
workspaceRoot: "~/.openclaw/sandboxes",
|
||||
docker: {
|
||||
image: "openclaw-sandbox:bookworm-slim",
|
||||
|
||||
@@ -1463,7 +1463,6 @@ scripts/sandbox-browser-setup.sh # optional browser image
|
||||
- `identity` derives defaults: `ackReaction` from `emoji`, `mentionPatterns` from `name`/`emoji`.
|
||||
- `subagents.allowAgents`: allowlist of agent ids for `sessions_spawn` (`["*"]` = any; default: same agent only).
|
||||
- Sandbox inheritance guard: if the requester session is sandboxed, `sessions_spawn` rejects targets that would run unsandboxed.
|
||||
- `subagents.requireAgentId`: when true, block `sessions_spawn` calls that omit `agentId` (forces explicit profile selection; default: false).
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -327,7 +327,7 @@ When validation fails:
|
||||
```
|
||||
|
||||
- `every`: duration string (`30m`, `2h`). Set `0m` to disable.
|
||||
- `target`: `last` | `none` | `<channel-id>` (for example `discord`, `matrix`, `telegram`, or `whatsapp`)
|
||||
- `target`: `last` | `whatsapp` | `telegram` | `discord` | `none`
|
||||
- `directPolicy`: `allow` (default) or `block` for DM-style heartbeat targets
|
||||
- See [Heartbeat](/gateway/heartbeat) for the full guide.
|
||||
|
||||
|
||||
@@ -59,8 +59,6 @@ Troubleshooting and beacon details: [Bonjour](/gateway/bonjour).
|
||||
- `_openclaw-gw._tcp` (gateway transport beacon)
|
||||
- TXT keys (non-secret):
|
||||
- `role=gateway`
|
||||
- `transport=gateway`
|
||||
- `displayName=<friendly name>` (operator-configured display name)
|
||||
- `lanHost=<hostname>.local`
|
||||
- `sshPort=22` (or whatever is advertised)
|
||||
- `gatewayPort=18789` (Gateway WS + HTTP)
|
||||
|
||||
@@ -61,22 +61,18 @@ cat ~/.openclaw/openclaw.json
|
||||
- Optional pre-flight update for git installs (interactive only).
|
||||
- UI protocol freshness check (rebuilds Control UI when the protocol schema is newer).
|
||||
- Health check + restart prompt.
|
||||
- Skills status summary (eligible/missing/blocked) and plugin status.
|
||||
- Skills status summary (eligible/missing/blocked).
|
||||
- Config normalization for legacy values.
|
||||
- Browser migration checks for legacy Chrome extension configs and Chrome MCP readiness.
|
||||
- OpenCode provider override warnings (`models.providers.opencode` / `models.providers.opencode-go`).
|
||||
- OAuth TLS prerequisites check for OpenAI Codex OAuth profiles.
|
||||
- Legacy on-disk state migration (sessions/agent dir/WhatsApp auth).
|
||||
- Legacy plugin manifest contract key migration (`speechProviders`, `mediaUnderstandingProviders`, `imageGenerationProviders` → `contracts`).
|
||||
- Legacy cron store migration (`jobId`, `schedule.cron`, top-level delivery/payload fields, payload `provider`, simple `notify: true` webhook fallback jobs).
|
||||
- Session lock file inspection and stale lock cleanup.
|
||||
- State integrity and permissions checks (sessions, transcripts, state dir).
|
||||
- Config file permission checks (chmod 600) when running locally.
|
||||
- Model auth health: checks OAuth expiry, can refresh expiring tokens, and reports auth-profile cooldown/disabled states.
|
||||
- Extra workspace dir detection (`~/openclaw`).
|
||||
- Sandbox image repair when sandboxing is enabled.
|
||||
- Legacy service migration and extra gateway detection.
|
||||
- Matrix channel legacy state migration (in `--fix` / `--repair` mode).
|
||||
- Gateway runtime checks (service installed but not running; cached launchd label).
|
||||
- Channel status warnings (probed from the running gateway).
|
||||
- Supervisor config audit (launchd/systemd/schtasks) with optional repair.
|
||||
@@ -85,9 +81,6 @@ cat ~/.openclaw/openclaw.json
|
||||
- Security warnings for open DM policies.
|
||||
- Gateway auth checks for local token mode (offers token generation when no token source exists; does not overwrite token SecretRef configs).
|
||||
- systemd linger check on Linux.
|
||||
- Workspace bootstrap file size check (truncation/near-limit warnings for context files).
|
||||
- Shell completion status check and auto-install/upgrade.
|
||||
- Memory search embedding provider readiness check (local model, remote API key, or QMD binary).
|
||||
- Source install checks (pnpm workspace mismatch, missing UI assets, missing tsx binary).
|
||||
- Writes updated config + wizard metadata.
|
||||
|
||||
@@ -129,10 +122,6 @@ Current migrations:
|
||||
- `routing.agents`/`routing.defaultAgentId` → `agents.list` + `agents.list[].default`
|
||||
- `routing.agentToAgent` → `tools.agentToAgent`
|
||||
- `routing.transcribeAudio` → `tools.media.audio.models`
|
||||
- `messages.tts.<provider>` (`openai`/`elevenlabs`/`microsoft`/`edge`) → `messages.tts.providers.<provider>`
|
||||
- `channels.discord.voice.tts.<provider>` (`openai`/`elevenlabs`/`microsoft`/`edge`) → `channels.discord.voice.tts.providers.<provider>`
|
||||
- `channels.discord.accounts.<id>.voice.tts.<provider>` (`openai`/`elevenlabs`/`microsoft`/`edge`) → `channels.discord.accounts.<id>.voice.tts.providers.<provider>`
|
||||
- `plugins.entries.voice-call.config.tts.<provider>` (`openai`/`elevenlabs`/`microsoft`/`edge`) → `plugins.entries.voice-call.config.tts.providers.<provider>`
|
||||
- `bindings[].match.accountID` → `bindings[].match.accountId`
|
||||
- For channels with named `accounts` but missing `accounts.default`, move account-scoped top-level single-account channel values into `channels.<channel>.accounts.default` when present
|
||||
- `identity` → `agents.list[].identity`
|
||||
@@ -184,16 +173,6 @@ still requires:
|
||||
This check does **not** apply to Docker, sandbox, remote-browser, or other
|
||||
headless flows. Those continue to use raw CDP.
|
||||
|
||||
### 2d) OAuth TLS prerequisites
|
||||
|
||||
When an OpenAI Codex OAuth profile is configured, doctor probes the OpenAI
|
||||
authorization endpoint to verify that the local Node/OpenSSL TLS stack can
|
||||
validate the certificate chain. If the probe fails with a certificate error (for
|
||||
example `UNABLE_TO_GET_ISSUER_CERT_LOCALLY`, expired cert, or self-signed cert),
|
||||
doctor prints platform-specific fix guidance. On macOS with a Homebrew Node, the
|
||||
fix is usually `brew postinstall ca-certificates`. With `--deep`, the probe runs
|
||||
even if the gateway is healthy.
|
||||
|
||||
### 3) Legacy state migrations (disk layout)
|
||||
|
||||
Doctor can migrate older on-disk layouts into the current structure:
|
||||
@@ -212,14 +191,6 @@ the legacy sessions + agent dir on startup so history/auth/models land in the
|
||||
per-agent path without a manual doctor run. WhatsApp auth is intentionally only
|
||||
migrated via `openclaw doctor`.
|
||||
|
||||
### 3a) Legacy plugin manifest migrations
|
||||
|
||||
Doctor scans all installed plugin manifests for deprecated top-level capability keys
|
||||
(`speechProviders`, `mediaUnderstandingProviders`, `imageGenerationProviders`).
|
||||
When found, it offers to move them into the `contracts` object and rewrite the manifest
|
||||
file in-place. This migration is idempotent; if the `contracts` key already has the
|
||||
same values, the legacy key is removed without duplicating the data.
|
||||
|
||||
### 3b) Legacy cron store migrations
|
||||
|
||||
Doctor also checks the cron job store (`~/.openclaw/cron/jobs.json` by default,
|
||||
@@ -239,15 +210,6 @@ Doctor only auto-migrates `notify: true` jobs when it can do so without
|
||||
changing behavior. If a job combines legacy notify fallback with an existing
|
||||
non-webhook delivery mode, doctor warns and leaves that job for manual review.
|
||||
|
||||
### 3c) Session lock cleanup
|
||||
|
||||
Doctor scans every agent session directory for stale write-lock files — files left
|
||||
behind when a session exited abnormally. For each lock file found it reports:
|
||||
the path, PID, whether the PID is still alive, lock age, and whether it is
|
||||
considered stale (dead PID or older than 30 minutes). In `--fix` / `--repair`
|
||||
mode it removes stale lock files automatically; otherwise it prints a note and
|
||||
instructs you to rerun with `--fix`.
|
||||
|
||||
### 4) State integrity checks (session persistence, routing, and safety)
|
||||
|
||||
The state directory is the operational brainstem. If it vanishes, you lose
|
||||
@@ -311,15 +273,6 @@ port. It can also scan for extra gateway-like services and print cleanup hints.
|
||||
Profile-named OpenClaw gateway services are considered first-class and are not
|
||||
flagged as "extra."
|
||||
|
||||
### 8b) Startup Matrix migration
|
||||
|
||||
When a Matrix channel account has a pending or actionable legacy state migration,
|
||||
doctor (in `--fix` / `--repair` mode) creates a pre-migration snapshot and then
|
||||
runs the best-effort migration steps: legacy Matrix state migration and legacy
|
||||
encrypted-state preparation. Both steps are non-fatal; errors are logged and
|
||||
startup continues. In read-only mode (`openclaw doctor` without `--fix`) this check
|
||||
is skipped entirely.
|
||||
|
||||
### 9) Security warnings
|
||||
|
||||
Doctor emits warnings when a provider is open to DMs without an allowlist, or
|
||||
@@ -330,44 +283,10 @@ when a policy is configured in a dangerous way.
|
||||
If running as a systemd user service, doctor ensures lingering is enabled so the
|
||||
gateway stays alive after logout.
|
||||
|
||||
### 11) Workspace status (skills, plugins, and legacy dirs)
|
||||
### 11) Skills status
|
||||
|
||||
Doctor prints a summary of the workspace state for the default agent:
|
||||
|
||||
- **Skills status**: counts eligible, missing-requirements, and allowlist-blocked skills.
|
||||
- **Legacy workspace dirs**: warns when `~/openclaw` or other legacy workspace directories
|
||||
exist alongside the current workspace.
|
||||
- **Plugin status**: counts loaded/disabled/errored plugins; lists plugin IDs for any
|
||||
errors; reports bundle plugin capabilities.
|
||||
- **Plugin compatibility warnings**: flags plugins that have compatibility issues with
|
||||
the current runtime.
|
||||
- **Plugin diagnostics**: surfaces any load-time warnings or errors emitted by the
|
||||
plugin registry.
|
||||
|
||||
### 11b) Bootstrap file size
|
||||
|
||||
Doctor checks whether workspace bootstrap files (for example `AGENTS.md`,
|
||||
`CLAUDE.md`, or other injected context files) are near or over the configured
|
||||
character budget. It reports per-file raw vs. injected character counts, truncation
|
||||
percentage, truncation cause (`max/file` or `max/total`), and total injected
|
||||
characters as a fraction of the total budget. When files are truncated or near
|
||||
the limit, doctor prints tips for tuning `agents.defaults.bootstrapMaxChars`
|
||||
and `agents.defaults.bootstrapTotalMaxChars`.
|
||||
|
||||
### 11c) Shell completion
|
||||
|
||||
Doctor checks whether tab completion is installed for the current shell
|
||||
(zsh, bash, fish, or PowerShell):
|
||||
|
||||
- If the shell profile uses a slow dynamic completion pattern
|
||||
(`source <(openclaw completion ...)`), doctor upgrades it to the faster
|
||||
cached file variant.
|
||||
- If completion is configured in the profile but the cache file is missing,
|
||||
doctor regenerates the cache automatically.
|
||||
- If no completion is configured at all, doctor prompts to install it
|
||||
(interactive mode only; skipped with `--non-interactive`).
|
||||
|
||||
Run `openclaw completion --write-state` to regenerate the cache manually.
|
||||
Doctor prints a quick summary of eligible/missing/blocked skills for the current
|
||||
workspace.
|
||||
|
||||
### 12) Gateway auth checks (local token)
|
||||
|
||||
@@ -390,26 +309,6 @@ Some repair flows need to inspect configured credentials without weakening runti
|
||||
Doctor runs a health check and offers to restart the gateway when it looks
|
||||
unhealthy.
|
||||
|
||||
### 13b) Memory search readiness
|
||||
|
||||
Doctor checks whether the configured memory search embedding provider is ready
|
||||
for the default agent. The behavior depends on the configured backend and provider:
|
||||
|
||||
- **QMD backend**: probes whether the `qmd` binary is available and startable.
|
||||
If not, prints fix guidance including the npm package and a manual binary path option.
|
||||
- **Explicit local provider**: checks for a local model file or a recognized
|
||||
remote/downloadable model URL. If missing, suggests switching to a remote provider.
|
||||
- **Explicit remote provider** (`openai`, `voyage`, etc.): verifies an API key is
|
||||
present in the environment or auth store. Prints actionable fix hints if missing.
|
||||
- **Auto provider**: checks local model availability first, then tries each remote
|
||||
provider in auto-selection order.
|
||||
|
||||
When a gateway probe result is available (gateway was healthy at the time of the
|
||||
check), doctor cross-references its result with the CLI-visible config and notes
|
||||
any discrepancy.
|
||||
|
||||
Use `openclaw memory status --deep` to verify embedding readiness at runtime.
|
||||
|
||||
### 14) Channel status warnings
|
||||
|
||||
If the gateway is healthy, doctor runs a channel status probe and reports
|
||||
|
||||
@@ -8,6 +8,8 @@ title: "Gateway Lock"
|
||||
|
||||
# Gateway lock
|
||||
|
||||
Last updated: 2025-12-11
|
||||
|
||||
## Why
|
||||
|
||||
- Ensure only one gateway instance runs per base port on the same host; additional gateways must use isolated profiles and unique ports.
|
||||
@@ -30,8 +32,3 @@ title: "Gateway Lock"
|
||||
|
||||
- If the port is occupied by _another_ process, the error is the same; free the port or choose another with `openclaw gateway --port <port>`.
|
||||
- The macOS app still maintains its own lightweight PID guard before spawning the gateway; the runtime lock is enforced by the WebSocket bind.
|
||||
|
||||
## Related
|
||||
|
||||
- [Multiple Gateways](/gateway/multiple-gateways) — running multiple instances with unique ports
|
||||
- [Troubleshooting](/gateway/troubleshooting) — diagnosing `EADDRINUSE` and port conflicts
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
---
|
||||
summary: "Health check commands and gateway health monitoring"
|
||||
summary: "Health check steps for channel connectivity"
|
||||
read_when:
|
||||
- Diagnosing channel connectivity or gateway health
|
||||
- Understanding health check CLI commands and options
|
||||
- Diagnosing WhatsApp channel health
|
||||
title: "Health Checks"
|
||||
---
|
||||
|
||||
@@ -42,12 +41,4 @@ Short guide to verify channel connectivity without guessing.
|
||||
|
||||
## Dedicated "health" command
|
||||
|
||||
`openclaw health --json` asks the running Gateway for its health snapshot (no direct channel sockets from the CLI). It reports linked creds/auth age when available, per-channel probe summaries, session-store summary, and a probe duration. It exits non-zero if the Gateway is unreachable or the probe fails/timeouts.
|
||||
|
||||
Options:
|
||||
|
||||
- `--json`: machine-readable JSON output
|
||||
- `--timeout <ms>`: override the default 10s probe timeout
|
||||
- `--probe`: force a live probe of all channels instead of returning the cached health snapshot
|
||||
|
||||
The health snapshot includes: `ok` (boolean), `ts` (timestamp), `durationMs` (probe time), per-channel status, agent availability, and session-store summary.
|
||||
`openclaw health --json` asks the running Gateway for its health snapshot (no direct channel sockets from the CLI). It reports linked creds/auth age when available, per-channel probe summaries, session-store summary, and a probe duration. It exits non-zero if the Gateway is unreachable or the probe fails/timeouts. Use `--timeout <ms>` to override the 10s default.
|
||||
|
||||
@@ -227,7 +227,7 @@ Use `accountId` to target a specific account on multi-account channels like Tele
|
||||
- Session key formats: see [Sessions](/concepts/session) and [Groups](/channels/groups).
|
||||
- `target`:
|
||||
- `last`: deliver to the last used external channel.
|
||||
- explicit channel: any configured channel or plugin id, for example `discord`, `matrix`, `telegram`, or `whatsapp`.
|
||||
- explicit channel: `whatsapp` / `telegram` / `discord` / `googlechat` / `slack` / `msteams` / `signal` / `imessage`.
|
||||
- `none` (default): run the heartbeat but **do not deliver** externally.
|
||||
- `directPolicy`: controls direct/DM delivery behavior:
|
||||
- `allow` (default): allow direct/DM heartbeat delivery.
|
||||
|
||||
@@ -7,8 +7,6 @@ title: "Network model"
|
||||
|
||||
# Network Model
|
||||
|
||||
> This content has been merged into [Network](/network#core-model). See that page for the current guide.
|
||||
|
||||
Most operations flow through the Gateway (`openclaw gateway`), a single long-running
|
||||
process that owns channel connections and the WebSocket control plane.
|
||||
|
||||
|
||||
@@ -4,8 +4,6 @@ read_when: "Connecting the macOS app to a remote gateway over SSH"
|
||||
title: "Remote Gateway Setup"
|
||||
---
|
||||
|
||||
> This content has been merged into [Remote Access](/gateway/remote#macos-persistent-ssh-tunnel-via-launchagent). See that page for the current guide.
|
||||
|
||||
# Running OpenClaw.app with a Remote Gateway
|
||||
|
||||
OpenClaw.app uses SSH tunneling to connect to a remote gateway. This guide shows you how to set it up.
|
||||
|
||||
@@ -151,98 +151,3 @@ Short version: **keep the Gateway loopback-only** unless you’re sure you need
|
||||
- Treat browser control like operator access: tailnet-only + deliberate node pairing.
|
||||
|
||||
Deep dive: [Security](/gateway/security).
|
||||
|
||||
### macOS: persistent SSH tunnel via LaunchAgent
|
||||
|
||||
For macOS clients connecting to a remote gateway, the easiest persistent setup uses an SSH `LocalForward` config entry plus a LaunchAgent to keep the tunnel alive across reboots and crashes.
|
||||
|
||||
#### Step 1: add SSH config
|
||||
|
||||
Edit `~/.ssh/config`:
|
||||
|
||||
```ssh
|
||||
Host remote-gateway
|
||||
HostName <REMOTE_IP>
|
||||
User <REMOTE_USER>
|
||||
LocalForward 18789 127.0.0.1:18789
|
||||
IdentityFile ~/.ssh/id_rsa
|
||||
```
|
||||
|
||||
Replace `<REMOTE_IP>` and `<REMOTE_USER>` with your values.
|
||||
|
||||
#### Step 2: copy SSH key (one-time)
|
||||
|
||||
```bash
|
||||
ssh-copy-id -i ~/.ssh/id_rsa <REMOTE_USER>@<REMOTE_IP>
|
||||
```
|
||||
|
||||
#### Step 3: configure the gateway token
|
||||
|
||||
Store the token in config so it persists across restarts:
|
||||
|
||||
```bash
|
||||
openclaw config set gateway.remote.token "<your-token>"
|
||||
```
|
||||
|
||||
#### Step 4: create the LaunchAgent
|
||||
|
||||
Save this as `~/Library/LaunchAgents/ai.openclaw.ssh-tunnel.plist`:
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>ai.openclaw.ssh-tunnel</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/usr/bin/ssh</string>
|
||||
<string>-N</string>
|
||||
<string>remote-gateway</string>
|
||||
</array>
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
```
|
||||
|
||||
#### Step 5: load the LaunchAgent
|
||||
|
||||
```bash
|
||||
launchctl bootstrap gui/$UID ~/Library/LaunchAgents/ai.openclaw.ssh-tunnel.plist
|
||||
```
|
||||
|
||||
The tunnel will start automatically at login, restart on crash, and keep the forwarded port live.
|
||||
|
||||
Note: if you have a leftover `com.openclaw.ssh-tunnel` LaunchAgent from an older setup, unload and delete it.
|
||||
|
||||
#### Troubleshooting
|
||||
|
||||
Check if the tunnel is running:
|
||||
|
||||
```bash
|
||||
ps aux | grep "ssh -N remote-gateway" | grep -v grep
|
||||
lsof -i :18789
|
||||
```
|
||||
|
||||
Restart the tunnel:
|
||||
|
||||
```bash
|
||||
launchctl kickstart -k gui/$UID/ai.openclaw.ssh-tunnel
|
||||
```
|
||||
|
||||
Stop the tunnel:
|
||||
|
||||
```bash
|
||||
launchctl bootout gui/$UID/ai.openclaw.ssh-tunnel
|
||||
```
|
||||
|
||||
| Config entry | What it does |
|
||||
| ------------------------------------ | ------------------------------------------------------------ |
|
||||
| `LocalForward 18789 127.0.0.1:18789` | Forwards local port 18789 to remote port 18789 |
|
||||
| `ssh -N` | SSH without executing remote commands (port-forwarding only) |
|
||||
| `KeepAlive` | Automatically restarts the tunnel if it crashes |
|
||||
| `RunAtLoad` | Starts the tunnel when the LaunchAgent loads at login |
|
||||
|
||||
@@ -50,8 +50,8 @@ Not sandboxed:
|
||||
|
||||
`agents.defaults.sandbox.scope` controls **how many containers** are created:
|
||||
|
||||
- `"agent"` (default): one container per agent.
|
||||
- `"session"`: one container per session.
|
||||
- `"session"` (default): one container per session.
|
||||
- `"agent"`: one container per agent.
|
||||
- `"shared"`: one container shared by all sandboxed sessions.
|
||||
|
||||
## Backend
|
||||
|
||||
@@ -364,7 +364,6 @@ Runtime-minted or rotating credentials and OAuth refresh material are intentiona
|
||||
- Field without a ref: unchanged.
|
||||
- Field with a ref: required on active surfaces during activation.
|
||||
- If both plaintext and ref are present, ref takes precedence on supported precedence paths.
|
||||
- The redaction sentinel `__OPENCLAW_REDACTED__` is reserved for internal config redaction/restore and is rejected as literal submitted config data.
|
||||
|
||||
Warning and audit signals:
|
||||
|
||||
@@ -384,14 +383,12 @@ Secret activation runs on:
|
||||
- Config reload hot-apply path
|
||||
- Config reload restart-check path
|
||||
- Manual reload via `secrets.reload`
|
||||
- Gateway config write RPC preflight (`config.set` / `config.apply` / `config.patch`) for active-surface SecretRef resolvability within the submitted config payload before persisting edits
|
||||
|
||||
Activation contract:
|
||||
|
||||
- Success swaps the snapshot atomically.
|
||||
- Startup failure aborts gateway startup.
|
||||
- Runtime reload failure keeps the last-known-good snapshot.
|
||||
- Write-RPC preflight failure rejects the submitted config and keeps both disk config and active runtime snapshot unchanged.
|
||||
- Providing an explicit per-call channel token to an outbound helper/tool call does not trigger SecretRef activation; activation points remain startup, reload, and explicit `secrets.reload`.
|
||||
|
||||
## Degraded and recovered signals
|
||||
|
||||
@@ -68,19 +68,11 @@ Important boundary notes:
|
||||
|
||||
Gateway HTTP also applies a hard deny list by default (even if session policy allows the tool):
|
||||
|
||||
- `exec` — direct command execution (RCE surface)
|
||||
- `spawn` — arbitrary child process creation (RCE surface)
|
||||
- `shell` — shell command execution (RCE surface)
|
||||
- `fs_write` — arbitrary file mutation on the host
|
||||
- `fs_delete` — arbitrary file deletion on the host
|
||||
- `fs_move` — arbitrary file move/rename on the host
|
||||
- `apply_patch` — patch application can rewrite arbitrary files
|
||||
- `sessions_spawn` — session orchestration; spawning agents remotely is RCE
|
||||
- `sessions_send` — cross-session message injection
|
||||
- `cron` — persistent automation control plane
|
||||
- `gateway` — gateway control plane; prevents reconfiguration via HTTP
|
||||
- `nodes` — node command relay can reach system.run on paired hosts
|
||||
- `whatsapp_login` — interactive setup requiring terminal QR scan; hangs on HTTP
|
||||
- `cron`
|
||||
- `sessions_spawn`
|
||||
- `sessions_send`
|
||||
- `gateway`
|
||||
- `whatsapp_login`
|
||||
|
||||
You can customize this deny list via `gateway.tools`:
|
||||
|
||||
|
||||
@@ -145,8 +145,7 @@ If logs show nonce/signature errors, update the connecting client and verify it:
|
||||
Related:
|
||||
|
||||
- [/web/control-ui](/web/control-ui)
|
||||
- [/gateway/configuration](/gateway/configuration) (gateway auth modes)
|
||||
- [/gateway/trusted-proxy-auth](/gateway/trusted-proxy-auth)
|
||||
- [/gateway/authentication](/gateway/authentication)
|
||||
- [/gateway/remote](/gateway/remote)
|
||||
- [/cli/devices](/cli/devices)
|
||||
|
||||
|
||||
@@ -66,7 +66,6 @@ Think of the suites as “increasing realism” (and increasing flakiness/cost):
|
||||
- Shared unit, extension, channel, and gateway runs all stay on Vitest `forks`.
|
||||
- The wrapper keeps measured fork-isolated exceptions and heavy singleton lanes explicit in `test/fixtures/test-parallel.behavior.json`.
|
||||
- The wrapper peels the heaviest measured files into dedicated lanes instead of relying on a growing hand-maintained exclusion list.
|
||||
- CLI startup benchmarking now has distinct saved outputs: `pnpm test:startup:bench:smoke` writes the targeted smoke artifact at `.artifacts/cli-startup-bench-smoke.json`, `pnpm test:startup:bench:save` writes the full-suite artifact at `.artifacts/cli-startup-bench-all.json` with `runs=5` and `warmup=1`, and `pnpm test:startup:bench:update` refreshes the checked-in fixture at `test/fixtures/cli-startup-bench.json` with `runs=5` and `warmup=1`.
|
||||
- For surface-only local runs, unit, extension, and channel shared lanes can overlap their isolated hotspots instead of waiting behind one serial prefix.
|
||||
- For multi-surface local runs, the wrapper keeps the shared surface phases ordered, but batches inside the same shared phase now fan out together, deferred isolated work can overlap the next shared phase, and spare `unit-fast` headroom now starts that deferred work earlier instead of leaving those slots idle.
|
||||
- Refresh the timing snapshots with `pnpm test:perf:update-timings` and `pnpm test:perf:update-timings:extensions` after major suite shape changes.
|
||||
|
||||
@@ -342,11 +342,3 @@ flowchart TD
|
||||
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Related
|
||||
|
||||
- [FAQ](/help/faq) — frequently asked questions
|
||||
- [Gateway Troubleshooting](/gateway/troubleshooting) — gateway-specific issues
|
||||
- [Doctor](/gateway/doctor) — automated health checks and repairs
|
||||
- [Channel Troubleshooting](/channels/troubleshooting) — channel connectivity issues
|
||||
- [Automation Troubleshooting](/automation/troubleshooting) — cron and heartbeat issues
|
||||
|
||||
@@ -394,11 +394,3 @@ scripts/sandbox-setup.sh
|
||||
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Related
|
||||
|
||||
- [Install Overview](/install) — all installation methods
|
||||
- [Podman](/install/podman) — Podman alternative to Docker
|
||||
- [ClawDock](/install/clawdock) — Docker Compose community setup
|
||||
- [Updating](/install/updating) — keeping OpenClaw up to date
|
||||
- [Configuration](/gateway/configuration) — gateway configuration after install
|
||||
|
||||
@@ -136,9 +136,3 @@ export PATH="$HOME/.npm-global/bin:$PATH"
|
||||
```
|
||||
|
||||
Add the `export PATH=...` line to your `~/.bashrc` or `~/.zshrc` to make it permanent.
|
||||
|
||||
## Related
|
||||
|
||||
- [Install Overview](/install) — all installation methods
|
||||
- [Updating](/install/updating) — keeping OpenClaw up to date
|
||||
- [Getting Started](/start/getting-started) — first steps after install
|
||||
|
||||
@@ -126,9 +126,3 @@ To return to latest: `git checkout main && git pull`.
|
||||
- Run `openclaw doctor` again and read the output carefully.
|
||||
- Check: [Troubleshooting](/gateway/troubleshooting)
|
||||
- Ask in Discord: [https://discord.gg/clawd](https://discord.gg/clawd)
|
||||
|
||||
## Related
|
||||
|
||||
- [Install Overview](/install) — all installation methods
|
||||
- [Doctor](/gateway/doctor) — health checks after updates
|
||||
- [Migrating](/install/migrating) — major version migration guides
|
||||
|
||||
@@ -350,8 +350,3 @@ Queues + sessions:
|
||||
- **Logs empty?** Check that the Gateway is running and writing to the file path
|
||||
in `logging.file`.
|
||||
- **Need more detail?** Set `logging.level` to `debug` or `trace` and retry.
|
||||
|
||||
## Related
|
||||
|
||||
- [Gateway Logging Internals](/gateway/logging) — WS log styles, subsystem prefixes, and console capture
|
||||
- [Diagnostics](/gateway/configuration-reference#diagnostics) — OpenTelemetry export and cache trace config
|
||||
|
||||
@@ -14,15 +14,6 @@ devices across localhost, LAN, and tailnet.
|
||||
|
||||
## Core model
|
||||
|
||||
Most operations flow through the Gateway (`openclaw gateway`), a single long-running process that owns channel connections and the WebSocket control plane.
|
||||
|
||||
- **Loopback first**: the Gateway WS defaults to `ws://127.0.0.1:18789`. Tokens are required for non-loopback binds.
|
||||
- **One Gateway per host** is recommended. For isolation, run multiple gateways with isolated profiles and ports ([Multiple Gateways](/gateway/multiple-gateways)).
|
||||
- **Canvas host** is served on the same port as the Gateway (`/__openclaw__/canvas/`, `/__openclaw__/a2ui/`), protected by Gateway auth when bound beyond loopback.
|
||||
- **Remote access** is typically SSH tunnel or Tailscale VPN ([Remote Access](/gateway/remote)).
|
||||
|
||||
Key references:
|
||||
|
||||
- [Gateway architecture](/concepts/architecture)
|
||||
- [Gateway protocol](/gateway/protocol)
|
||||
- [Gateway runbook](/gateway)
|
||||
|
||||
@@ -273,11 +273,3 @@ internal imports — never import your own plugin through its SDK path.
|
||||
Full manifest schema reference
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Related
|
||||
|
||||
- [Plugin Architecture](/plugins/architecture) — internal architecture deep dive
|
||||
- [SDK Overview](/plugins/sdk-overview) — Plugin SDK reference
|
||||
- [Manifest](/plugins/manifest) — plugin manifest format
|
||||
- [Channel Plugins](/plugins/sdk-channel-plugins) — building channel plugins
|
||||
- [Provider Plugins](/plugins/sdk-provider-plugins) — building provider plugins
|
||||
|
||||
@@ -276,9 +276,3 @@ See [Configuration reference](/gateway/configuration) for the full `plugins.*` s
|
||||
- If your plugin depends on native modules, document the build steps and any
|
||||
package-manager allowlist requirements (for example, pnpm `allow-build-scripts`
|
||||
- `pnpm rebuild <package>`).
|
||||
|
||||
## Related
|
||||
|
||||
- [Building Plugins](/plugins/building-plugins) — getting started with plugins
|
||||
- [Plugin Architecture](/plugins/architecture) — internal architecture
|
||||
- [SDK Overview](/plugins/sdk-overview) — Plugin SDK reference
|
||||
|
||||
@@ -219,11 +219,9 @@ streaming speech on calls. You can override it under the plugin config with the
|
||||
{
|
||||
tts: {
|
||||
provider: "elevenlabs",
|
||||
providers: {
|
||||
elevenlabs: {
|
||||
voiceId: "pMsXgVXv3BLzUgSXRplE",
|
||||
modelId: "eleven_multilingual_v2",
|
||||
},
|
||||
elevenlabs: {
|
||||
voiceId: "pMsXgVXv3BLzUgSXRplE",
|
||||
modelId: "eleven_multilingual_v2",
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -231,11 +229,9 @@ streaming speech on calls. You can override it under the plugin config with the
|
||||
|
||||
Notes:
|
||||
|
||||
- Legacy `tts.<provider>` keys inside plugin config (`openai`, `elevenlabs`, `microsoft`, `edge`) are auto-migrated to `tts.providers.<provider>` on load. Prefer the `providers` shape in committed config.
|
||||
- **Microsoft speech is ignored for voice calls** (telephony audio needs PCM; the current Microsoft transport does not expose telephony PCM output).
|
||||
- Core TTS is used when Twilio media streaming is enabled; otherwise calls fall back to provider native voices.
|
||||
- If a Twilio media stream is already active, Voice Call does not fall back to TwiML `<Say>`. If telephony TTS is unavailable in that state, the playback request fails instead of mixing two playback paths.
|
||||
- When telephony TTS falls back to a secondary provider, Voice Call logs a warning with the provider chain (`from`, `to`, `attempts`) for debugging.
|
||||
|
||||
### More examples
|
||||
|
||||
@@ -246,9 +242,7 @@ Use core TTS only (no override):
|
||||
messages: {
|
||||
tts: {
|
||||
provider: "openai",
|
||||
providers: {
|
||||
openai: { voice: "alloy" },
|
||||
},
|
||||
openai: { voice: "alloy" },
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -264,12 +258,10 @@ Override to ElevenLabs just for calls (keep core default elsewhere):
|
||||
config: {
|
||||
tts: {
|
||||
provider: "elevenlabs",
|
||||
providers: {
|
||||
elevenlabs: {
|
||||
apiKey: "elevenlabs_key",
|
||||
voiceId: "pMsXgVXv3BLzUgSXRplE",
|
||||
modelId: "eleven_multilingual_v2",
|
||||
},
|
||||
elevenlabs: {
|
||||
apiKey: "elevenlabs_key",
|
||||
voiceId: "pMsXgVXv3BLzUgSXRplE",
|
||||
modelId: "eleven_multilingual_v2",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -288,11 +280,9 @@ Override only the OpenAI model for calls (deep‑merge example):
|
||||
"voice-call": {
|
||||
config: {
|
||||
tts: {
|
||||
providers: {
|
||||
openai: {
|
||||
model: "gpt-4o-mini-tts",
|
||||
voice: "marin",
|
||||
},
|
||||
openai: {
|
||||
model: "gpt-4o-mini-tts",
|
||||
voice: "marin",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -53,10 +53,6 @@ OpenClaw has three public release lanes:
|
||||
- npm release preflight fails closed unless the tarball includes both
|
||||
`dist/control-ui/index.html` and a non-empty `dist/control-ui/assets/` payload
|
||||
so we do not ship an empty browser dashboard again
|
||||
- If the release work touched CI planning, extension timing manifests, or fast
|
||||
test matrices, regenerate and review the planner-owned `checks-fast-extensions`
|
||||
shard plan via `node scripts/ci-write-manifest-outputs.mjs --workflow ci`
|
||||
before approval so release notes do not describe a stale CI layout
|
||||
- Stable macOS release readiness also includes the updater surfaces:
|
||||
- the GitHub release must end up with the packaged `.zip`, `.dmg`, and `.dSYM.zip`
|
||||
- `appcast.xml` on `main` must point at the new stable zip after publish
|
||||
|
||||
@@ -187,12 +187,6 @@ Evergreen files (`MEMORY.md`, non-dated files in `memory/`) are never decayed.
|
||||
Paths can be absolute or workspace-relative. Directories are scanned
|
||||
recursively for `.md` files. Symlinks are ignored.
|
||||
|
||||
For agent-scoped cross-agent transcript search, use
|
||||
`agents.list[].memorySearch.qmd.extraCollections` instead of `memory.qmd.paths`.
|
||||
Those extra collections follow the same `{ path, name, pattern? }` shape, but
|
||||
they are merged per agent and can preserve explicit shared names when the path
|
||||
points outside the current workspace.
|
||||
|
||||
---
|
||||
|
||||
## Multimodal memory (Gemini)
|
||||
|
||||
@@ -64,40 +64,19 @@ Script: [`scripts/bench-cli-startup.ts`](https://github.com/openclaw/openclaw/bl
|
||||
|
||||
Usage:
|
||||
|
||||
- `pnpm test:startup:bench`
|
||||
- `pnpm test:startup:bench:smoke`
|
||||
- `pnpm test:startup:bench:save`
|
||||
- `pnpm test:startup:bench:update`
|
||||
- `pnpm test:startup:bench:check`
|
||||
- `pnpm tsx scripts/bench-cli-startup.ts`
|
||||
- `pnpm tsx scripts/bench-cli-startup.ts --runs 12`
|
||||
- `pnpm tsx scripts/bench-cli-startup.ts --preset real`
|
||||
- `pnpm tsx scripts/bench-cli-startup.ts --preset real --case status --case gatewayStatus --runs 3`
|
||||
- `pnpm tsx scripts/bench-cli-startup.ts --entry openclaw.mjs --entry-secondary dist/entry.js --preset all`
|
||||
- `pnpm tsx scripts/bench-cli-startup.ts --preset all --output .artifacts/cli-startup-bench-all.json`
|
||||
- `pnpm tsx scripts/bench-cli-startup.ts --preset real --case gatewayStatusJson --output .artifacts/cli-startup-bench-smoke.json`
|
||||
- `pnpm tsx scripts/bench-cli-startup.ts --preset real --cpu-prof-dir .artifacts/cli-cpu`
|
||||
- `pnpm tsx scripts/bench-cli-startup.ts --json`
|
||||
- `pnpm tsx scripts/bench-cli-startup.ts --entry dist/entry.js --timeout-ms 45000`
|
||||
|
||||
Presets:
|
||||
This benchmarks these commands:
|
||||
|
||||
- `startup`: `--version`, `--help`, `health`, `health --json`, `status --json`, `status`
|
||||
- `real`: `health`, `status`, `status --json`, `sessions`, `sessions --json`, `agents list --json`, `gateway status`, `gateway status --json`, `gateway health --json`, `config get gateway.port`
|
||||
- `all`: both presets
|
||||
- `--version`
|
||||
- `--help`
|
||||
- `health --json`
|
||||
- `status --json`
|
||||
- `status`
|
||||
|
||||
Output includes `sampleCount`, avg, p50, p95, min/max, exit-code/signal distribution, and max RSS summaries for each command. Optional `--cpu-prof-dir` / `--heap-prof-dir` writes V8 profiles per run so timing and profile capture use the same harness.
|
||||
|
||||
Saved output conventions:
|
||||
|
||||
- `pnpm test:startup:bench:smoke` writes the targeted smoke artifact at `.artifacts/cli-startup-bench-smoke.json`
|
||||
- `pnpm test:startup:bench:save` writes the full-suite artifact at `.artifacts/cli-startup-bench-all.json` using `runs=5` and `warmup=1`
|
||||
- `pnpm test:startup:bench:update` refreshes the checked-in baseline fixture at `test/fixtures/cli-startup-bench.json` using `runs=5` and `warmup=1`
|
||||
|
||||
Checked-in fixture:
|
||||
|
||||
- `test/fixtures/cli-startup-bench.json`
|
||||
- Refresh with `pnpm test:startup:bench:update`
|
||||
- Compare current results against the fixture with `pnpm test:startup:bench:check`
|
||||
Output includes avg, p50, p95, min/max, and exit-code/signal distribution for each command.
|
||||
|
||||
## Onboarding E2E (Docker)
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ Nothing is explicitly out of scope for this threat model.
|
||||
│ TRUST BOUNDARY 1: Channel Access │
|
||||
│ ┌──────────────────────────────────────────────────────────┐ │
|
||||
│ │ GATEWAY │ │
|
||||
│ │ • Device Pairing (1h DM / 5m node grace period) │ │
|
||||
│ │ • Device Pairing (30s grace period) │ │
|
||||
│ │ • AllowFrom / AllowList validation │ │
|
||||
│ │ • Token/Password/Tailscale auth │ │
|
||||
│ └──────────────────────────────────────────────────────────┘ │
|
||||
@@ -177,15 +177,15 @@ Nothing is explicitly out of scope for this threat model.
|
||||
|
||||
#### T-ACCESS-001: Pairing Code Interception
|
||||
|
||||
| Attribute | Value |
|
||||
| ----------------------- | ------------------------------------------------------------------------------------------------------------- |
|
||||
| **ATLAS ID** | AML.T0040 - AI Model Inference API Access |
|
||||
| **Description** | Attacker intercepts pairing code during pairing grace period (1h for DM channel pairing, 5m for node pairing) |
|
||||
| **Attack Vector** | Shoulder surfing, network sniffing, social engineering |
|
||||
| **Affected Components** | Device pairing system |
|
||||
| **Current Mitigations** | 1h expiry (DM pairing) / 5m expiry (node pairing), codes sent via existing channel |
|
||||
| **Residual Risk** | Medium - Grace period exploitable |
|
||||
| **Recommendations** | Reduce grace period, add confirmation step |
|
||||
| Attribute | Value |
|
||||
| ----------------------- | -------------------------------------------------------- |
|
||||
| **ATLAS ID** | AML.T0040 - AI Model Inference API Access |
|
||||
| **Description** | Attacker intercepts pairing code during 30s grace period |
|
||||
| **Attack Vector** | Shoulder surfing, network sniffing, social engineering |
|
||||
| **Affected Components** | Device pairing system |
|
||||
| **Current Mitigations** | 30s expiry, codes sent via existing channel |
|
||||
| **Residual Risk** | Medium - Grace period exploitable |
|
||||
| **Recommendations** | Reduce grace period, add confirmation step |
|
||||
|
||||
#### T-ACCESS-002: AllowFrom Spoofing
|
||||
|
||||
@@ -586,9 +586,12 @@ T-EXEC-002 → T-EXFIL-001 → External exfiltration
|
||||
| ----------------------------------- | --------------------------- | ------------ |
|
||||
| `src/infra/exec-approvals.ts` | Command approval logic | **Critical** |
|
||||
| `src/gateway/auth.ts` | Gateway authentication | **Critical** |
|
||||
| `src/web/inbound/access-control.ts` | Channel access control | **Critical** |
|
||||
| `src/infra/net/ssrf.ts` | SSRF protection | **Critical** |
|
||||
| `src/security/external-content.ts` | Prompt injection mitigation | **Critical** |
|
||||
| `src/agents/sandbox/tool-policy.ts` | Tool policy enforcement | **Critical** |
|
||||
| `convex/lib/moderation.ts` | ClawHub moderation | **High** |
|
||||
| `convex/lib/skillPublish.ts` | Skill publishing flow | **High** |
|
||||
| `src/routing/resolve-route.ts` | Session isolation | **Medium** |
|
||||
|
||||
### 7.3 Glossary
|
||||
|
||||
@@ -11,7 +11,7 @@ read_when:
|
||||
Real projects from the community. See what people are building with OpenClaw.
|
||||
|
||||
<Info>
|
||||
**Want to be featured?** Share your project in [#self-promotion on Discord](https://discord.gg/clawd) or [tag @openclaw on X](https://x.com/openclaw).
|
||||
**Want to be featured?** Share your project in [#showcase on Discord](https://discord.gg/clawd) or [tag @openclaw on X](https://x.com/openclaw).
|
||||
</Info>
|
||||
|
||||
## 🎥 OpenClaw in Action
|
||||
@@ -407,7 +407,7 @@ Have something to share? We'd love to feature it!
|
||||
|
||||
<Steps>
|
||||
<Step title="Share It">
|
||||
Post in [#self-promotion on Discord](https://discord.gg/clawd) or [tweet @openclaw](https://x.com/openclaw)
|
||||
Post in [#showcase on Discord](https://discord.gg/clawd) or [tweet @openclaw](https://x.com/openclaw)
|
||||
</Step>
|
||||
<Step title="Include Details">
|
||||
Tell us what it does, link to the repo/demo, share a screenshot if you have one
|
||||
|
||||
@@ -816,9 +816,3 @@ How it maps:
|
||||
- If a browser-capable node is connected, the tool may auto-route to it unless you pin `target="host"` or `target="node"`.
|
||||
|
||||
This keeps the agent deterministic and avoids brittle selectors.
|
||||
|
||||
## Related
|
||||
|
||||
- [Tools Overview](/tools) — all available agent tools
|
||||
- [Sandboxing](/gateway/sandboxing) — browser control in sandboxed environments
|
||||
- [Security](/gateway/security) — browser control risks and hardening
|
||||
|
||||
@@ -107,7 +107,7 @@ All fields are optional unless noted:
|
||||
- `after` (`string`): updated text. Required with `before` when `patch` is omitted.
|
||||
- `patch` (`string`): unified diff text. Mutually exclusive with `before` and `after`.
|
||||
- `path` (`string`): display filename for before and after mode.
|
||||
- `lang` (`string`): language override hint for before and after mode. Unknown values fall back to plain text.
|
||||
- `lang` (`string`): language override hint for before and after mode.
|
||||
- `title` (`string`): viewer title override.
|
||||
- `mode` (`"view" | "file" | "both"`): output mode. Defaults to plugin default `defaults.mode`.
|
||||
Deprecated alias: `"image"` behaves like `"file"` and is still accepted for backward compatibility.
|
||||
@@ -281,8 +281,6 @@ Viewer assets:
|
||||
- `/plugins/diffs/assets/viewer.js`
|
||||
- `/plugins/diffs/assets/viewer-runtime.js`
|
||||
|
||||
The viewer document resolves those assets relative to the viewer URL, so an optional `baseUrl` path prefix is preserved for both asset requests too.
|
||||
|
||||
URL construction behavior:
|
||||
|
||||
- If `baseUrl` is provided, it is used after strict validation.
|
||||
|
||||
@@ -493,10 +493,3 @@ Related:
|
||||
- [Exec tool](/tools/exec)
|
||||
- [Elevated mode](/tools/elevated)
|
||||
- [Skills](/tools/skills)
|
||||
|
||||
## Related
|
||||
|
||||
- [Exec](/tools/exec) — shell command execution tool
|
||||
- [Sandboxing](/gateway/sandboxing) — sandbox modes and workspace access
|
||||
- [Security](/gateway/security) — security model and hardening
|
||||
- [Sandbox vs Tool Policy vs Elevated](/gateway/sandbox-vs-tool-policy-vs-elevated) — when to use each
|
||||
|
||||
@@ -208,10 +208,3 @@ Notes:
|
||||
- Config lives under `tools.exec.applyPatch`.
|
||||
- `tools.exec.applyPatch.enabled` defaults to `true`; set it to `false` to disable the tool for OpenAI models.
|
||||
- `tools.exec.applyPatch.workspaceOnly` defaults to `true` (workspace-contained). Set it to `false` only if you intentionally want `apply_patch` to write/delete outside the workspace directory.
|
||||
|
||||
## Related
|
||||
|
||||
- [Exec Approvals](/tools/exec-approvals) — approval gates for shell commands
|
||||
- [Sandboxing](/gateway/sandboxing) — running commands in sandboxed environments
|
||||
- [Background Process](/gateway/background-process) — long-running exec and process tool
|
||||
- [Security](/gateway/security) — tool policy and elevated access
|
||||
|
||||
@@ -36,12 +36,12 @@ The agent calls `image_generate` automatically. No tool allow-listing needed —
|
||||
|
||||
## Supported providers
|
||||
|
||||
| Provider | Default model | Edit support | API key |
|
||||
| -------- | -------------------------------- | ----------------------- | ------------------------------------ |
|
||||
| OpenAI | `gpt-image-1` | No | `OPENAI_API_KEY` |
|
||||
| Google | `gemini-3.1-flash-image-preview` | Yes | `GEMINI_API_KEY` or `GOOGLE_API_KEY` |
|
||||
| fal | `fal-ai/flux/dev` | Yes | `FAL_KEY` |
|
||||
| MiniMax | `image-01` | Yes (subject reference) | `MINIMAX_API_KEY` |
|
||||
| Provider | Default model | Edit support | API key |
|
||||
|---|---|---|---|
|
||||
| OpenAI | `gpt-image-1` | No | `OPENAI_API_KEY` |
|
||||
| Google | `gemini-3.1-flash-image-preview` | Yes | `GEMINI_API_KEY` or `GOOGLE_API_KEY` |
|
||||
| fal | `fal-ai/flux/dev` | Yes | `FAL_KEY` |
|
||||
| MiniMax | `image-01` | Yes (subject reference) | `MINIMAX_API_KEY` |
|
||||
|
||||
Use `action: "list"` to inspect available providers and models at runtime:
|
||||
|
||||
@@ -51,18 +51,18 @@ Use `action: "list"` to inspect available providers and models at runtime:
|
||||
|
||||
## Tool parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| ------------- | -------- | ------------------------------------------------------------------------------------- |
|
||||
| `prompt` | string | Image generation prompt (required for `action: "generate"`) |
|
||||
| `action` | string | `"generate"` (default) or `"list"` to inspect providers |
|
||||
| `model` | string | Provider/model override, e.g. `openai/gpt-image-1` |
|
||||
| `image` | string | Single reference image path or URL for edit mode |
|
||||
| `images` | string[] | Multiple reference images for edit mode (up to 5) |
|
||||
| `size` | string | Size hint: `1024x1024`, `1536x1024`, `1024x1536`, `1024x1792`, `1792x1024` |
|
||||
| `aspectRatio` | string | Aspect ratio: `1:1`, `2:3`, `3:2`, `3:4`, `4:3`, `4:5`, `5:4`, `9:16`, `16:9`, `21:9` |
|
||||
| `resolution` | string | Resolution hint: `1K`, `2K`, or `4K` |
|
||||
| `count` | number | Number of images to generate (1–4) |
|
||||
| `filename` | string | Output filename hint |
|
||||
| Parameter | Type | Description |
|
||||
|---|---|---|
|
||||
| `prompt` | string | Image generation prompt (required for `action: "generate"`) |
|
||||
| `action` | string | `"generate"` (default) or `"list"` to inspect providers |
|
||||
| `model` | string | Provider/model override, e.g. `openai/gpt-image-1` |
|
||||
| `image` | string | Single reference image path or URL for edit mode |
|
||||
| `images` | string[] | Multiple reference images for edit mode (up to 5) |
|
||||
| `size` | string | Size hint: `1024x1024`, `1536x1024`, `1024x1536`, `1024x1792`, `1792x1024` |
|
||||
| `aspectRatio` | string | Aspect ratio: `1:1`, `2:3`, `3:2`, `3:4`, `4:3`, `4:5`, `5:4`, `9:16`, `16:9`, `21:9` |
|
||||
| `resolution` | string | Resolution hint: `1K`, `2K`, or `4K` |
|
||||
| `count` | number | Number of images to generate (1–4) |
|
||||
| `filename` | string | Output filename hint |
|
||||
|
||||
Not all providers support all parameters. The tool passes what each provider supports and ignores the rest.
|
||||
|
||||
@@ -110,13 +110,13 @@ Google supports up to 5 reference images via the `images` parameter. fal and Min
|
||||
|
||||
## Provider capabilities
|
||||
|
||||
| Capability | OpenAI | Google | fal | MiniMax |
|
||||
| --------------------- | ------------- | -------------------- | ------------------- | -------------------------- |
|
||||
| Generate | Yes (up to 4) | Yes (up to 4) | Yes (up to 4) | Yes (up to 9) |
|
||||
| Edit/reference | No | Yes (up to 5 images) | Yes (1 image) | Yes (1 image, subject ref) |
|
||||
| Size control | Yes | Yes | Yes | No |
|
||||
| Aspect ratio | No | Yes | Yes (generate only) | Yes |
|
||||
| Resolution (1K/2K/4K) | No | Yes | Yes | No |
|
||||
| Capability | OpenAI | Google | fal | MiniMax |
|
||||
|---|---|---|---|---|
|
||||
| Generate | Yes (up to 4) | Yes (up to 4) | Yes (up to 4) | Yes (up to 9) |
|
||||
| Edit/reference | No | Yes (up to 5 images) | Yes (1 image) | Yes (1 image, subject ref) |
|
||||
| Size control | Yes | Yes | Yes | No |
|
||||
| Aspect ratio | No | Yes | Yes (generate only) | Yes |
|
||||
| Resolution (1K/2K/4K) | No | Yes | Yes | No |
|
||||
|
||||
## Related
|
||||
|
||||
|
||||
@@ -338,9 +338,3 @@ One public example: a “second brain” CLI + Lobster pipelines that manage thr
|
||||
|
||||
- Thread: [https://x.com/plattenschieber/status/2014508656335770033](https://x.com/plattenschieber/status/2014508656335770033)
|
||||
- Repo: [https://github.com/bloomedai/brain-cli](https://github.com/bloomedai/brain-cli)
|
||||
|
||||
## Related
|
||||
|
||||
- [Cron vs Heartbeat](/automation/cron-vs-heartbeat) — scheduling Lobster workflows
|
||||
- [Automation Overview](/automation) — all automation mechanisms
|
||||
- [Tools Overview](/tools) — all available agent tools
|
||||
|
||||
@@ -154,8 +154,3 @@ Page-filtered fallback model:
|
||||
"prompt": "Extract only customer-impacting incidents"
|
||||
}
|
||||
```
|
||||
|
||||
## Related
|
||||
|
||||
- [Tools Overview](/tools) — all available agent tools
|
||||
- [Configuration Reference](/gateway/configuration-reference#agent-defaults) — pdfMaxBytesMb and pdfMaxPages config
|
||||
|
||||
@@ -320,10 +320,3 @@ See [Skills config](/tools/skills-config) for the full configuration schema.
|
||||
Browse [https://clawhub.com](https://clawhub.com).
|
||||
|
||||
---
|
||||
|
||||
## Related
|
||||
|
||||
- [Creating Skills](/tools/creating-skills) — building custom skills
|
||||
- [Skills Config](/tools/skills-config) — skill configuration reference
|
||||
- [Slash Commands](/tools/slash-commands) — all available slash commands
|
||||
- [Plugins](/tools/plugin) — plugin system overview
|
||||
|
||||
@@ -127,7 +127,6 @@ Allowlist:
|
||||
|
||||
- `agents.list[].subagents.allowAgents`: list of agent ids that can be targeted via `agentId` (`["*"]` to allow any). Default: only the requester agent.
|
||||
- Sandbox inheritance guard: if the requester session is sandboxed, `sessions_spawn` rejects targets that would run unsandboxed.
|
||||
- `agents.defaults.subagents.requireAgentId` / `agents.list[].subagents.requireAgentId`: when true, block `sessions_spawn` calls that omit `agentId` (forces explicit profile selection). Default: false.
|
||||
|
||||
Discovery:
|
||||
|
||||
|
||||
@@ -219,7 +219,6 @@ Then run:
|
||||
- `modelOverrides`: allow the model to emit TTS directives (on by default).
|
||||
- `allowProvider` defaults to `false` (provider switching is opt-in).
|
||||
- `providers.<id>`: provider-owned settings keyed by speech provider id.
|
||||
- Legacy direct provider blocks (`messages.tts.openai`, `messages.tts.elevenlabs`, `messages.tts.microsoft`, `messages.tts.edge`) are auto-migrated to `messages.tts.providers.<id>` on load.
|
||||
- `maxTextLength`: hard cap for TTS input (chars). `/tts audio` fails if exceeded.
|
||||
- `timeoutMs`: request timeout (ms).
|
||||
- `prefsPath`: override the local prefs JSON path (provider/limit/summary).
|
||||
@@ -392,11 +391,6 @@ Notes:
|
||||
- `off|always|inbound|tagged` are per‑session toggles (`/tts on` is an alias for `/tts always`).
|
||||
- `limit` and `summary` are stored in local prefs, not the main config.
|
||||
- `/tts audio` generates a one-off audio reply (does not toggle TTS on).
|
||||
- `/tts status` includes fallback visibility for the latest attempt:
|
||||
- success fallback: `Fallback: <primary> -> <used>` plus `Attempts: ...`
|
||||
- failure: `Error: ...` plus `Attempts: ...`
|
||||
- detailed diagnostics: `Attempt details: provider:outcome(reasonCode) latency`
|
||||
- OpenAI and ElevenLabs API failures now include parsed provider error detail and request id (when returned by the provider), which is surfaced in TTS errors/logs.
|
||||
|
||||
## Agent tool
|
||||
|
||||
|
||||
@@ -219,7 +219,6 @@ Then run:
|
||||
- `modelOverrides`: allow the model to emit TTS directives (on by default).
|
||||
- `allowProvider` defaults to `false` (provider switching is opt-in).
|
||||
- `providers.<id>`: provider-owned settings keyed by speech provider id.
|
||||
- Legacy direct provider blocks (`messages.tts.openai`, `messages.tts.elevenlabs`, `messages.tts.microsoft`, `messages.tts.edge`) are auto-migrated to `messages.tts.providers.<id>` on load.
|
||||
- `maxTextLength`: hard cap for TTS input (chars). `/tts audio` fails if exceeded.
|
||||
- `timeoutMs`: request timeout (ms).
|
||||
- `prefsPath`: override the local prefs JSON path (provider/limit/summary).
|
||||
@@ -392,11 +391,6 @@ Notes:
|
||||
- `off|always|inbound|tagged` are per‑session toggles (`/tts on` is an alias for `/tts always`).
|
||||
- `limit` and `summary` are stored in local prefs, not the main config.
|
||||
- `/tts audio` generates a one-off audio reply (does not toggle TTS on).
|
||||
- `/tts status` includes fallback visibility for the latest attempt:
|
||||
- success fallback: `Fallback: <primary> -> <used>` plus `Attempts: ...`
|
||||
- failure: `Error: ...` plus `Attempts: ...`
|
||||
- detailed diagnostics: `Attempt details: provider:outcome(reasonCode) latency`
|
||||
- OpenAI and ElevenLabs API failures now include parsed provider error detail and request id (when returned by the provider), which is surfaced in TTS errors/logs.
|
||||
|
||||
## Agent tool
|
||||
|
||||
|
||||
@@ -87,10 +87,7 @@ The Control UI can localize itself on first load based on your browser locale, a
|
||||
- Config: view/edit `~/.openclaw/openclaw.json` (`config.get`, `config.set`)
|
||||
- Config: apply + restart with validation (`config.apply`) and wake the last active session
|
||||
- Config writes include a base-hash guard to prevent clobbering concurrent edits
|
||||
- Config writes (`config.set`/`config.apply`/`config.patch`) also preflight active SecretRef resolution for refs in the submitted config payload; unresolved active submitted refs are rejected before write
|
||||
- Config schema + form rendering (`config.schema`, including plugin + channel schemas); Raw JSON editor is available only when the snapshot has a safe raw round-trip
|
||||
- If a snapshot cannot safely round-trip raw text, Control UI forces Form mode and disables Raw mode for that snapshot
|
||||
- Structured SecretRef object values are rendered read-only in form text inputs to prevent accidental object-to-string corruption
|
||||
- Config schema + form rendering (`config.schema`, including plugin + channel schemas); Raw JSON editor remains available
|
||||
- Debug: status/health/models snapshots + event log + manual RPC calls (`status`, `health`, `models.list`)
|
||||
- Logs: live tail of gateway file logs with filter/export (`logs.tail`)
|
||||
- Update: run a package/git update + restart (`update.run`) with a restart report
|
||||
@@ -276,10 +273,3 @@ Example:
|
||||
```
|
||||
|
||||
Remote access setup details: [Remote access](/gateway/remote).
|
||||
|
||||
## Related
|
||||
|
||||
- [Dashboard](/web/dashboard) — gateway dashboard
|
||||
- [WebChat](/web/webchat) — browser-based chat interface
|
||||
- [TUI](/web/tui) — terminal user interface
|
||||
- [Health Checks](/gateway/health) — gateway health monitoring
|
||||
|
||||
@@ -168,8 +168,3 @@ No output after sending a message:
|
||||
- `disconnected`: ensure the Gateway is running and your `--url/--token/--password` are correct.
|
||||
- No agents in picker: check `openclaw agents list` and your routing config.
|
||||
- Empty session picker: you might be in global scope or have no sessions yet.
|
||||
|
||||
## Related
|
||||
|
||||
- [Control UI](/web/control-ui) — web-based control interface
|
||||
- [CLI Reference](/cli) — full CLI command reference
|
||||
|
||||
@@ -62,7 +62,7 @@ const pluginPkg = JSON.parse(fs.readFileSync(path.join(ACPX_PLUGIN_ROOT, "packag
|
||||
const acpxVersion: unknown = pluginPkg?.dependencies?.acpx;
|
||||
if (typeof acpxVersion !== "string" || acpxVersion.trim() === "") {
|
||||
throw new Error(
|
||||
`Could not read acpx version from ${path.join(ACPX_PLUGIN_ROOT, "package.json")} — expected a non-empty string at dependencies.acpx`,
|
||||
`Could not read acpx version from ${path.join(ACPX_PLUGIN_ROOT, "package.json")} — expected a non-empty string at dependencies.acpx`
|
||||
);
|
||||
}
|
||||
export const ACPX_PINNED_VERSION: string = acpxVersion.replace(/^[^0-9]*/, "");
|
||||
|
||||
@@ -35,6 +35,7 @@ describe("mcp-proxy", () => {
|
||||
const { createInterface } = require("node:readline");
|
||||
const rl = createInterface({ input: process.stdin });
|
||||
rl.on("line", (line) => process.stdout.write(line + "\n"));
|
||||
rl.on("close", () => process.exit(0));
|
||||
`,
|
||||
);
|
||||
|
||||
|
||||
@@ -333,7 +333,7 @@ describe("spawnAndCollect", () => {
|
||||
command: process.execPath,
|
||||
args: [
|
||||
"-e",
|
||||
`process.stdout.write(JSON.stringify({openai:process.env.${openAiEnvKey},github:process.env.${githubEnvKey},hf:process.env.${hfEnvKey},openclaw:process.env.OPENCLAW_API_KEY,shell:process.env.OPENCLAW_SHELL}), () => process.exit(0))`,
|
||||
`process.stdout.write(JSON.stringify({openai:process.env.${openAiEnvKey},github:process.env.${githubEnvKey},hf:process.env.${hfEnvKey},openclaw:process.env.OPENCLAW_API_KEY,shell:process.env.OPENCLAW_SHELL}))`,
|
||||
],
|
||||
cwd: process.cwd(),
|
||||
stripProviderAuthEnvVars: options?.stripProviderAuthEnvVars,
|
||||
@@ -341,7 +341,7 @@ describe("spawnAndCollect", () => {
|
||||
|
||||
expect(result.code).toBe(0);
|
||||
expect(result.error).toBeNull();
|
||||
return JSON.parse(result.stdout.trim()) as SpawnedEnvSnapshot;
|
||||
return JSON.parse(result.stdout) as SpawnedEnvSnapshot;
|
||||
}
|
||||
|
||||
it("returns abort error immediately when signal is already aborted", async () => {
|
||||
|
||||
@@ -188,18 +188,6 @@ function createAbortError(): Error {
|
||||
return error;
|
||||
}
|
||||
|
||||
async function collectStreamOutput(stream: NodeJS.ReadableStream): Promise<string> {
|
||||
let output = "";
|
||||
try {
|
||||
for await (const chunk of stream) {
|
||||
output += String(chunk);
|
||||
}
|
||||
} catch {
|
||||
// Return whatever was captured before the stream failed.
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
export function spawnWithResolvedCommand(
|
||||
params: {
|
||||
command: string;
|
||||
@@ -292,8 +280,14 @@ export async function spawnAndCollect(
|
||||
const child = spawnWithResolvedCommand(params, options);
|
||||
child.stdin.end();
|
||||
|
||||
const stdoutPromise = collectStreamOutput(child.stdout);
|
||||
const stderrPromise = collectStreamOutput(child.stderr);
|
||||
let stdout = "";
|
||||
let stderr = "";
|
||||
child.stdout.on("data", (chunk) => {
|
||||
stdout += String(chunk);
|
||||
});
|
||||
child.stderr.on("data", (chunk) => {
|
||||
stderr += String(chunk);
|
||||
});
|
||||
|
||||
let abortKillTimer: NodeJS.Timeout | undefined;
|
||||
let aborted = false;
|
||||
@@ -320,7 +314,6 @@ export async function spawnAndCollect(
|
||||
|
||||
try {
|
||||
const exit = await waitForExit(child);
|
||||
const [stdout, stderr] = await Promise.all([stdoutPromise, stderrPromise]);
|
||||
return {
|
||||
stdout,
|
||||
stderr,
|
||||
|
||||
@@ -241,24 +241,13 @@ describe("AcpxRuntime", () => {
|
||||
expect(resumeArgs[resumeFlagIndex + 1]).toBe(resumeSessionId);
|
||||
});
|
||||
|
||||
it("retains dead named sessions when status only reports queue owner unavailable", async () => {
|
||||
it("replaces dead named sessions returned by sessions ensure", async () => {
|
||||
await expectSessionEnsureFallback({
|
||||
sessionKey: "agent:codex:acp:dead-session",
|
||||
env: {
|
||||
MOCK_ACPX_STATUS_STATUS: "dead",
|
||||
MOCK_ACPX_STATUS_SUMMARY: "queue owner unavailable",
|
||||
},
|
||||
expectNewAfterStatus: false,
|
||||
});
|
||||
});
|
||||
|
||||
it("replaces dead named sessions when status indicates an unrecoverable failure", async () => {
|
||||
await expectSessionEnsureFallback({
|
||||
sessionKey: "agent:codex:acp:dead-session-unrecoverable",
|
||||
env: {
|
||||
MOCK_ACPX_STATUS_STATUS: "dead",
|
||||
MOCK_ACPX_STATUS_SUMMARY: "agent process exited",
|
||||
},
|
||||
expectNewAfterStatus: true,
|
||||
});
|
||||
});
|
||||
@@ -275,7 +264,7 @@ describe("AcpxRuntime", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("retains the named session after ensure failure when status only reports queue owner unavailable", async () => {
|
||||
it("creates a fresh named session when sessions ensure exits and status is dead", async () => {
|
||||
await expectSessionEnsureFallback({
|
||||
sessionKey: "agent:codex:acp:ensure-fallback-dead",
|
||||
env: {
|
||||
@@ -283,19 +272,6 @@ describe("AcpxRuntime", () => {
|
||||
MOCK_ACPX_STATUS_STATUS: "dead",
|
||||
MOCK_ACPX_STATUS_SUMMARY: "queue owner unavailable",
|
||||
},
|
||||
expectNewAfterStatus: false,
|
||||
expectedRecordId: "rec-agent:codex:acp:ensure-fallback-dead",
|
||||
});
|
||||
});
|
||||
|
||||
it("creates a fresh named session after ensure failure when status indicates an unrecoverable failure", async () => {
|
||||
await expectSessionEnsureFallback({
|
||||
sessionKey: "agent:codex:acp:ensure-fallback-dead-unrecoverable",
|
||||
env: {
|
||||
MOCK_ACPX_ENSURE_EXIT_1: "1",
|
||||
MOCK_ACPX_STATUS_STATUS: "dead",
|
||||
MOCK_ACPX_STATUS_SUMMARY: "agent process exited",
|
||||
},
|
||||
expectNewAfterStatus: true,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -119,15 +119,6 @@ function summarizeLogText(text: string, maxChars = 240): string {
|
||||
return `${normalized.slice(0, maxChars)}...`;
|
||||
}
|
||||
|
||||
function shouldRetainNamedSessionForDeadStatus(detail: AcpxJsonObject | undefined): boolean {
|
||||
const status = asTrimmedString(detail?.status)?.toLowerCase();
|
||||
if (status !== "dead") {
|
||||
return false;
|
||||
}
|
||||
const summary = asTrimmedString(detail?.summary)?.toLowerCase();
|
||||
return summary?.includes("queue owner unavailable") ?? false;
|
||||
}
|
||||
|
||||
function findSessionIdentifierEvent(events: AcpxJsonObject[]): AcpxJsonObject | undefined {
|
||||
return events.find(
|
||||
(event) =>
|
||||
@@ -367,12 +358,6 @@ export class AcpxRuntime implements AcpRuntime {
|
||||
const status = asTrimmedString(detail?.status)?.toLowerCase();
|
||||
if (status === "dead") {
|
||||
const summary = summarizeLogText(asOptionalString(detail?.summary) ?? "");
|
||||
if (shouldRetainNamedSessionForDeadStatus(detail)) {
|
||||
this.logger?.warn?.(
|
||||
`acpx ensureSession retaining dead named session with recoverable status: session=${params.sessionName} cwd=${params.cwd} status=${status} summary=${summary || "<empty>"}`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
this.logger?.warn?.(
|
||||
`acpx ensureSession replacing dead named session: session=${params.sessionName} cwd=${params.cwd} status=${status} summary=${summary || "<empty>"}`,
|
||||
);
|
||||
@@ -429,13 +414,6 @@ export class AcpxRuntime implements AcpRuntime {
|
||||
const detail = events.find((event) => !toAcpxErrorEvent(event));
|
||||
const status = asTrimmedString(detail?.status)?.toLowerCase();
|
||||
if (status === "dead") {
|
||||
const summary = summarizeLogText(asOptionalString(detail?.summary) ?? "");
|
||||
if (shouldRetainNamedSessionForDeadStatus(detail)) {
|
||||
this.logger?.warn?.(
|
||||
`acpx ensureSession retaining dead named session after ensure failure with recoverable status: session=${params.sessionName} cwd=${params.cwd} status=${status} summary=${summary || "<empty>"}`,
|
||||
);
|
||||
return events;
|
||||
}
|
||||
this.logger?.warn?.(
|
||||
`acpx ensureSession replacing dead named session after ensure failure: session=${params.sessionName} cwd=${params.cwd}`,
|
||||
);
|
||||
|
||||
@@ -20,7 +20,6 @@ let logFileSequence = 0;
|
||||
const MOCK_CLI_SCRIPT = String.raw`#!/usr/bin/env node
|
||||
const fs = require("node:fs");
|
||||
|
||||
(async () => {
|
||||
const args = process.argv.slice(2);
|
||||
const logPath = process.env.MOCK_ACPX_LOG;
|
||||
const openclawShell = process.env.OPENCLAW_SHELL || "";
|
||||
@@ -29,12 +28,6 @@ const writeLog = (entry) => {
|
||||
fs.appendFileSync(logPath, JSON.stringify(entry) + "\n");
|
||||
};
|
||||
const emitJson = (payload) => process.stdout.write(JSON.stringify(payload) + "\n");
|
||||
const flushAndExit = (code) => process.stdout.write("", () => process.exit(code));
|
||||
const emitJsonAndExit = (payload, code = 0) => {
|
||||
emitJson(payload);
|
||||
flushAndExit(code);
|
||||
};
|
||||
const emitTextAndExit = (text, code = 0) => process.stdout.write(text, () => process.exit(code));
|
||||
const emitUpdate = (sessionId, update) =>
|
||||
emitJson({
|
||||
jsonrpc: "2.0",
|
||||
@@ -43,14 +36,16 @@ const emitUpdate = (sessionId, update) =>
|
||||
});
|
||||
|
||||
if (args.includes("--version")) {
|
||||
return emitTextAndExit("mock-acpx ${ACPX_PINNED_VERSION}\\n");
|
||||
process.stdout.write("mock-acpx ${ACPX_PINNED_VERSION}\\n");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (args.includes("--help")) {
|
||||
if (process.env.MOCK_ACPX_HELP_SIGNAL) {
|
||||
process.kill(process.pid, process.env.MOCK_ACPX_HELP_SIGNAL);
|
||||
}
|
||||
return emitTextAndExit("mock-acpx help\\n");
|
||||
process.stdout.write("mock-acpx help\\n");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const commandIndex = args.findIndex(
|
||||
@@ -85,14 +80,15 @@ const setValue = command === "set" ? String(args[commandIndex + 2] || "") : "";
|
||||
if (command === "sessions" && args[commandIndex + 1] === "ensure") {
|
||||
writeLog({ kind: "ensure", agent, args, sessionName: ensureName });
|
||||
if (process.env.MOCK_ACPX_ENSURE_EXIT_1 === "1") {
|
||||
return emitJsonAndExit({
|
||||
emitJson({
|
||||
jsonrpc: "2.0",
|
||||
id: null,
|
||||
error: {
|
||||
code: -32603,
|
||||
message: "mock ensure failure",
|
||||
},
|
||||
}, 1);
|
||||
});
|
||||
process.exit(1);
|
||||
}
|
||||
if (process.env.MOCK_ACPX_ENSURE_EMPTY === "1") {
|
||||
emitJson({ action: "session_ensured", name: ensureName });
|
||||
@@ -106,8 +102,7 @@ if (command === "sessions" && args[commandIndex + 1] === "ensure") {
|
||||
created: true,
|
||||
});
|
||||
}
|
||||
flushAndExit(0);
|
||||
return;
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (command === "sessions" && args[commandIndex + 1] === "new") {
|
||||
@@ -124,8 +119,7 @@ if (command === "sessions" && args[commandIndex + 1] === "new") {
|
||||
created: true,
|
||||
});
|
||||
}
|
||||
flushAndExit(0);
|
||||
return;
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (command === "config" && args[commandIndex + 1] === "show") {
|
||||
@@ -151,25 +145,26 @@ if (command === "config" && args[commandIndex + 1] === "show") {
|
||||
project: false,
|
||||
},
|
||||
});
|
||||
flushAndExit(0);
|
||||
return;
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (command === "cancel") {
|
||||
writeLog({ kind: "cancel", agent, args, sessionName: sessionFromOption });
|
||||
return emitJsonAndExit({
|
||||
emitJson({
|
||||
acpxSessionId: "sid-" + sessionFromOption,
|
||||
cancelled: true,
|
||||
});
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (command === "set-mode") {
|
||||
writeLog({ kind: "set-mode", agent, args, sessionName: sessionFromOption, mode: setModeValue });
|
||||
return emitJsonAndExit({
|
||||
emitJson({
|
||||
action: "mode_set",
|
||||
acpxSessionId: "sid-" + sessionFromOption,
|
||||
mode: setModeValue,
|
||||
});
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (command === "set") {
|
||||
@@ -187,8 +182,7 @@ if (command === "set") {
|
||||
key: setKey,
|
||||
value: setValue,
|
||||
});
|
||||
flushAndExit(0);
|
||||
return;
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (command === "status") {
|
||||
@@ -207,18 +201,18 @@ if (command === "status") {
|
||||
pid: 4242,
|
||||
uptime: 120,
|
||||
});
|
||||
flushAndExit(0);
|
||||
return;
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (command === "sessions" && args[commandIndex + 1] === "close") {
|
||||
writeLog({ kind: "close", agent, args, sessionName: closeName });
|
||||
return emitJsonAndExit({
|
||||
emitJson({
|
||||
action: "session_closed",
|
||||
acpxRecordId: "rec-" + closeName,
|
||||
acpxSessionId: "sid-" + closeName,
|
||||
name: closeName,
|
||||
});
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (command === "prompt") {
|
||||
@@ -270,16 +264,16 @@ if (command === "prompt") {
|
||||
});
|
||||
|
||||
if (stdinText.includes("trigger-error")) {
|
||||
return emitJsonAndExit({
|
||||
emitJson({
|
||||
type: "error",
|
||||
code: "-32000",
|
||||
message: "mock failure",
|
||||
}, 1);
|
||||
});
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (stdinText.includes("permission-denied")) {
|
||||
flushAndExit(5);
|
||||
return;
|
||||
process.exit(5);
|
||||
}
|
||||
|
||||
if (process.env.MOCK_ACPX_PROMPT_SIGNAL) {
|
||||
@@ -300,8 +294,7 @@ if (command === "prompt") {
|
||||
content: { type: "text", text: " gamma" },
|
||||
});
|
||||
emitJson({ type: "done", stopReason: "end_turn" });
|
||||
flushAndExit(0);
|
||||
return;
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
if (stdinText.includes("double-done")) {
|
||||
@@ -311,8 +304,7 @@ if (command === "prompt") {
|
||||
});
|
||||
emitJson({ type: "done", stopReason: "end_turn" });
|
||||
emitJson({ type: "done", stopReason: "end_turn" });
|
||||
flushAndExit(0);
|
||||
return;
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
emitUpdate(sessionFromOption, {
|
||||
@@ -331,20 +323,16 @@ if (command === "prompt") {
|
||||
content: { type: "text", text: "echo:" + stdinText.trim() },
|
||||
});
|
||||
emitJson({ type: "done", stopReason: "end_turn" });
|
||||
flushAndExit(0);
|
||||
return;
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
writeLog({ kind: "unknown", args });
|
||||
emitJsonAndExit({
|
||||
emitJson({
|
||||
type: "error",
|
||||
code: "USAGE",
|
||||
message: "unknown command",
|
||||
}, 2);
|
||||
})().catch((error) => {
|
||||
process.stderr.write(String(error) + "\\n");
|
||||
process.exit(1);
|
||||
});
|
||||
process.exit(2);
|
||||
`;
|
||||
|
||||
export async function createMockRuntimeFixture(params?: {
|
||||
|
||||
@@ -28,10 +28,6 @@ import {
|
||||
type DispatchReplyParams,
|
||||
} from "./test-support/monitor-test-support.js";
|
||||
|
||||
const { TEST_WEBHOOK_RATE_LIMIT_MAX_REQUESTS } = vi.hoisted(() => ({
|
||||
TEST_WEBHOOK_RATE_LIMIT_MAX_REQUESTS: 3,
|
||||
}));
|
||||
|
||||
// Mock dependencies
|
||||
vi.mock("./send.js", () => ({
|
||||
resolveChatGuidForTarget: vi.fn().mockResolvedValue("iMessage;-;+15551234567"),
|
||||
@@ -62,17 +58,6 @@ vi.mock("./history.js", () => ({
|
||||
fetchBlueBubblesHistory: vi.fn().mockResolvedValue({ entries: [], resolved: true }),
|
||||
}));
|
||||
|
||||
vi.mock("./runtime-api.js", async () => {
|
||||
const actual = await vi.importActual<typeof import("./runtime-api.js")>("./runtime-api.js");
|
||||
return {
|
||||
...actual,
|
||||
WEBHOOK_RATE_LIMIT_DEFAULTS: {
|
||||
...actual.WEBHOOK_RATE_LIMIT_DEFAULTS,
|
||||
maxRequests: TEST_WEBHOOK_RATE_LIMIT_MAX_REQUESTS,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
// Mock runtime
|
||||
const mockEnqueueSystemEvent = vi.fn();
|
||||
const mockBuildPairingReply = vi.fn(() => "Pairing code: TESTCODE");
|
||||
@@ -429,7 +414,8 @@ describe("BlueBubbles webhook monitor", () => {
|
||||
});
|
||||
|
||||
let saw429 = false;
|
||||
for (let i = 0; i < TEST_WEBHOOK_RATE_LIMIT_MAX_REQUESTS + 4; i += 1) {
|
||||
// Default webhook fixed-window budget is 120 requests/minute, so loop past it.
|
||||
for (let i = 0; i < 130; i += 1) {
|
||||
const candidate = String(i).padStart(8, "0");
|
||||
const { res } = await dispatchWebhookPayloadForTest(
|
||||
createPasswordQueryRequestParamsForTest({
|
||||
@@ -467,7 +453,7 @@ describe("BlueBubbles webhook monitor", () => {
|
||||
});
|
||||
|
||||
let saw429 = false;
|
||||
for (let i = 0; i < TEST_WEBHOOK_RATE_LIMIT_MAX_REQUESTS + 4; i += 1) {
|
||||
for (let i = 0; i < 130; i += 1) {
|
||||
const candidate = String(i).padStart(8, "0");
|
||||
const { res } = await dispatchWebhookPayloadForTest(
|
||||
createPasswordQueryRequestParamsForTest({
|
||||
@@ -529,7 +515,7 @@ describe("BlueBubbles webhook monitor", () => {
|
||||
});
|
||||
|
||||
let saw429 = false;
|
||||
for (let i = 0; i < TEST_WEBHOOK_RATE_LIMIT_MAX_REQUESTS + 4; i += 1) {
|
||||
for (let i = 0; i < 130; i += 1) {
|
||||
const candidate = String(i).padStart(8, "0");
|
||||
const { res } = await dispatchWebhookPayloadForTest(
|
||||
createPasswordQueryRequestParamsForTest({
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { SsrFBlockedError, type LookupFn } from "../infra/net/ssrf.js";
|
||||
import {
|
||||
assertBrowserNavigationAllowed,
|
||||
@@ -13,22 +13,7 @@ function createLookupFn(address: string): LookupFn {
|
||||
return vi.fn(async () => [{ address, family }]) as unknown as LookupFn;
|
||||
}
|
||||
|
||||
const PROXY_ENV_KEYS = [
|
||||
"HTTP_PROXY",
|
||||
"HTTPS_PROXY",
|
||||
"ALL_PROXY",
|
||||
"http_proxy",
|
||||
"https_proxy",
|
||||
"all_proxy",
|
||||
] as const;
|
||||
|
||||
describe("browser navigation guard", () => {
|
||||
beforeEach(() => {
|
||||
for (const key of PROXY_ENV_KEYS) {
|
||||
vi.stubEnv(key, "");
|
||||
}
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllEnvs();
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { SsrFBlockedError } from "../infra/net/ssrf.js";
|
||||
import { InvalidBrowserNavigationUrlError } from "./navigation-guard.js";
|
||||
import {
|
||||
@@ -10,26 +10,7 @@ import {
|
||||
installPwToolsCoreTestHooks();
|
||||
const mod = await import("./pw-tools-core.snapshot.js");
|
||||
|
||||
const PROXY_ENV_KEYS = [
|
||||
"HTTP_PROXY",
|
||||
"HTTPS_PROXY",
|
||||
"ALL_PROXY",
|
||||
"http_proxy",
|
||||
"https_proxy",
|
||||
"all_proxy",
|
||||
] as const;
|
||||
|
||||
describe("pw-tools-core.snapshot navigate guard", () => {
|
||||
beforeEach(() => {
|
||||
for (const key of PROXY_ENV_KEYS) {
|
||||
vi.stubEnv(key, "");
|
||||
}
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllEnvs();
|
||||
});
|
||||
|
||||
it("blocks unsupported non-network URLs before page lookup", async () => {
|
||||
const goto = vi.fn(async () => {});
|
||||
setPwToolsCoreCurrentPage({
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -38,8 +38,6 @@ export default definePluginEntry({
|
||||
store,
|
||||
logger: api.logger,
|
||||
allowRemoteViewer: security.allowRemoteViewer,
|
||||
trustedProxies: api.config.gateway?.trustedProxies,
|
||||
allowRealIpFallback: api.config.gateway?.allowRealIpFallback === true,
|
||||
}),
|
||||
});
|
||||
api.on("before_prompt_build", async () => ({
|
||||
|
||||
@@ -133,12 +133,14 @@
|
||||
"fileScale": {
|
||||
"type": "number",
|
||||
"minimum": 1,
|
||||
"maximum": 4
|
||||
"maximum": 4,
|
||||
"default": 2
|
||||
},
|
||||
"fileMaxWidth": {
|
||||
"type": "number",
|
||||
"minimum": 640,
|
||||
"maximum": 2400
|
||||
"maximum": 2400,
|
||||
"default": 960
|
||||
},
|
||||
"imageFormat": {
|
||||
"type": "string",
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export { resolveRequestClientIp } from "openclaw/plugin-sdk/webhook-ingress";
|
||||
@@ -261,9 +261,6 @@ describe("diffs plugin registration", () => {
|
||||
diffIndicators: "classic",
|
||||
lineSpacing: 2,
|
||||
},
|
||||
security: {
|
||||
allowRemoteViewer: true,
|
||||
},
|
||||
},
|
||||
runtime: {} as never,
|
||||
registerTool(tool: Parameters<OpenClawPluginApi["registerTool"]>[0]) {
|
||||
@@ -314,21 +311,6 @@ describe("diffs plugin registration", () => {
|
||||
agentAccountId: "default",
|
||||
},
|
||||
);
|
||||
|
||||
const proxiedRes = createMockServerResponse();
|
||||
const proxiedHandled = await registeredHttpRouteHandler?.(
|
||||
localReq({
|
||||
method: "GET",
|
||||
url: viewerPath,
|
||||
headers: {
|
||||
"x-forwarded-for": "203.0.113.10",
|
||||
},
|
||||
}),
|
||||
proxiedRes,
|
||||
);
|
||||
|
||||
expect(proxiedHandled).toBe(true);
|
||||
expect(proxiedRes.statusCode).toBe(200);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ const SHARED_BROWSER_KEY = "__default__";
|
||||
const IMAGE_SIZE_LIMIT_ERROR = "Diff frame did not render within image size limits.";
|
||||
const PDF_REFERENCE_PAGE_HEIGHT_PX = 1_056;
|
||||
const MAX_PDF_PAGES = 50;
|
||||
const LOCAL_VIEWER_BASE_HREF = "http://127.0.0.1/plugins/diffs/view/local/local";
|
||||
|
||||
export type DiffScreenshotter = {
|
||||
screenshotHtml(params: {
|
||||
@@ -275,7 +274,7 @@ function injectBaseHref(html: string): string {
|
||||
if (html.includes("<base ")) {
|
||||
return html;
|
||||
}
|
||||
return html.replace("<head>", `<head><base href="${LOCAL_VIEWER_BASE_HREF}" />`);
|
||||
return html.replace("<head>", '<head><base href="http://127.0.0.1/" />');
|
||||
}
|
||||
|
||||
async function resolveBrowserExecutablePath(config: OpenClawConfig): Promise<string | undefined> {
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
ResolvedThemes,
|
||||
ResolvingThemes,
|
||||
} from "@pierre/diffs";
|
||||
import AjvPkg from "ajv";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
DEFAULT_DIFFS_PLUGIN_SECURITY,
|
||||
@@ -165,41 +164,6 @@ describe("resolveDiffsPluginDefaults", () => {
|
||||
fileMaxWidth: 1024,
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps loader-applied schema defaults from shadowing aliases and quality-derived defaults", () => {
|
||||
const manifest = JSON.parse(
|
||||
fs.readFileSync(new URL("../openclaw.plugin.json", import.meta.url), "utf8"),
|
||||
) as { configSchema: Record<string, unknown> };
|
||||
const Ajv = AjvPkg as unknown as new (opts?: object) => import("ajv").default;
|
||||
const ajv = new Ajv({ allErrors: true, strict: false, useDefaults: true });
|
||||
const validate = ajv.compile(manifest.configSchema);
|
||||
|
||||
const aliasOnly = {
|
||||
defaults: {
|
||||
format: "pdf",
|
||||
imageQuality: "hq",
|
||||
},
|
||||
};
|
||||
expect(validate(aliasOnly)).toBe(true);
|
||||
expect(resolveDiffsPluginDefaults(aliasOnly)).toMatchObject({
|
||||
fileFormat: "pdf",
|
||||
fileQuality: "hq",
|
||||
fileScale: 2.5,
|
||||
fileMaxWidth: 1200,
|
||||
});
|
||||
|
||||
const qualityOnly = {
|
||||
defaults: {
|
||||
fileQuality: "hq",
|
||||
},
|
||||
};
|
||||
expect(validate(qualityOnly)).toBe(true);
|
||||
expect(resolveDiffsPluginDefaults(qualityOnly)).toMatchObject({
|
||||
fileQuality: "hq",
|
||||
fileScale: 2.5,
|
||||
fileMaxWidth: 1200,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveDiffsPluginSecurity", () => {
|
||||
@@ -215,63 +179,6 @@ describe("resolveDiffsPluginSecurity", () => {
|
||||
});
|
||||
|
||||
describe("diffs plugin schema surfaces", () => {
|
||||
it("preserves defaults and security for direct safeParse callers", () => {
|
||||
expect(
|
||||
diffsPluginConfigSchema.safeParse?.({
|
||||
defaults: {
|
||||
theme: "light",
|
||||
},
|
||||
security: {
|
||||
allowRemoteViewer: true,
|
||||
},
|
||||
}),
|
||||
).toMatchObject({
|
||||
success: true,
|
||||
data: {
|
||||
defaults: {
|
||||
fontFamily: "Fira Code",
|
||||
fontSize: 15,
|
||||
lineSpacing: 1.6,
|
||||
layout: "unified",
|
||||
showLineNumbers: true,
|
||||
diffIndicators: "bars",
|
||||
wordWrap: true,
|
||||
background: true,
|
||||
theme: "light",
|
||||
fileFormat: "png",
|
||||
fileQuality: "standard",
|
||||
fileScale: 2,
|
||||
fileMaxWidth: 960,
|
||||
mode: "both",
|
||||
},
|
||||
security: {
|
||||
allowRemoteViewer: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("canonicalizes alias-driven defaults for direct safeParse callers", () => {
|
||||
expect(
|
||||
diffsPluginConfigSchema.safeParse?.({
|
||||
defaults: {
|
||||
format: "pdf",
|
||||
imageQuality: "hq",
|
||||
},
|
||||
}),
|
||||
).toMatchObject({
|
||||
success: true,
|
||||
data: {
|
||||
defaults: {
|
||||
fileFormat: "pdf",
|
||||
fileQuality: "hq",
|
||||
fileScale: 2.5,
|
||||
fileMaxWidth: 1200,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps the runtime json schema in sync with the manifest config schema", () => {
|
||||
const manifest = JSON.parse(
|
||||
fs.readFileSync(new URL("../openclaw.plugin.json", import.meta.url), "utf8"),
|
||||
@@ -354,8 +261,8 @@ describe("renderDiffDocument", () => {
|
||||
expect(rendered.fileCount).toBe(1);
|
||||
expect(rendered.html).toContain("data-openclaw-diff-root");
|
||||
expect(rendered.html).toContain("src/example.ts");
|
||||
expect(rendered.html).toContain("../../assets/viewer.js");
|
||||
expect(rendered.imageHtml).toContain("../../assets/viewer.js");
|
||||
expect(rendered.html).toContain("/plugins/diffs/assets/viewer.js");
|
||||
expect(rendered.imageHtml).toContain("/plugins/diffs/assets/viewer.js");
|
||||
expect(rendered.imageHtml).toContain("max-width: 960px;");
|
||||
expect(rendered.imageHtml).toContain("--diffs-font-size: 16px;");
|
||||
expect(rendered.html).toContain("min-height: 100vh;");
|
||||
@@ -366,58 +273,6 @@ describe("renderDiffDocument", () => {
|
||||
expect(rendered.html).not.toContain("fonts.googleapis.com");
|
||||
});
|
||||
|
||||
it("resolves viewer assets under an optional base path", async () => {
|
||||
const rendered = await renderDiffDocument(
|
||||
{
|
||||
kind: "before_after",
|
||||
before: "const value = 1;\n",
|
||||
after: "const value = 2;\n",
|
||||
},
|
||||
{
|
||||
presentation: DEFAULT_DIFFS_TOOL_DEFAULTS,
|
||||
image: resolveDiffImageRenderOptions({ defaults: DEFAULT_DIFFS_TOOL_DEFAULTS }),
|
||||
expandUnchanged: false,
|
||||
},
|
||||
);
|
||||
|
||||
const html = rendered.html ?? "";
|
||||
const loaderSrc = html.match(/<script type="module" src="([^"]+)"><\/script>/)?.[1];
|
||||
expect(loaderSrc).toBe("../../assets/viewer.js");
|
||||
expect(
|
||||
new URL(loaderSrc ?? "", "https://example.com/openclaw/plugins/diffs/view/id/token").pathname,
|
||||
).toBe("/openclaw/plugins/diffs/assets/viewer.js");
|
||||
});
|
||||
|
||||
it("downgrades invalid language hints to plain text", async () => {
|
||||
const rendered = await renderDiffDocument(
|
||||
{
|
||||
kind: "before_after",
|
||||
before: "const value = 1;\n",
|
||||
after: "const value = 2;\n",
|
||||
lang: "not-a-real-language",
|
||||
},
|
||||
{
|
||||
presentation: DEFAULT_DIFFS_TOOL_DEFAULTS,
|
||||
image: resolveDiffImageRenderOptions({ defaults: DEFAULT_DIFFS_TOOL_DEFAULTS }),
|
||||
expandUnchanged: false,
|
||||
},
|
||||
);
|
||||
|
||||
const html = rendered.html ?? "";
|
||||
|
||||
expect(rendered.title).toBe("Text diff");
|
||||
expect(html).toContain("diff.txt");
|
||||
expect(html).not.toContain("not-a-real-language");
|
||||
|
||||
const payloads = [...html.matchAll(/data-openclaw-diff-payload>(.*?)<\/script>/g)].map(
|
||||
(match) => parseViewerPayloadJson(match[1] ?? ""),
|
||||
);
|
||||
expect(payloads).toHaveLength(1);
|
||||
expect(payloads[0]?.langs).toEqual(["text"]);
|
||||
expect(payloads[0]?.oldFile?.lang).toBeUndefined();
|
||||
expect(payloads[0]?.newFile?.lang).toBeUndefined();
|
||||
});
|
||||
|
||||
it("renders multi-file patch input", async () => {
|
||||
const patch = [
|
||||
"diff --git a/a.ts b/a.ts",
|
||||
@@ -544,7 +399,7 @@ describe("viewer assets", () => {
|
||||
const loader = await getServedViewerAsset(VIEWER_LOADER_PATH);
|
||||
|
||||
expect(loader?.contentType).toBe("text/javascript; charset=utf-8");
|
||||
expect(String(loader?.body)).toContain(`./viewer-runtime.js?v=`);
|
||||
expect(String(loader?.body)).toContain(`${VIEWER_RUNTIME_PATH}?v=`);
|
||||
});
|
||||
|
||||
it("serves the runtime bundle body", async () => {
|
||||
|
||||
@@ -122,8 +122,13 @@ const DiffsPluginJsonSchemaSource = z.strictObject({
|
||||
.enum(DIFF_IMAGE_QUALITY_PRESETS)
|
||||
.default(DEFAULT_DIFFS_TOOL_DEFAULTS.fileQuality)
|
||||
.optional(),
|
||||
fileScale: z.number().min(1).max(4).optional(),
|
||||
fileMaxWidth: z.number().min(640).max(2400).optional(),
|
||||
fileScale: z.number().min(1).max(4).default(DEFAULT_DIFFS_TOOL_DEFAULTS.fileScale).optional(),
|
||||
fileMaxWidth: z
|
||||
.number()
|
||||
.min(640)
|
||||
.max(2400)
|
||||
.default(DEFAULT_DIFFS_TOOL_DEFAULTS.fileMaxWidth)
|
||||
.optional(),
|
||||
imageFormat: z.enum(DIFF_OUTPUT_FORMATS).optional(),
|
||||
imageQuality: z.enum(DIFF_IMAGE_QUALITY_PRESETS).optional(),
|
||||
imageScale: z.number().min(1).max(4).optional(),
|
||||
@@ -148,48 +153,20 @@ export const diffsPluginConfigSchema: OpenClawPluginConfigSchema = buildPluginCo
|
||||
if (value === undefined) {
|
||||
return { success: true, data: undefined };
|
||||
}
|
||||
const result = DiffsPluginJsonSchemaSource.safeParse(value);
|
||||
if (result.success) {
|
||||
try {
|
||||
return { success: true, data: resolveDiffsPluginDefaults(value) };
|
||||
} catch (error) {
|
||||
return {
|
||||
success: true,
|
||||
data: buildDiffsPluginConfigShape(result.data as DiffsPluginConfig),
|
||||
success: false,
|
||||
error: {
|
||||
issues: [{ path: [], message: error instanceof Error ? error.message : String(error) }],
|
||||
},
|
||||
};
|
||||
}
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
issues: result.error.issues.map((issue) => ({
|
||||
path: issue.path.filter((segment): segment is string | number => {
|
||||
const kind = typeof segment;
|
||||
return kind === "string" || kind === "number";
|
||||
}),
|
||||
message: issue.message,
|
||||
})),
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
function resolveConfiguredValue<T>(options: {
|
||||
primary: T | undefined;
|
||||
aliases: Array<T | undefined>;
|
||||
schemaDefault?: T;
|
||||
}): T | undefined {
|
||||
const alias = options.aliases.find((value): value is T => value !== undefined);
|
||||
if (alias !== undefined && options.primary === options.schemaDefault) {
|
||||
return alias;
|
||||
}
|
||||
return options.primary ?? alias;
|
||||
}
|
||||
|
||||
function buildDiffsPluginConfigShape(config: DiffsPluginConfig): DiffsPluginConfig {
|
||||
return {
|
||||
...(config.defaults !== undefined ? { defaults: resolveDiffsPluginDefaults(config) } : {}),
|
||||
...(config.security !== undefined ? { security: resolveDiffsPluginSecurity(config) } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
export function resolveDiffsPluginDefaults(config: unknown): DiffToolDefaults {
|
||||
if (!config || typeof config !== "object" || Array.isArray(config)) {
|
||||
return { ...DEFAULT_DIFFS_TOOL_DEFAULTS };
|
||||
@@ -200,27 +177,8 @@ export function resolveDiffsPluginDefaults(config: unknown): DiffToolDefaults {
|
||||
return { ...DEFAULT_DIFFS_TOOL_DEFAULTS };
|
||||
}
|
||||
|
||||
const fileQuality = normalizeFileQuality(
|
||||
resolveConfiguredValue({
|
||||
primary: defaults.fileQuality,
|
||||
aliases: [defaults.imageQuality],
|
||||
schemaDefault: DEFAULT_DIFFS_TOOL_DEFAULTS.fileQuality,
|
||||
}),
|
||||
);
|
||||
const fileQuality = normalizeFileQuality(defaults.fileQuality ?? defaults.imageQuality);
|
||||
const profile = DEFAULT_IMAGE_QUALITY_PROFILES[fileQuality];
|
||||
const fileFormat = resolveConfiguredValue({
|
||||
primary: defaults.fileFormat,
|
||||
aliases: [defaults.imageFormat, defaults.format],
|
||||
schemaDefault: DEFAULT_DIFFS_TOOL_DEFAULTS.fileFormat,
|
||||
});
|
||||
const fileScale = resolveConfiguredValue({
|
||||
primary: defaults.fileScale,
|
||||
aliases: [defaults.imageScale],
|
||||
});
|
||||
const fileMaxWidth = resolveConfiguredValue({
|
||||
primary: defaults.fileMaxWidth,
|
||||
aliases: [defaults.imageMaxWidth],
|
||||
});
|
||||
|
||||
return {
|
||||
fontFamily: normalizeFontFamily(defaults.fontFamily),
|
||||
@@ -232,10 +190,13 @@ export function resolveDiffsPluginDefaults(config: unknown): DiffToolDefaults {
|
||||
wordWrap: defaults.wordWrap !== false,
|
||||
background: defaults.background !== false,
|
||||
theme: normalizeTheme(defaults.theme),
|
||||
fileFormat: normalizeFileFormat(fileFormat),
|
||||
fileFormat: normalizeFileFormat(defaults.fileFormat ?? defaults.imageFormat ?? defaults.format),
|
||||
fileQuality,
|
||||
fileScale: normalizeFileScale(fileScale, profile.scale),
|
||||
fileMaxWidth: normalizeFileMaxWidth(fileMaxWidth, profile.maxWidth),
|
||||
fileScale: normalizeFileScale(defaults.fileScale ?? defaults.imageScale, profile.scale),
|
||||
fileMaxWidth: normalizeFileMaxWidth(
|
||||
defaults.fileMaxWidth ?? defaults.imageMaxWidth,
|
||||
profile.maxWidth,
|
||||
),
|
||||
mode: normalizeMode(defaults.mode),
|
||||
};
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user